package handler import ( "baron-sso-backend/internal/service" "bytes" "context" "encoding/csv" "errors" "log/slog" "strings" "github.com/gofiber/fiber/v2" ) type WorksmobileHandler struct { Service service.WorksmobileAdminService } func NewWorksmobileHandler(svc service.WorksmobileAdminService) *WorksmobileHandler { return &WorksmobileHandler{Service: svc} } func (h *WorksmobileHandler) GetOverview(c *fiber.Ctx) error { overview, err := h.Service.GetTenantOverview(c.Context(), strings.TrimSpace(c.Params("tenantId"))) if err != nil { return worksmobileGuardError(c, err, "get_overview") } if !worksmobileOverviewAllowed(overview) { return errorJSON(c, fiber.StatusNotFound, "worksmobile is only available for hanmac-family root tenant") } return c.JSON(overview) } func (h *WorksmobileHandler) GetComparison(c *fiber.Ctx) error { includeMatched := strings.EqualFold(strings.TrimSpace(c.Query("includeMatched")), "true") comparison, err := h.Service.GetComparison(c.Context(), strings.TrimSpace(c.Params("tenantId")), includeMatched) if err != nil { return worksmobileGuardError(c, err, "get_comparison") } return c.JSON(comparison) } func (h *WorksmobileHandler) OAuthCallback(c *fiber.Ctx) error { return c.Type("html").SendString("Worksmobile OAuth callback reachable") } func (h *WorksmobileHandler) BackfillDryRun(c *fiber.Ctx) error { result, err := h.Service.EnqueueBackfillDryRun(c.Context(), strings.TrimSpace(c.Params("tenantId"))) if err != nil { return worksmobileGuardError(c, err, "backfill_dry_run") } return c.JSON(result) } func (h *WorksmobileHandler) SyncOrgUnit(c *fiber.Ctx) error { orgUnitID := strings.TrimSpace(c.Params("orgUnitId")) job, err := h.Service.EnqueueOrgUnitSync(c.Context(), strings.TrimSpace(c.Params("tenantId")), orgUnitID) if err != nil { return worksmobileGuardError(c, err, "sync_orgunit", "org_unit_id", orgUnitID) } return c.Status(fiber.StatusAccepted).JSON(job) } func (h *WorksmobileHandler) SyncUser(c *fiber.Ctx) error { userID := strings.TrimSpace(c.Params("userId")) job, err := h.Service.EnqueueUserSync(c.Context(), strings.TrimSpace(c.Params("tenantId")), userID) if err != nil { return worksmobileGuardError(c, err, "sync_user", "user_id", userID) } return c.Status(fiber.StatusAccepted).JSON(job) } func (h *WorksmobileHandler) RetryJob(c *fiber.Ctx) error { jobID := strings.TrimSpace(c.Params("jobId")) job, err := h.Service.RetryJob(c.Context(), strings.TrimSpace(c.Params("tenantId")), jobID) if err != nil { return worksmobileGuardError(c, err, "retry_job", "job_id", jobID) } return c.JSON(job) } func (h *WorksmobileHandler) DownloadInitialPasswordsCSV(c *fiber.Ctx) error { credentials, err := h.Service.ListInitialPasswordCredentials(c.Context(), strings.TrimSpace(c.Params("tenantId"))) if err != nil { return worksmobileGuardError(c, err, "download_initial_passwords") } var buf bytes.Buffer writer := csv.NewWriter(&buf) if err := writer.Write([]string{"email", "initialPassword", "status", "lastError"}); err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } for _, credential := range credentials { if err := writer.Write([]string{credential.Email, credential.InitialPassword, credential.Status, credential.LastError}); err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } } writer.Flush() if err := writer.Error(); err != nil { return errorJSON(c, fiber.StatusInternalServerError, err.Error()) } c.Set(fiber.HeaderContentType, "text/csv; charset=utf-8") c.Set(fiber.HeaderContentDisposition, `attachment; filename="worksmobile_initial_passwords.csv"`) return c.Send(buf.Bytes()) } func worksmobileOverviewAllowed(overview service.WorksmobileTenantOverview) bool { return overview.Tenant.Slug == service.HanmacFamilyTenantSlug && overview.Tenant.ParentID == nil } func worksmobileGuardError(c *fiber.Ctx, err error, operation string, attrs ...any) error { if err == nil { return nil } logAttrs := []any{ "operation", operation, "tenant_id", strings.TrimSpace(c.Params("tenantId")), "path", c.Path(), "error", err, } logAttrs = append(logAttrs, attrs...) if errors.Is(err, context.Canceled) { slog.Warn("worksmobile admin operation failed", logAttrs...) return errorJSON(c, fiber.StatusRequestTimeout, err.Error()) } slog.Error("worksmobile admin operation failed", logAttrs...) if strings.Contains(err.Error(), "hanmac-family root") { return errorJSON(c, fiber.StatusNotFound, err.Error()) } return errorJSON(c, fiber.StatusInternalServerError, err.Error()) }