첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
217
baron-sso/backend/internal/handler/worksmobile_handler.go
Normal file
217
baron-sso/backend/internal/handler/worksmobile_handler.go
Normal file
@@ -0,0 +1,217 @@
|
||||
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("<!doctype html><html><body>Worksmobile OAuth callback reachable</body></html>")
|
||||
}
|
||||
|
||||
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) DeleteOrgUnit(c *fiber.Ctx) error {
|
||||
orgUnitID := strings.TrimSpace(c.Params("orgUnitId"))
|
||||
job, err := h.Service.EnqueueOrgUnitDelete(c.Context(), strings.TrimSpace(c.Params("tenantId")), orgUnitID)
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "delete_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"))
|
||||
credentialRequest, err := parseWorksmobileCredentialRequest(c)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
job, err := h.Service.EnqueueUserSync(
|
||||
c.Context(),
|
||||
strings.TrimSpace(c.Params("tenantId")),
|
||||
userID,
|
||||
credentialRequest.CredentialBatchID,
|
||||
credentialRequest.InitialPassword,
|
||||
)
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "sync_user", "user_id", userID)
|
||||
}
|
||||
return c.Status(fiber.StatusAccepted).JSON(job)
|
||||
}
|
||||
|
||||
func (h *WorksmobileHandler) ResetUserPassword(c *fiber.Ctx) error {
|
||||
userID := strings.TrimSpace(c.Params("userId"))
|
||||
credentialBatchID, err := parseWorksmobileCredentialBatchID(c)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
job, err := h.Service.EnqueueUserPasswordReset(c.Context(), strings.TrimSpace(c.Params("tenantId")), userID, credentialBatchID)
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "reset_user_password", "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) DeletePendingJobs(c *fiber.Ctx) error {
|
||||
result, err := h.Service.DeletePendingJobs(c.Context(), strings.TrimSpace(c.Params("tenantId")))
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "delete_pending_jobs")
|
||||
}
|
||||
return c.JSON(result)
|
||||
}
|
||||
|
||||
func (h *WorksmobileHandler) DownloadInitialPasswordsCSV(c *fiber.Ctx) error {
|
||||
credentials, err := h.Service.ListInitialPasswordCredentials(c.Context(), strings.TrimSpace(c.Params("tenantId")), strings.TrimSpace(c.Query("batchId")))
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "download_initial_passwords")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := csv.NewWriter(&buf)
|
||||
if err := writer.Write([]string{"email", "name", "primaryLeafOrgName", "initialPassword", "status", "lastError"}); err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
for _, credential := range credentials {
|
||||
if err := writer.Write([]string{credential.Email, credential.Name, credential.PrimaryLeafOrgName, 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 (h *WorksmobileHandler) ListCredentialBatches(c *fiber.Ctx) error {
|
||||
batches, err := h.Service.ListCredentialBatches(c.Context(), strings.TrimSpace(c.Params("tenantId")))
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "list_credential_batches")
|
||||
}
|
||||
return c.JSON(batches)
|
||||
}
|
||||
|
||||
func (h *WorksmobileHandler) DeleteCredentialBatchPasswords(c *fiber.Ctx) error {
|
||||
batchID := strings.TrimSpace(c.Params("batchId"))
|
||||
batch, err := h.Service.DeleteCredentialBatchPasswords(c.Context(), strings.TrimSpace(c.Params("tenantId")), batchID)
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "delete_credential_batch_passwords", "batch_id", batchID)
|
||||
}
|
||||
return c.JSON(batch)
|
||||
}
|
||||
|
||||
type worksmobileCredentialBatchRequest struct {
|
||||
CredentialBatchID string `json:"credentialBatchId"`
|
||||
InitialPassword string `json:"initialPassword"`
|
||||
}
|
||||
|
||||
func parseWorksmobileCredentialBatchID(c *fiber.Ctx) (string, error) {
|
||||
req, err := parseWorksmobileCredentialRequest(c)
|
||||
return req.CredentialBatchID, err
|
||||
}
|
||||
|
||||
func parseWorksmobileCredentialRequest(c *fiber.Ctx) (worksmobileCredentialBatchRequest, error) {
|
||||
batchID := strings.TrimSpace(c.Query("credentialBatchId"))
|
||||
req := worksmobileCredentialBatchRequest{CredentialBatchID: batchID}
|
||||
if len(bytes.TrimSpace(c.Body())) == 0 {
|
||||
return req, nil
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return worksmobileCredentialBatchRequest{}, err
|
||||
}
|
||||
req.InitialPassword = strings.TrimSpace(req.InitialPassword)
|
||||
if bodyBatchID := strings.TrimSpace(req.CredentialBatchID); bodyBatchID != "" {
|
||||
req.CredentialBatchID = bodyBatchID
|
||||
return req, nil
|
||||
}
|
||||
req.CredentialBatchID = batchID
|
||||
return req, nil
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user