diff --git a/internal/darkstorm_backend/README.md b/internal/darkstorm_backend/README.md index 416026c..7ccb267 100644 --- a/internal/darkstorm_backend/README.md +++ b/internal/darkstorm_backend/README.md @@ -118,11 +118,26 @@ If an error status code is returned then the body will be as follows. ### Count -API Key must have the `Count` permission. +API Key must have the `count` permission. Request: -> POST: /count/{uuid} +> POST: /count + +```json +{ + id: "uuid", // Should be an empty string on first request. If invalid or too old, a new UUID will be returned. + platform: "web" +} +``` + +Returns: + +```json +{ + id: "uuid" +} +``` ### User Count diff --git a/internal/darkstorm_backend/crash.go b/internal/darkstorm_backend/crash.go index 6748a27..73b660e 100644 --- a/internal/darkstorm_backend/crash.go +++ b/internal/darkstorm_backend/crash.go @@ -4,6 +4,7 @@ import ( "encoding/json" "log" "net/http" + "strings" ) type ArchivedCrash struct { @@ -163,6 +164,38 @@ func (b *Backend) actualCrashArchive(w http.ResponseWriter, ap App, toArchive Ar } err := crash.Archive(toArchive) if err != nil { - log.Println() + log.Println("error archive crash:", err) + return + } + first, _, _ := strings.Cut(toArchive.Stack, "\n") + crashes, err := crash.Find(map[string]any{"error": toArchive.Error, "firstLine": first}) + if err == ErrNotFound { + return + } else if err != nil { + ReturnError(w, http.StatusInternalServerError, "internal", "Server error") + return + } + for _, c := range crashes { + ogLen := len(c.Individual) + for i := 0; i < len(c.Individual); i++ { + ind := c.Individual[i] + if ind.Stack == toArchive.Stack { + if toArchive.Platform == "all" || toArchive.Platform == ind.Platform { + c.Individual = append(c.Individual[:i], c.Individual[i+1:]...) + i-- + } + } + } + if len(c.Individual) == 0 { + err = crash.Remove(c.ID) + if err != nil { + log.Println("error removing empty crash report:", err) + } + } else if len(c.Individual) < ogLen { + err = crash.PartUpdate(c.ID, map[string]any{"individual": c.Individual}) + if err != nil { + log.Println("error updating individual crash reports:", err) + } + } } } diff --git a/internal/darkstorm_backend/darkstorm.go b/internal/darkstorm_backend/darkstorm.go index 8feee4b..06e5d05 100644 --- a/internal/darkstorm_backend/darkstorm.go +++ b/internal/darkstorm_backend/darkstorm.go @@ -45,7 +45,7 @@ func NewBackend(keyTable Table[ApiKey], apps ...App) (*Backend, error) { } } if hasLog { - b.m.HandleFunc("POST /count/{uuid}", b.countLog) + b.m.HandleFunc("POST /count", b.countLog) b.m.HandleFunc("GET /count", b.getCount) } if hasCrash { @@ -59,8 +59,7 @@ func NewBackend(keyTable Table[ApiKey], apps ...App) (*Backend, error) { func (b *Backend) cleanupLoop() { for range time.Tick(24 * time.Hour) { - oldTim := time.Now().Add(-30 * 24 * time.Hour) - old := (oldTim.Year() * 10000) + (int(oldTim.Month()) * 100) + oldTim.Day() + old := getDate(time.Now().Add(-30 * 24 * time.Hour)) for _, a := range b.apps { tab := a.CountTable() if tab == nil { @@ -71,6 +70,10 @@ func (b *Backend) cleanupLoop() { } } +func getDate(t time.Time) int { + return (t.Year() * 10000) + (int(t.Month()) * 100) + t.Day() +} + func (b *Backend) EnableManagementKey(managementID string) { b.managementKeyID = managementID b.m.HandleFunc("DELETE /{appID}/crash/{crashID}", b.managementDeleteCrash) diff --git a/internal/darkstorm_backend/darkstorm_test.go b/internal/darkstorm_backend/darkstorm_test.go index 53758c2..fd1346e 100644 --- a/internal/darkstorm_backend/darkstorm_test.go +++ b/internal/darkstorm_backend/darkstorm_test.go @@ -1,10 +1,23 @@ package darkstorm import ( - "os" + "fmt" "testing" + "time" + + "github.com/google/uuid" ) -func TestMain(t *testing.M) { - os.Exit(t.Run()) +func TestStuff(t *testing.T) { + for i := 0; i < 50; i++ { + go func() { + id, err := uuid.NewV7() + if err != nil { + fmt.Println(err) + } + fmt.Println(id.String()) + }() + } + time.Sleep(3 * time.Second) + t.Fatal("end") } diff --git a/internal/darkstorm_backend/db.go b/internal/darkstorm_backend/db.go index f58b8a1..189f993 100644 --- a/internal/darkstorm_backend/db.go +++ b/internal/darkstorm_backend/db.go @@ -23,13 +23,13 @@ type CountTable interface { Table[CountLog] // Remove all Log items that have a CountLog.Date value less then the given value. RemoveOldLogs(date int) + // Get count. If platform is an empty string or "all", the full count should be given Count(platform string) int } type CrashTable interface { Table[CrashReport] - // Move a crash type to archive. All instances that perfectly match that appear in CrashReport.Individual should be deleted. - // If a CrashReport ends up with an empty Individual array it should also be deleted. + // Move a crash type to archive. Crashes that match the archived crash will be automatically removed from the CrashTable. Archive(ArchivedCrash) error IsArchived(IndividualCrash) bool // Add the IndividualCrash report to the crash table. If a CrashReport exists that matches, then it gets added to CrashReport.Individual. diff --git a/internal/darkstorm_backend/log.go b/internal/darkstorm_backend/log.go index bf4cb0a..0d6972d 100644 --- a/internal/darkstorm_backend/log.go +++ b/internal/darkstorm_backend/log.go @@ -1,20 +1,29 @@ package darkstorm import ( + "encoding/json" "log" "net/http" + "time" + + "github.com/google/uuid" ) type CountLog struct { - ID string - Platform string - Date int + ID string `json:"id" bson:"_id"` + Platform string `json:"platform" bson:"platform"` + Date int `json:"date" bson:"date"` } func (c CountLog) GetID() string { return c.ID } +type countLogReq struct { + ID string + Platform string +} + func (b *Backend) countLog(w http.ResponseWriter, r *http.Request) { hdr, err := b.VerifyHeader(w, r, "count", false) if hdr == nil { @@ -23,7 +32,69 @@ func (b *Backend) countLog(w http.ResponseWriter, r *http.Request) { } return } - //TODO + defer r.Body.Close() + var req countLogReq + err = json.NewDecoder(r.Body).Decode(&req) + if err != nil || req.Platform == "" { + ReturnError(w, http.StatusBadRequest, "invalidBody", "Bad request") + return + } + ap := b.GetApp(hdr.Key) + count := ap.CountTable() + if count == nil { + ReturnError(w, http.StatusInternalServerError, "misconfigured", "Server Misconfigured") + return + } + curDate := getDate(time.Now()) + if req.ID == "" { + err = addToCountTable(w, count, req.Platform, curDate) + if err != nil { + log.Println("error adding to count table:", err) + } + return + } + l, err := count.Get(req.ID) + if err == ErrNotFound { + err = addToCountTable(w, count, req.Platform, curDate) + if err != nil { + log.Println("error adding to count table:", err) + } + return + } + if l.Date >= curDate { + json.NewEncoder(w).Encode(map[string]string{"id": req.ID}) + w.WriteHeader(http.StatusCreated) + return + } + err = count.PartUpdate(req.ID, map[string]any{"date": curDate}) + if err != nil { + ReturnError(w, http.StatusInternalServerError, "internal", "Server error") + return + } + json.NewEncoder(w).Encode(map[string]string{"id": req.ID}) + w.WriteHeader(http.StatusCreated) +} + +func addToCountTable(w http.ResponseWriter, c CountTable, platform string, curDate int) error { + id, err := uuid.NewV7() + if err != nil { + log.Println("error generating new log UUID:", err) + ReturnError(w, http.StatusInternalServerError, "internal", "Server error") + return err + } + err = c.Insert(CountLog{ + ID: id.String(), + Platform: platform, + Date: curDate, + }) + if err != nil { + log.Println("error inserting new count log:", err) + ReturnError(w, http.StatusInternalServerError, "internal", "Server error") + return err + } + json.NewEncoder(w).Encode(map[string]string{"id": id.String()}) + w.WriteHeader(http.StatusCreated) + return nil } func (b *Backend) getCount(w http.ResponseWriter, r *http.Request) { @@ -44,5 +115,11 @@ func (b *Backend) getCount(w http.ResponseWriter, r *http.Request) { } else { ap = b.GetApp(hdr.Key) } - //TODO + count := ap.CountTable() + if count == nil { + ReturnError(w, http.StatusBadRequest, "badRequest", "Trying to get user count on app that doesn't have a count table") + return + } + out := count.Count(r.URL.Query().Get("platform")) + json.NewEncoder(w).Encode(map[string]int{"count": out}) }