package admin import ( "errors" "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/ultisuite/ulti-backend/internal/api/apiresponse" "github.com/ultisuite/ulti-backend/internal/api/apivalidate" "github.com/ultisuite/ulti-backend/internal/api/middleware" "github.com/ultisuite/ulti-backend/internal/api/query" "github.com/ultisuite/ulti-backend/internal/permission" "github.com/ultisuite/ulti-backend/internal/securityaudit" ) type Handler struct { svc *Service logger *slog.Logger } func NewHandler(db *pgxpool.Pool, audit *securityaudit.Logger) *Handler { return &Handler{ svc: NewService(db, audit), logger: slog.Default().With("component", "admin-api"), } } func (h *Handler) Routes() chi.Router { r := chi.NewRouter() r.Use(middleware.RequireRole(permission.RoleAdmin)) r.Get("/users", h.ListUsers) r.Get("/users/{userID}", h.GetUser) r.Put("/users/{userID}/quota", h.SetQuota) r.Delete("/users/{userID}", h.DeleteUser) r.Get("/audit", h.ListAuditLogs) r.Get("/stats", h.GetStats) return r } func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) { params, err := query.ParseListRequest(r) if err != nil { apivalidate.WriteQueryError(w, r, err) return } result, err := h.svc.ListUsers(r.Context(), params) if err != nil { h.logger.Error("list users", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, result) } func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") if verr := validateUserID(userID); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } user, err := h.svc.GetUser(r.Context(), userID) if err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("get user", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, user) } func (h *Handler) SetQuota(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") if verr := validateUserID(userID); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } claims := middleware.ClaimsFromContext(r.Context()) var req setQuotaRequest if err := apivalidate.DecodeJSON(w, r, maxQuotaRequestBody, &req); err != nil { return } if verr := validateSetQuota(&req); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } if err := h.svc.SetQuota(r.Context(), claims.Sub, userID, req.MaxStorageBytes); err != nil { h.logger.Error("set quota", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) { userID := chi.URLParam(r, "userID") if verr := validateUserID(userID); verr != nil { apivalidate.WriteValidationError(w, r, verr) return } claims := middleware.ClaimsFromContext(r.Context()) if err := h.svc.DeleteUser(r.Context(), claims.Sub, userID); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("delete user", "error", err) apivalidate.WriteInternal(w, r) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) ListAuditLogs(w http.ResponseWriter, r *http.Request) { params, err := query.ParseListRequest(r) if err != nil { apivalidate.WriteQueryError(w, r, err) return } result, err := h.svc.ListAuditLogs(r.Context(), params) if err != nil { h.logger.Error("list audit logs", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, result) } func (h *Handler) GetStats(w http.ResponseWriter, r *http.Request) { stats, err := h.svc.GetStats(r.Context()) if err != nil { h.logger.Error("get stats", "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, stats) }