chore: update admin apis

This commit is contained in:
Krispire Admin
2026-06-16 15:48:44 +05:30
parent 4c42d9a7f9
commit 04ad94ad3c
4 changed files with 35 additions and 16 deletions
+10
View File
@@ -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) { func (h *AdminHandler) ListUsers(w http.ResponseWriter, r *http.Request) {
q, limit, offset := pageParams(r) q, limit, offset := pageParams(r)
items, total, err := h.svc.ListUsers(r.Context(), q, limit, offset) items, total, err := h.svc.ListUsers(r.Context(), q, limit, offset)
+13 -13
View File
@@ -17,22 +17,22 @@ func NewStatsRepo(pg *Postgres) *StatsRepo { return &StatsRepo{pg: pg} }
// StatsSnapshot bundles totals, last-7-days counts, and feedback // StatsSnapshot bundles totals, last-7-days counts, and feedback
// category breakdowns. One value rendered by /stats per request. // category breakdowns. One value rendered by /stats per request.
type StatsSnapshot struct { type StatsSnapshot struct {
Users int Users int `json:"users"`
UsersLast7Days int UsersLast7Days int `json:"users_last_7_days"`
Posts int Posts int `json:"posts"`
PostsLast7Days int PostsLast7Days int `json:"posts_last_7_days"`
Collections int Collections int `json:"collections"`
PublicCollections int PublicCollections int `json:"public_collections"`
Bookmarks int Bookmarks int `json:"bookmarks"`
Reshares int Reshares int `json:"reshares"`
Feedback int Feedback int `json:"feedback"`
FeedbackLast7Days int FeedbackLast7Days int `json:"feedback_last_7_days"`
FeedbackByCategory []CategoryCount FeedbackByCategory []CategoryCount `json:"feedback_by_category"`
} }
type CategoryCount struct { type CategoryCount struct {
Category string Category string `json:"category"`
Count int Count int `json:"count"`
} }
// Snapshot runs every count needed by /stats. Queries run in series — // Snapshot runs every count needed by /stats. Queries run in series —
+2 -1
View File
@@ -290,7 +290,7 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
// Reuses the notification service (delivery) and mailer (feedback reply). // Reuses the notification service (delivery) and mailer (feedback reply).
// Gated by RequireAuth + RequireAdmin in the router. // Gated by RequireAuth + RequireAdmin in the router.
adminRepo := repository.NewAdminRepo(s.pg) 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) s.adminH = handler.NewAdminHandler(adminSvc)
} }
@@ -986,6 +986,7 @@ func (s *Server) setupRouter() {
} }
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo)) r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
r.Use(middleware.RequireAdmin(s.userRepo)) r.Use(middleware.RequireAdmin(s.userRepo))
r.Get("/stats", s.adminH.Stats)
r.Get("/users", s.adminH.ListUsers) r.Get("/users", s.adminH.ListUsers)
r.Post("/users/{id}/notify", s.adminH.NotifyUser) r.Post("/users/{id}/notify", s.adminH.NotifyUser)
r.Post("/users/{id}/ban", s.adminH.BanUser) r.Post("/users/{id}/ban", s.adminH.BanUser)
+10 -2
View File
@@ -22,6 +22,7 @@ var ErrAdminUserNotFound = errors.New("user not found")
// feedback-reply email — no new infrastructure. // feedback-reply email — no new infrastructure.
type AdminService struct { type AdminService struct {
repo *repository.AdminRepo repo *repository.AdminRepo
stats *repository.StatsRepo
notif *NotificationService notif *NotificationService
feedback *FeedbackService feedback *FeedbackService
mail mailer.Mailer mail mailer.Mailer
@@ -29,8 +30,15 @@ type AdminService struct {
frontendURL string frontendURL string
} }
func NewAdminService(repo *repository.AdminRepo, notif *NotificationService, feedback *FeedbackService, mail mailer.Mailer, logger *zap.Logger, frontendURL string) *AdminService { 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, notif: notif, feedback: feedback, mail: mail, logger: logger, frontendURL: frontendURL} 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 // AcknowledgeFeedback marks a feedback row acknowledged and emails the author