forked from baron/baron-sso
264 lines
7.6 KiB
Go
264 lines
7.6 KiB
Go
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
|
|
}
|