diff --git a/cmd/query-api/main.go b/cmd/query-api/main.go index db9be44..45e1a41 100644 --- a/cmd/query-api/main.go +++ b/cmd/query-api/main.go @@ -1,10 +1,12 @@ package main import ( + "database/sql" "log" "net/http" "os" "strconv" + "time" "agentmon/internal/httpx" "agentmon/internal/store/postgres" @@ -47,6 +49,81 @@ func main() { httpx.WriteJSON(w, http.StatusOK, map[string]any{"events": events}) }) + r.Get("/v1/sessions", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + f := postgres.SessionsFilter{ + Framework: q.Get("framework"), + Host: q.Get("host"), + } + + if v := q.Get("limit"); v != "" { + f.Limit, _ = strconv.Atoi(v) + } + + if v := q.Get("from"); v != "" { + if t, err := time.Parse(time.RFC3339, v); err == nil { + f.From = &t + } else if t, err := time.Parse("2006-01-02", v); err == nil { + f.From = &t + } + } + + if v := q.Get("to"); v != "" { + if t, err := time.Parse(time.RFC3339, v); err == nil { + f.To = &t + } else if t, err := time.Parse("2006-01-02", v); err == nil { + end := t.Add(24*time.Hour - time.Nanosecond) + f.To = &end + } + } + + if v := q.Get("cursor"); v != "" { + if t, err := time.Parse(time.RFC3339Nano, v); err == nil { + f.Cursor = &t + } + } + + sessions, nextCursor, err := db.ListSessions(r.Context(), f) + if err != nil { + httpx.WriteJSON(w, http.StatusInternalServerError, map[string]any{"error": "db_error"}) + return + } + + resp := map[string]any{"sessions": sessions} + if nextCursor != nil { + resp["next_cursor"] = nextCursor.Format(time.RFC3339Nano) + } + httpx.WriteJSON(w, http.StatusOK, resp) + }) + + r.Get("/v1/sessions/{sessionID}", func(w http.ResponseWriter, r *http.Request) { + sessionID := chi.URLParam(r, "sessionID") + session, runs, err := db.GetSessionWithRuns(r.Context(), sessionID) + if err == sql.ErrNoRows { + httpx.WriteJSON(w, http.StatusNotFound, map[string]any{"error": "not_found"}) + return + } + if err != nil { + httpx.WriteJSON(w, http.StatusInternalServerError, map[string]any{"error": "db_error"}) + return + } + httpx.WriteJSON(w, http.StatusOK, map[string]any{"session": session, "runs": runs}) + }) + + r.Get("/v1/runs/{runID}", func(w http.ResponseWriter, r *http.Request) { + runID := chi.URLParam(r, "runID") + run, spans, err := db.GetRunWithSpans(r.Context(), runID) + if err == sql.ErrNoRows { + httpx.WriteJSON(w, http.StatusNotFound, map[string]any{"error": "not_found"}) + return + } + if err != nil { + httpx.WriteJSON(w, http.StatusInternalServerError, map[string]any{"error": "db_error"}) + return + } + httpx.WriteJSON(w, http.StatusOK, map[string]any{"run": run, "spans": spans}) + }) + log.Printf("query-api listening on %s", addr) log.Fatal(http.ListenAndServe(addr, r)) }