package handler import ( "baron-sso-backend/internal/domain" "errors" "strings" "time" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type TenantHandler struct { DB *gorm.DB } func NewTenantHandler(db *gorm.DB) *TenantHandler { return &TenantHandler{DB: db} } type tenantSummary struct { ID string `json:"id"` Name string `json:"name"` Slug string `json:"slug"` Description string `json:"description"` Status string `json:"status"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type tenantListResponse struct { Items []tenantSummary `json:"items"` Limit int `json:"limit"` Offset int `json:"offset"` Total int64 `json:"total"` } func (h *TenantHandler) ListTenants(c *fiber.Ctx) error { if h.DB == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "database not available"}) } limit := c.QueryInt("limit", 50) offset := c.QueryInt("offset", 0) if limit <= 0 { limit = 50 } if offset < 0 { offset = 0 } var total int64 if err := h.DB.Model(&domain.Tenant{}).Count(&total).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } var tenants []domain.Tenant if err := h.DB.Order("created_at desc").Limit(limit).Offset(offset).Find(&tenants).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } items := make([]tenantSummary, 0, len(tenants)) for _, t := range tenants { items = append(items, mapTenantSummary(t)) } return c.JSON(tenantListResponse{Items: items, Limit: limit, Offset: offset, Total: total}) } func (h *TenantHandler) GetTenant(c *fiber.Ctx) error { if h.DB == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "database not available"}) } tenantID := strings.TrimSpace(c.Params("id")) if tenantID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant id is required"}) } var tenant domain.Tenant if err := h.DB.First(&tenant, "id = ?", tenantID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "tenant not found"}) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(mapTenantSummary(tenant)) } func (h *TenantHandler) CreateTenant(c *fiber.Ctx) error { if h.DB == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "database not available"}) } var req struct { Name string `json:"name"` Slug string `json:"slug"` Description string `json:"description"` Status string `json:"status"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"}) } name := strings.TrimSpace(req.Name) if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name is required"}) } slug := normalizeTenantSlug(req.Slug) if slug == "" { slug = normalizeTenantSlug(name) } if slug == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "slug is required"}) } status := normalizeTenantStatus(req.Status) if status == "" { status = "active" } var exists domain.Tenant if err := h.DB.Unscoped().Where("slug = ?", slug).First(&exists).Error; err == nil { return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "slug already exists"}) } else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } tenant := domain.Tenant{ Name: name, Slug: slug, Description: strings.TrimSpace(req.Description), Status: status, } if err := h.DB.Create(&tenant).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.Status(fiber.StatusCreated).JSON(mapTenantSummary(tenant)) } func (h *TenantHandler) UpdateTenant(c *fiber.Ctx) error { if h.DB == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "database not available"}) } tenantID := strings.TrimSpace(c.Params("id")) if tenantID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant id is required"}) } var tenant domain.Tenant if err := h.DB.First(&tenant, "id = ?", tenantID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "tenant not found"}) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } var req struct { Name *string `json:"name"` Slug *string `json:"slug"` Description *string `json:"description"` Status *string `json:"status"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"}) } if req.Name != nil { name := strings.TrimSpace(*req.Name) if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "name cannot be empty"}) } tenant.Name = name } if req.Slug != nil { slug := normalizeTenantSlug(*req.Slug) if slug == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "slug cannot be empty"}) } if slug != tenant.Slug { var exists domain.Tenant if err := h.DB.Unscoped().Where("slug = ?", slug).First(&exists).Error; err == nil { return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "slug already exists"}) } else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } tenant.Slug = slug } } if req.Description != nil { tenant.Description = strings.TrimSpace(*req.Description) } if req.Status != nil { status := normalizeTenantStatus(*req.Status) if status == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"}) } tenant.Status = status } if err := h.DB.Save(&tenant).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.JSON(mapTenantSummary(tenant)) } func (h *TenantHandler) DeleteTenant(c *fiber.Ctx) error { if h.DB == nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "database not available"}) } tenantID := strings.TrimSpace(c.Params("id")) if tenantID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "tenant id is required"}) } if err := h.DB.Delete(&domain.Tenant{}, "id = ?", tenantID).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } return c.SendStatus(fiber.StatusNoContent) } func mapTenantSummary(t domain.Tenant) tenantSummary { return tenantSummary{ ID: t.ID, Name: t.Name, Slug: t.Slug, Description: t.Description, Status: t.Status, CreatedAt: t.CreatedAt.Format(time.RFC3339), UpdatedAt: t.UpdatedAt.Format(time.RFC3339), } } func normalizeTenantSlug(value string) string { value = strings.ToLower(strings.TrimSpace(value)) value = strings.ReplaceAll(value, " ", "-") var b strings.Builder for _, r := range value { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' { b.WriteRune(r) } } return strings.Trim(b.String(), "-") } func normalizeTenantStatus(value string) string { value = strings.ToLower(strings.TrimSpace(value)) if value == "" { return "" } if value != "active" && value != "inactive" { return "" } return value }