package handler import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/repository" "baron-sso-backend/internal/service" "context" "runtime" "strconv" "strings" "time" "github.com/gofiber/fiber/v2" ) type adminHydraClientLister interface { ListClients(ctx context.Context, limit, offset int) ([]domain.HydraClient, error) } type AdminHandler struct { Keto service.KetoService KetoOutbox repository.KetoOutboxRepository RPUsageQueries domain.RPUsageQueryRepository TenantRepo repository.TenantRepository Hydra adminHydraClientLister AuditRepo domain.AuditRepository UserProjectionRepo repository.UserProjectionRepository UserProjectionSyncer service.UserProjectionReconciler } func NewAdminHandler(keto service.KetoService, ketoOutbox repository.KetoOutboxRepository) *AdminHandler { return &AdminHandler{ Keto: keto, KetoOutbox: ketoOutbox, } } func (h *AdminHandler) GetRPUsageDaily(c *fiber.Ctx) error { if h == nil || h.RPUsageQueries == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{ "error": "rp usage query service unavailable", }) } days := 14 if raw := c.Query("days"); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil { days = parsed } } period := normalizeRPUsagePeriod(c.Query("period")) tenantID, allowed := h.authorizedRPUsageTenantID(c, strings.TrimSpace(c.Query("tenantId"))) if !allowed { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ "error": "forbidden: tenant rp usage stats permission denied", }) } items, err := h.RPUsageQueries.FindRPUsage(c.Context(), domain.RPUsageQuery{ Days: days, Period: period, TenantID: tenantID, }) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": err.Error(), }) } return c.JSON(fiber.Map{ "items": items, "days": days, "period": period, "tenantId": tenantID, }) } func normalizeRPUsagePeriod(period string) string { switch strings.ToLower(strings.TrimSpace(period)) { case "week": return "week" case "month": return "month" default: return "day" } } func (h *AdminHandler) authorizedRPUsageTenantID(c *fiber.Ctx, requestedTenantID string) (string, bool) { profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) if profile != nil && domain.NormalizeRole(profile.Role) == domain.RoleSuperAdmin { return requestedTenantID, true } tenantID := requestedTenantID if tenantID == "" && profile != nil && profile.TenantID != nil { tenantID = strings.TrimSpace(*profile.TenantID) } if tenantID == "" { return "", false } if h == nil || h.Keto == nil || profile == nil || strings.TrimSpace(profile.ID) == "" { return "", false } allowed, err := h.Keto.CheckPermission(c.Context(), "User:"+profile.ID, "Tenant", tenantID, "view_rp_usage_stats") if err != nil || !allowed { return "", false } return tenantID, true } func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"}) } func requireSuperAdminProfile(c *fiber.Ctx) error { profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) if profile == nil || domain.NormalizeRole(profile.Role) != domain.RoleSuperAdmin { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden: super_admin required"}) } return nil } func (h *AdminHandler) GetUserProjectionStatus(c *fiber.Ctx) error { if err := requireSuperAdminProfile(c); err != nil { return err } if h == nil || h.UserProjectionRepo == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection service unavailable"}) } status, err := h.UserProjectionRepo.GetStatus(c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(status) } func (h *AdminHandler) ReconcileUserProjection(c *fiber.Ctx) error { if err := requireSuperAdminProfile(c); err != nil { return err } if h == nil || h.UserProjectionSyncer == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "user projection sync service unavailable"}) } count, err := h.UserProjectionSyncer.Reconcile(c.Context()) if err != nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(fiber.Map{ "status": "success", "syncedUsers": count, "updatedAt": time.Now().UTC().Format(time.RFC3339), }) } func (h *AdminHandler) ResetUserProjection(c *fiber.Ctx) error { return h.ReconcileUserProjection(c) } // GetSystemStats returns runtime statistics for monitoring func (h *AdminHandler) GetSystemStats(c *fiber.Ctx) error { var m runtime.MemStats runtime.ReadMemStats(&m) ctx := c.Context() stats := fiber.Map{ "totalTenants": h.countTenants(ctx), "oidcClients": h.countOIDCClients(ctx), "auditEvents24h": h.countAuditEventsSince(ctx, time.Now().UTC().Add(-24*time.Hour)), "goroutines": runtime.NumGoroutine(), "cpus": runtime.NumCPU(), "memory": fiber.Map{ "alloc": m.Alloc, "totalAlign": m.TotalAlloc, "sys": m.Sys, "numGC": m.NumGC, }, "timestamp": time.Now(), } return c.Status(fiber.StatusOK).JSON(stats) } func (h *AdminHandler) countTenants(ctx context.Context) int64 { if h == nil || h.TenantRepo == nil { return 0 } _, total, err := h.TenantRepo.List(ctx, 1, 0, "") if err != nil { return 0 } return total } func (h *AdminHandler) countOIDCClients(ctx context.Context) int64 { if h == nil || h.Hydra == nil { return 0 } const pageSize = 500 var total int64 for offset := 0; ; offset += pageSize { clients, err := h.Hydra.ListClients(ctx, pageSize, offset) if err != nil { return total } for _, client := range clients { if isHiddenSystemClient(client) { continue } total++ } if len(clients) < pageSize { break } } return total } func (h *AdminHandler) countAuditEventsSince(ctx context.Context, since time.Time) int64 { if h == nil || h.AuditRepo == nil { return 0 } count, err := h.AuditRepo.CountEventsSince(ctx, since) if err == nil && count > 0 { return count } logs, pageErr := h.AuditRepo.FindPage(ctx, 10000, nil, "") if pageErr != nil { return count } var fallbackCount int64 for _, log := range logs { if !log.Timestamp.Before(since) { fallbackCount++ } } return fallbackCount }