From 04ad94ad3ce3aa09e55c1654b4ead39ed1f5557f Mon Sep 17 00:00:00 2001 From: Krispire Admin Date: Tue, 16 Jun 2026 15:48:44 +0530 Subject: [PATCH] chore: update admin apis --- internal/handler/admin.go | 10 ++++++++++ internal/repository/stats_repo.go | 26 +++++++++++++------------- internal/server/server.go | 3 ++- internal/service/admin_service.go | 12 ++++++++++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/internal/handler/admin.go b/internal/handler/admin.go index 27d1220..e58a9e4 100644 --- a/internal/handler/admin.go +++ b/internal/handler/admin.go @@ -46,6 +46,16 @@ func meta(total, limit, offset int) *response.Meta { } } +// Stats — GET /admin/stats → system snapshot for the dashboard home. +func (h *AdminHandler) Stats(w http.ResponseWriter, r *http.Request) { + snap, err := h.svc.Stats(r.Context()) + if err != nil { + response.Error(w, http.StatusInternalServerError, "could not load stats") + return + } + response.JSON(w, http.StatusOK, snap) +} + func (h *AdminHandler) ListUsers(w http.ResponseWriter, r *http.Request) { q, limit, offset := pageParams(r) items, total, err := h.svc.ListUsers(r.Context(), q, limit, offset) diff --git a/internal/repository/stats_repo.go b/internal/repository/stats_repo.go index 51532dc..a85e0e5 100644 --- a/internal/repository/stats_repo.go +++ b/internal/repository/stats_repo.go @@ -17,22 +17,22 @@ func NewStatsRepo(pg *Postgres) *StatsRepo { return &StatsRepo{pg: pg} } // StatsSnapshot bundles totals, last-7-days counts, and feedback // category breakdowns. One value rendered by /stats per request. type StatsSnapshot struct { - Users int - UsersLast7Days int - Posts int - PostsLast7Days int - Collections int - PublicCollections int - Bookmarks int - Reshares int - Feedback int - FeedbackLast7Days int - FeedbackByCategory []CategoryCount + Users int `json:"users"` + UsersLast7Days int `json:"users_last_7_days"` + Posts int `json:"posts"` + PostsLast7Days int `json:"posts_last_7_days"` + Collections int `json:"collections"` + PublicCollections int `json:"public_collections"` + Bookmarks int `json:"bookmarks"` + Reshares int `json:"reshares"` + Feedback int `json:"feedback"` + FeedbackLast7Days int `json:"feedback_last_7_days"` + FeedbackByCategory []CategoryCount `json:"feedback_by_category"` } type CategoryCount struct { - Category string - Count int + Category string `json:"category"` + Count int `json:"count"` } // Snapshot runs every count needed by /stats. Queries run in series — diff --git a/internal/server/server.go b/internal/server/server.go index 0ae9a3a..1c5eacf 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -290,7 +290,7 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) { // Reuses the notification service (delivery) and mailer (feedback reply). // Gated by RequireAuth + RequireAdmin in the router. adminRepo := repository.NewAdminRepo(s.pg) - adminSvc := service.NewAdminService(adminRepo, notifSvc, feedbackSvc, mail, logger, cfg.FrontendURL) + adminSvc := service.NewAdminService(adminRepo, statsRepo, notifSvc, feedbackSvc, mail, logger, cfg.FrontendURL) s.adminH = handler.NewAdminHandler(adminSvc) } @@ -986,6 +986,7 @@ func (s *Server) setupRouter() { } r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo)) r.Use(middleware.RequireAdmin(s.userRepo)) + r.Get("/stats", s.adminH.Stats) r.Get("/users", s.adminH.ListUsers) r.Post("/users/{id}/notify", s.adminH.NotifyUser) r.Post("/users/{id}/ban", s.adminH.BanUser) diff --git a/internal/service/admin_service.go b/internal/service/admin_service.go index ed8b344..a3961a4 100644 --- a/internal/service/admin_service.go +++ b/internal/service/admin_service.go @@ -22,6 +22,7 @@ var ErrAdminUserNotFound = errors.New("user not found") // feedback-reply email — no new infrastructure. type AdminService struct { repo *repository.AdminRepo + stats *repository.StatsRepo notif *NotificationService feedback *FeedbackService mail mailer.Mailer @@ -29,8 +30,15 @@ type AdminService struct { frontendURL string } -func NewAdminService(repo *repository.AdminRepo, notif *NotificationService, feedback *FeedbackService, mail mailer.Mailer, logger *zap.Logger, frontendURL string) *AdminService { - return &AdminService{repo: repo, notif: notif, feedback: feedback, mail: mail, logger: logger, frontendURL: frontendURL} +func NewAdminService(repo *repository.AdminRepo, stats *repository.StatsRepo, notif *NotificationService, feedback *FeedbackService, mail mailer.Mailer, logger *zap.Logger, frontendURL string) *AdminService { + return &AdminService{repo: repo, stats: stats, notif: notif, feedback: feedback, mail: mail, logger: logger, frontendURL: frontendURL} +} + +// Stats returns the system snapshot (counts + 7-day deltas + feedback +// breakdown) — the same data the public /stats page renders, as JSON for the +// admin dashboard home. +func (s *AdminService) Stats(ctx context.Context) (*repository.StatsSnapshot, error) { + return s.stats.Snapshot(ctx) } // AcknowledgeFeedback marks a feedback row acknowledged and emails the author