package admin import ( "context" "crypto/subtle" "log" "strings" "time" "boostai-backend/internal/config" "boostai-backend/internal/database" "boostai-backend/internal/http/respond" "boostai-backend/internal/seeddata" "github.com/gofiber/fiber/v2" ) const ( ReseedHeaderName = "X-Admin-Reseed-Secret" ReseedConfirm = "RESEED" ) type Runner interface { Run(ctx context.Context, mockDataDir string) (seeddata.Summary, error) } type Handler struct { cfg *config.Config runner runFunc } type runFunc func(timeoutCtx context.Context, mockDataDir string) (seeddata.Summary, error) type reseedRequest struct { Confirm string `json:"confirm"` } type reseedResponse struct { OK bool `json:"ok"` Environment string `json:"environment"` TriggeredBy string `json:"triggered_by,omitempty"` TriggeredAt time.Time `json:"triggered_at"` Summary seeddata.Summary `json:"summary"` } func NewHandler(db *database.DB, cfg *config.Config) *Handler { return &Handler{ cfg: cfg, runner: func(timeoutCtx context.Context, mockDataDir string) (seeddata.Summary, error) { return seeddata.Run(timeoutCtx, db, mockDataDir) }, } } func (h *Handler) ReseedDatabase(c *fiber.Ctx) error { if !h.cfg.AdminReseedEnabled { return respond.Error(c, fiber.StatusNotFound, "not_found", "The requested endpoint does not exist") } if strings.TrimSpace(h.cfg.AdminReseedSecret) == "" { return respond.Error(c, fiber.StatusServiceUnavailable, "admin_reseed_unavailable", "Admin reseed is not configured") } providedSecret := strings.TrimSpace(c.Get(ReseedHeaderName)) if subtle.ConstantTimeCompare([]byte(providedSecret), []byte(h.cfg.AdminReseedSecret)) != 1 { return respond.Error(c, fiber.StatusForbidden, "forbidden", "Valid reseed secret required") } var req reseedRequest if err := c.BodyParser(&req); err != nil { return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Invalid request body") } if strings.TrimSpace(req.Confirm) != ReseedConfirm { return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "confirm must equal RESEED") } triggeredBy, _ := c.Locals("auth.email").(string) userID, _ := c.Locals("auth.user_id").(int64) startedAt := time.Now().UTC() log.Printf("admin reseed requested environment=%s user_id=%d email=%s ip=%s", h.cfg.Environment, userID, triggeredBy, c.IP()) timeoutCtx, cancelTimeout := context.WithTimeout(context.Background(), 2*time.Minute) defer cancelTimeout() summary, err := h.runner(timeoutCtx, h.cfg.MockDataDir) if err != nil { log.Printf("admin reseed failed environment=%s user_id=%d email=%s err=%v", h.cfg.Environment, userID, triggeredBy, err) return respond.Error(c, fiber.StatusInternalServerError, "admin_reseed_failed", err.Error()) } log.Printf("admin reseed completed environment=%s user_id=%d email=%s users=%d assignments=%d student_answers=%d", h.cfg.Environment, userID, triggeredBy, summary.Users, summary.Assignments, summary.StudentAnswers) return c.JSON(reseedResponse{ OK: true, Environment: h.cfg.Environment, TriggeredBy: triggeredBy, TriggeredAt: startedAt, Summary: summary, }) }