package app // Legacy admin handlers retired in favor of ResolveSpec-backed routes. import ( "encoding/json" "log/slog" "net/http" "strconv" "strings" "git.warky.dev/wdevs/amcs/internal/store" ext "git.warky.dev/wdevs/amcs/internal/types" "github.com/google/uuid" ) type adminHandlers struct { db *store.DB logger *slog.Logger } func newAdminHandlers(db *store.DB, logger *slog.Logger) *adminHandlers { return &adminHandlers{db: db, logger: logger} } func (h *adminHandlers) register(mux *http.ServeMux, middleware func(http.Handler) http.Handler) { handle := func(pattern string, fn http.HandlerFunc) { mux.Handle(pattern, middleware(fn)) } handle("GET /api/admin/projects", h.listProjects) handle("POST /api/admin/projects", h.createProject) handle("GET /api/admin/thoughts", h.listThoughts) handle("GET /api/admin/thoughts/{id}", h.getThought) handle("DELETE /api/admin/thoughts/{id}", h.deleteThought) handle("POST /api/admin/thoughts/{id}/archive", h.archiveThought) handle("GET /api/admin/skills", h.listSkills) handle("DELETE /api/admin/skills/{id}", h.deleteSkill) handle("GET /api/admin/guardrails", h.listGuardrails) handle("DELETE /api/admin/guardrails/{id}", h.deleteGuardrail) handle("GET /api/admin/files", h.listFiles) handle("GET /api/admin/stats", h.stats) } // --- Projects --- func (h *adminHandlers) listProjects(w http.ResponseWriter, r *http.Request) { projects, err := h.db.ListProjects(r.Context()) if err != nil { h.internalError(w, "list projects", err) return } writeJSON(w, projects) } func (h *adminHandlers) createProject(w http.ResponseWriter, r *http.Request) { var body struct { Name string `json:"name"` Description string `json:"description"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } if strings.TrimSpace(body.Name) == "" { http.Error(w, "name is required", http.StatusBadRequest) return } project, err := h.db.CreateProject(r.Context(), body.Name, body.Description) if err != nil { h.internalError(w, "create project", err) return } w.WriteHeader(http.StatusCreated) writeJSON(w, project) } // --- Thoughts --- func (h *adminHandlers) listThoughts(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() limit := 50 if l := q.Get("limit"); l != "" { if n, err := strconv.Atoi(l); err == nil && n > 0 { limit = min(n, 200) } } query := strings.TrimSpace(q.Get("q")) includeArchived := q.Get("include_archived") == "true" var projectID *uuid.UUID if pid := q.Get("project_id"); pid != "" { if id, err := uuid.Parse(pid); err == nil { projectID = &id } } if query != "" { results, err := h.db.SearchThoughtsText(r.Context(), query, limit, projectID, nil) if err != nil { h.internalError(w, "search thoughts", err) return } writeJSON(w, results) return } thoughts, err := h.db.ListThoughts(r.Context(), ext.ListFilter{ Limit: limit, ProjectID: projectID, IncludeArchived: includeArchived, }) if err != nil { h.internalError(w, "list thoughts", err) return } writeJSON(w, thoughts) } func (h *adminHandlers) getThought(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(w, r.PathValue("id")) if !ok { return } thought, err := h.db.GetThought(r.Context(), id) if err != nil { h.internalError(w, "get thought", err) return } writeJSON(w, thought) } func (h *adminHandlers) deleteThought(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(w, r.PathValue("id")) if !ok { return } if err := h.db.DeleteThought(r.Context(), id); err != nil { h.internalError(w, "delete thought", err) return } w.WriteHeader(http.StatusNoContent) } func (h *adminHandlers) archiveThought(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(w, r.PathValue("id")) if !ok { return } if err := h.db.ArchiveThought(r.Context(), id); err != nil { h.internalError(w, "archive thought", err) return } w.WriteHeader(http.StatusNoContent) } // --- Skills --- func (h *adminHandlers) listSkills(w http.ResponseWriter, r *http.Request) { tag := r.URL.Query().Get("tag") skills, err := h.db.ListSkills(r.Context(), tag) if err != nil { h.internalError(w, "list skills", err) return } writeJSON(w, skills) } func (h *adminHandlers) deleteSkill(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(w, r.PathValue("id")) if !ok { return } if err := h.db.RemoveSkill(r.Context(), id); err != nil { h.internalError(w, "delete skill", err) return } w.WriteHeader(http.StatusNoContent) } // --- Guardrails --- func (h *adminHandlers) listGuardrails(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() guardrails, err := h.db.ListGuardrails(r.Context(), q.Get("tag"), q.Get("severity")) if err != nil { h.internalError(w, "list guardrails", err) return } writeJSON(w, guardrails) } func (h *adminHandlers) deleteGuardrail(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(w, r.PathValue("id")) if !ok { return } if err := h.db.RemoveGuardrail(r.Context(), id); err != nil { h.internalError(w, "delete guardrail", err) return } w.WriteHeader(http.StatusNoContent) } // --- Files --- func (h *adminHandlers) listFiles(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() limit := 100 if l := q.Get("limit"); l != "" { if n, err := strconv.Atoi(l); err == nil && n > 0 { limit = min(n, 500) } } filter := ext.StoredFileFilter{Limit: limit} if pid := q.Get("project_id"); pid != "" { if id, err := uuid.Parse(pid); err == nil { filter.ProjectID = &id } } if tid := q.Get("thought_id"); tid != "" { if id, err := uuid.Parse(tid); err == nil { filter.ThoughtID = &id } } filter.Kind = q.Get("kind") files, err := h.db.ListStoredFiles(r.Context(), filter) if err != nil { h.internalError(w, "list files", err) return } writeJSON(w, files) } // --- Stats --- func (h *adminHandlers) stats(w http.ResponseWriter, r *http.Request) { stats, err := h.db.Stats(r.Context()) if err != nil { h.internalError(w, "stats", err) return } writeJSON(w, stats) } // --- Helpers --- func (h *adminHandlers) internalError(w http.ResponseWriter, op string, err error) { h.logger.Error("admin handler error", slog.String("op", op), slog.String("error", err.Error())) http.Error(w, "internal server error", http.StatusInternalServerError) } func writeJSON(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) } func parseUUID(w http.ResponseWriter, s string) (uuid.UUID, bool) { id, err := uuid.Parse(s) if err != nil { http.Error(w, "invalid id", http.StatusBadRequest) return uuid.UUID{}, false } return id, true }