feat: add admin apis
This commit is contained in:
@@ -67,6 +67,7 @@ type Server struct {
|
||||
reminderH *handler.ReminderHandler
|
||||
feedbackH *handler.FeedbackHandler
|
||||
statsH *handler.StatsHandler
|
||||
adminH *handler.AdminHandler
|
||||
|
||||
// Auth state (nil when Postgres is unavailable — /auth and /users return 503)
|
||||
authIssuer *auth.Issuer
|
||||
@@ -284,6 +285,13 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
|
||||
feedbackSvc := service.NewFeedbackService(feedbackRepo, mail, cfg.FeedbackAdminEmails, logger)
|
||||
s.feedbackH = handler.NewFeedbackHandler(feedbackSvc)
|
||||
s.statsH = handler.NewStatsHandler(statsRepo, feedbackRepo, feedbackSvc)
|
||||
|
||||
// Admin panel — read-mostly listing + notify/reply/resolve actions.
|
||||
// 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, mail, logger, cfg.FrontendURL)
|
||||
s.adminH = handler.NewAdminHandler(adminSvc)
|
||||
}
|
||||
|
||||
// initShortener wires Postgres + Redis + the shorturl handler/workers.
|
||||
@@ -404,9 +412,17 @@ func (s *Server) setupRouter() {
|
||||
r.Use(chiMiddleware.Recoverer)
|
||||
r.Use(chiMiddleware.Compress(5))
|
||||
|
||||
// CORS
|
||||
// CORS — the main frontend plus, when configured, the admin panel (a
|
||||
// separate origin). Empty origins are filtered so the list never widens
|
||||
// unintentionally.
|
||||
allowedOrigins := []string{}
|
||||
for _, o := range []string{s.cfg.FrontendURL, s.cfg.AdminURL} {
|
||||
if o != "" {
|
||||
allowedOrigins = append(allowedOrigins, o)
|
||||
}
|
||||
}
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{s.cfg.FrontendURL},
|
||||
AllowedOrigins: allowedOrigins,
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-Request-ID"},
|
||||
ExposedHeaders: []string{"X-Request-ID"},
|
||||
@@ -961,6 +977,34 @@ func (s *Server) setupRouter() {
|
||||
r.Post("/timezone", s.reminderH.SetTimezone)
|
||||
}
|
||||
})
|
||||
|
||||
// --- Admin panel --- every route gated by RequireAuth + RequireAdmin
|
||||
// (role == "admin"), so the whole surface is invisible to ordinary
|
||||
// accounts. Additive: no existing route or payload changes.
|
||||
r.Route("/admin", func(r chi.Router) {
|
||||
if s.adminH == nil {
|
||||
r.HandleFunc("/*", s.authUnavailable)
|
||||
return
|
||||
}
|
||||
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
|
||||
r.Use(middleware.RequireAdmin(s.userRepo))
|
||||
r.Get("/users", s.adminH.ListUsers)
|
||||
r.Post("/users/{id}/notify", s.adminH.NotifyUser)
|
||||
r.Post("/users/{id}/ban", s.adminH.BanUser)
|
||||
r.Delete("/users/{id}/ban", s.adminH.UnbanUser)
|
||||
r.Get("/posts", s.adminH.ListPosts)
|
||||
r.Post("/posts/{id}/hide", s.adminH.HidePost)
|
||||
r.Delete("/posts/{id}/hide", s.adminH.UnhidePost)
|
||||
r.Delete("/posts/{id}", s.adminH.DeletePost)
|
||||
r.Post("/posts/{id}/restore", s.adminH.RestorePost)
|
||||
r.Delete("/comments/{id}", s.adminH.DeleteComment)
|
||||
r.Post("/comments/{id}/restore", s.adminH.RestoreComment)
|
||||
r.Get("/collections", s.adminH.ListCollections)
|
||||
r.Get("/feedback", s.adminH.ListFeedback)
|
||||
r.Post("/feedback/{id}/reply", s.adminH.ReplyFeedback)
|
||||
r.Get("/reports", s.adminH.ListReports)
|
||||
r.Post("/reports/{type}/{id}/resolve", s.adminH.ResolveReport)
|
||||
})
|
||||
})
|
||||
|
||||
s.router = r
|
||||
|
||||
Reference in New Issue
Block a user