summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-05-06 20:32:35 +0300
committerPaul Buetow <paul@buetow.org>2024-05-06 20:32:35 +0300
commit8c2e75df51d1a74d8bca1d3d1a423f777159ba82 (patch)
treeb340984869230f92d0aea54d104efc5618c54954 /internal
parentd06809e50aa20b7f2e12ed43374aebb39e77a220 (diff)
refactor project file structure
Diffstat (limited to 'internal')
-rw-r--r--internal/io.go16
-rw-r--r--internal/server/handle/handle.go80
-rw-r--r--internal/server/health/health.go104
-rw-r--r--internal/server/health/health_test.go24
-rw-r--r--internal/server/repository/repository.go36
-rw-r--r--internal/types/entry.go34
6 files changed, 294 insertions, 0 deletions
diff --git a/internal/io.go b/internal/io.go
new file mode 100644
index 0000000..c0f28bf
--- /dev/null
+++ b/internal/io.go
@@ -0,0 +1,16 @@
+package internal
+
+import (
+ "os"
+ "path/filepath"
+)
+
+func SaveFile(filePath string, bytes []byte) error {
+ dir := filepath.Dir(filePath)
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ }
+ return os.WriteFile(filePath, bytes, 0644)
+}
diff --git a/internal/server/handle/handle.go b/internal/server/handle/handle.go
new file mode 100644
index 0000000..d1f7748
--- /dev/null
+++ b/internal/server/handle/handle.go
@@ -0,0 +1,80 @@
+package handle
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "regexp"
+ "time"
+
+ "codeberg.org/snonux/gos/internal"
+ "codeberg.org/snonux/gos/internal/server/repository"
+ "codeberg.org/snonux/gos/internal/types"
+)
+
+var getIDRe = regexp.MustCompile(`^/[0-9]{4}/[a-z0-9]{64}\.json$`)
+
+func Submit(w http.ResponseWriter, r *http.Request, dataDir string) error {
+ if r.Method != "POST" {
+ return fmt.Errorf("expexted POST request")
+ }
+
+ bytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ return err
+ }
+
+ entry, err := types.NewEntry(bytes)
+ if err != nil {
+ return err
+ }
+ filePath := fmt.Sprintf("%s/%s/%s.json", dataDir, time.Now().Format("2006"), entry.ID)
+
+ jsonStr, err := entry.Serialize()
+ if err != nil {
+ return err
+ }
+
+ if err := internal.SaveFile(filePath, jsonStr); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func List(w http.ResponseWriter, r *http.Request, dataDir string) error {
+ if r.Method != "GET" {
+ return fmt.Errorf("expexted GET request")
+ }
+
+ repository := repository.New(dataDir)
+ ids, err := repository.List()
+ if err != nil {
+ return err
+ }
+
+ jsonData, err := json.Marshal(ids)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprint(w, string(jsonData))
+ return nil
+}
+
+func Get(w http.ResponseWriter, r *http.Request, dataDir string) error {
+ path := r.URL.Query().Get("path")
+ if !getIDRe.MatchString(path) {
+ return fmt.Errorf("invalid path %s", path)
+ }
+
+ data, err := os.ReadFile(fmt.Sprintf("%s/%s", dataDir, path))
+ if err != err {
+ return err
+ }
+
+ fmt.Fprint(w, string(data))
+ return nil
+}
diff --git a/internal/server/health/health.go b/internal/server/health/health.go
new file mode 100644
index 0000000..d87bd3d
--- /dev/null
+++ b/internal/server/health/health.go
@@ -0,0 +1,104 @@
+package health
+
+import (
+ "fmt"
+ "log"
+ "strings"
+ "sync"
+)
+
+type Severity int
+
+const (
+ OK Severity = iota
+ Warning
+ Critical
+ Unknown
+)
+
+func (s Severity) String() string {
+ switch s {
+ case OK:
+ return "OK"
+ case Warning:
+ return "WARNING"
+ case Critical:
+ return "CRITICAL"
+ case Unknown:
+ fallthrough
+ default:
+ return "UNKNOWN"
+ }
+}
+
+type alert struct {
+ text string
+ severity Severity
+}
+
+func (a alert) String() string {
+ return fmt.Sprintf("%s: %s", a.severity, a.text)
+}
+
+type Status struct {
+ alerts map[string]alert
+ mu sync.Mutex
+}
+
+func NewStatus() Status {
+ return Status{
+ alerts: make(map[string]alert),
+ }
+}
+
+func (hs Status) Set(s Severity, what, text string) {
+ log.Println("alerting", what, "to", text, "with severity", s)
+
+ hs.mu.Lock()
+ defer hs.mu.Unlock()
+
+ hs.alerts[what] = alert{
+ text: text,
+ severity: s,
+ }
+}
+
+func (hs Status) Clear(what string) {
+ hs.mu.Lock()
+ defer hs.mu.Unlock()
+
+ if _, ok := hs.alerts[what]; ok {
+ log.Println("clearing alert for", what)
+ delete(hs.alerts, what)
+ }
+}
+
+func (hs Status) String() string {
+ var (
+ alerts [4][]string // Alerts by severity
+ sb strings.Builder
+ )
+
+ hs.mu.Lock()
+ defer hs.mu.Unlock()
+
+ for _, alert := range hs.alerts {
+ alerts[alert.severity] = append(alerts[alert.severity], alert.String())
+ }
+
+ possible := [4]Severity{Unknown, Critical, Warning, OK}
+ for _, severity := range possible {
+ if len(alerts[severity]) == 0 {
+ continue
+ }
+ for _, alert := range alerts[severity] {
+ sb.WriteString(alert)
+ sb.WriteString("\n")
+ }
+ }
+
+ if result := sb.String(); result != "" {
+ return result
+ }
+ return "OK: all is fine :-)\n"
+}
diff --git a/internal/server/health/health_test.go b/internal/server/health/health_test.go
new file mode 100644
index 0000000..1723d42
--- /dev/null
+++ b/internal/server/health/health_test.go
@@ -0,0 +1,24 @@
+package health
+
+import "testing"
+
+func TestHealthStatus(t *testing.T) {
+ t.Parallel()
+
+ h := NewStatus()
+ h.Set(warning, "fooService", "this is not good")
+ h.Set(critical, "barService", "this is not good either")
+ h.Set(warning, "bazService", "urgh!")
+ h.Set(unknown, "bazService", "don't know what happened here!")
+ h.Clear("fooService")
+
+ result := h.String()
+ expected := `UNKNOWN: don't know what happened here!
+CRITICAL: this is not good either
+`
+
+ if result != expected {
+ t.Error("expected", expected, "but got", result)
+ }
+ t.Log("got as expexted", result)
+}
diff --git a/internal/server/repository/repository.go b/internal/server/repository/repository.go
new file mode 100644
index 0000000..5fe79fd
--- /dev/null
+++ b/internal/server/repository/repository.go
@@ -0,0 +1,36 @@
+package repository
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+type Repository struct {
+ dataDir string
+}
+
+func New(dataDir string) Repository {
+ return Repository{dataDir}
+}
+
+func (r Repository) List() ([]string, error) {
+ var ids []string
+
+ visit := func(files *[]string) filepath.WalkFunc {
+ return func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ log.Println(err)
+ return nil
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".json") {
+ *files = append(*files, strings.TrimPrefix(path, r.dataDir))
+ }
+ return nil
+ }
+ }
+
+ err := filepath.Walk(r.dataDir, visit(&ids))
+ return ids, err
+}
diff --git a/internal/types/entry.go b/internal/types/entry.go
new file mode 100644
index 0000000..a274e2c
--- /dev/null
+++ b/internal/types/entry.go
@@ -0,0 +1,34 @@
+package types
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+)
+
+type Shared struct {
+ Name string `json:"id"`
+ Is bool `json:"is,omitempty"`
+}
+
+type Entry struct {
+ Body string `json:"body"`
+ Shared []Shared `json:"shared,omitempty"`
+ Epoch int `json:"epoch,omitempty"`
+ ID string `json:"id,omitempty"`
+}
+
+func NewEntry(bytes []byte) (Entry, error) {
+ var entry Entry
+ if err := json.Unmarshal(bytes, &entry); err != nil {
+ return entry, fmt.Errorf("unable to deserialise payload: %w", err)
+ }
+ if entry.ID == "" {
+ entry.ID = fmt.Sprintf("%x", sha256.Sum256(bytes))
+ }
+ return entry, nil
+}
+
+func (e Entry) Serialize() ([]byte, error) {
+ return json.Marshal(e)
+}