forked from baron/baron-sso
backend swagger/redoc 추가
This commit is contained in:
20
backend/internal/handler/admin_auth.go
Normal file
20
backend/internal/handler/admin_auth.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func requireAdmin(c *fiber.Ctx) error {
|
||||
adminPass := os.Getenv("ADMIN_PASSWORD")
|
||||
if adminPass == "" {
|
||||
adminPass = "admin"
|
||||
}
|
||||
|
||||
reqPass := c.Get("X-Admin-Password")
|
||||
if reqPass != adminPass {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid Admin Password"})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (h *AdminHandler) checkAuth(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {
|
||||
if err := h.checkAuth(c); err != nil {
|
||||
if err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type DevHandler struct {
|
||||
@@ -37,8 +38,8 @@ type clientListResponse struct {
|
||||
}
|
||||
|
||||
type clientDetailResponse struct {
|
||||
Client clientSummary `json:"client"`
|
||||
Endpoints clientEndpoints `json:"endpoints"`
|
||||
Client clientSummary `json:"client"`
|
||||
Endpoints clientEndpoints `json:"endpoints"`
|
||||
}
|
||||
|
||||
type clientEndpoints struct {
|
||||
@@ -61,6 +62,19 @@ type consentListResponse struct {
|
||||
Items []consentSummary `json:"items"`
|
||||
}
|
||||
|
||||
type clientUpsertRequest struct {
|
||||
ID *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Type *string `json:"type"`
|
||||
Status *string `json:"status"`
|
||||
RedirectURIs *[]string `json:"redirectUris"`
|
||||
Scopes *[]string `json:"scopes"`
|
||||
GrantTypes *[]string `json:"grantTypes"`
|
||||
ResponseTypes *[]string `json:"responseTypes"`
|
||||
TokenEndpointAuthMethod *string `json:"tokenEndpointAuthMethod"`
|
||||
Metadata *map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
func (h *DevHandler) ListClients(c *fiber.Ctx) error {
|
||||
limit := c.QueryInt("limit", 50)
|
||||
offset := c.QueryInt("offset", 0)
|
||||
@@ -163,6 +177,189 @@ func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
|
||||
var req clientUpsertRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
clientID := strings.TrimSpace(valueOr(req.ID, ""))
|
||||
if clientID == "" {
|
||||
clientID = uuid.NewString()
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(valueOr(req.Name, ""))
|
||||
if name == "" {
|
||||
name = clientID
|
||||
}
|
||||
|
||||
redirectURIs := derefSlice(req.RedirectURIs, nil)
|
||||
if len(redirectURIs) == 0 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris is required"})
|
||||
}
|
||||
|
||||
scopes := derefSlice(req.Scopes, defaultClientScopes())
|
||||
grantTypes := derefSlice(req.GrantTypes, defaultGrantTypes())
|
||||
responseTypes := derefSlice(req.ResponseTypes, defaultResponseTypes())
|
||||
|
||||
clientType := strings.ToLower(strings.TrimSpace(valueOr(req.Type, "confidential")))
|
||||
if clientType != "public" && clientType != "confidential" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be public or confidential"})
|
||||
}
|
||||
|
||||
status := strings.ToLower(strings.TrimSpace(valueOr(req.Status, "active")))
|
||||
if status != "active" && status != "inactive" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
|
||||
}
|
||||
|
||||
metadata := mergeMetadata(nil, req.Metadata)
|
||||
if metadata == nil {
|
||||
metadata = map[string]interface{}{}
|
||||
}
|
||||
metadata["status"] = status
|
||||
|
||||
tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
|
||||
if tokenAuthMethod == "" {
|
||||
if clientType == "public" {
|
||||
tokenAuthMethod = "none"
|
||||
} else {
|
||||
tokenAuthMethod = "client_secret_basic"
|
||||
}
|
||||
}
|
||||
|
||||
client := service.HydraClient{
|
||||
ClientID: clientID,
|
||||
ClientName: name,
|
||||
RedirectURIs: redirectURIs,
|
||||
GrantTypes: grantTypes,
|
||||
ResponseTypes: responseTypes,
|
||||
Scope: strings.Join(scopes, " "),
|
||||
TokenEndpointAuthMethod: tokenAuthMethod,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
created, err := h.Hydra.CreateClient(c.Context(), client)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
summary := mapClientSummary(*created)
|
||||
return c.Status(fiber.StatusCreated).JSON(clientDetailResponse{
|
||||
Client: summary,
|
||||
Endpoints: clientEndpoints{
|
||||
Discovery: strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
|
||||
Issuer: h.Hydra.PublicURL,
|
||||
Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
|
||||
Token: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
|
||||
UserInfo: strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
|
||||
clientID := strings.TrimSpace(c.Params("id"))
|
||||
if clientID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
|
||||
}
|
||||
|
||||
var req clientUpsertRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
current, err := h.Hydra.GetClient(c.Context(), clientID)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrHydraNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
|
||||
}
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
clientType := ""
|
||||
if req.Type != nil {
|
||||
clientType = strings.ToLower(strings.TrimSpace(*req.Type))
|
||||
if clientType != "public" && clientType != "confidential" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be public or confidential"})
|
||||
}
|
||||
}
|
||||
|
||||
status := ""
|
||||
if req.Status != nil {
|
||||
status = strings.ToLower(strings.TrimSpace(*req.Status))
|
||||
if status != "active" && status != "inactive" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
|
||||
}
|
||||
}
|
||||
|
||||
tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
|
||||
if tokenAuthMethod == "" && clientType != "" {
|
||||
if clientType == "public" {
|
||||
tokenAuthMethod = "none"
|
||||
} else {
|
||||
tokenAuthMethod = "client_secret_basic"
|
||||
}
|
||||
}
|
||||
|
||||
if req.RedirectURIs != nil && len(*req.RedirectURIs) == 0 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris cannot be empty"})
|
||||
}
|
||||
|
||||
metadata := mergeMetadata(current.Metadata, req.Metadata)
|
||||
if status != "" {
|
||||
if metadata == nil {
|
||||
metadata = map[string]interface{}{}
|
||||
}
|
||||
metadata["status"] = status
|
||||
}
|
||||
|
||||
updated := service.HydraClient{
|
||||
ClientID: current.ClientID,
|
||||
ClientName: valueOr(req.Name, current.ClientName),
|
||||
RedirectURIs: derefSlice(req.RedirectURIs, current.RedirectURIs),
|
||||
GrantTypes: derefSlice(req.GrantTypes, current.GrantTypes),
|
||||
ResponseTypes: derefSlice(req.ResponseTypes, current.ResponseTypes),
|
||||
Scope: buildScope(valueOrSlice(req.Scopes, strings.Fields(current.Scope))),
|
||||
TokenEndpointAuthMethod: resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod),
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
updatedClient, err := h.Hydra.UpdateClient(c.Context(), clientID, updated)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrHydraNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
|
||||
}
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
summary := mapClientSummary(*updatedClient)
|
||||
return c.JSON(clientDetailResponse{
|
||||
Client: summary,
|
||||
Endpoints: clientEndpoints{
|
||||
Discovery: strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
|
||||
Issuer: h.Hydra.PublicURL,
|
||||
Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
|
||||
Token: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
|
||||
UserInfo: strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
|
||||
clientID := strings.TrimSpace(c.Params("id"))
|
||||
if clientID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
|
||||
}
|
||||
|
||||
if err := h.Hydra.DeleteClient(c.Context(), clientID); err != nil {
|
||||
if errors.Is(err, service.ErrHydraNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
|
||||
}
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
|
||||
subject := strings.TrimSpace(c.Query("subject"))
|
||||
if subject == "" {
|
||||
@@ -239,3 +436,61 @@ func mapClientSummary(client service.HydraClient) clientSummary {
|
||||
Metadata: client.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultClientScopes() []string {
|
||||
return []string{"openid", "profile", "email"}
|
||||
}
|
||||
|
||||
func defaultGrantTypes() []string {
|
||||
return []string{"authorization_code", "refresh_token"}
|
||||
}
|
||||
|
||||
func defaultResponseTypes() []string {
|
||||
return []string{"code"}
|
||||
}
|
||||
|
||||
func buildScope(scopes []string) string {
|
||||
return strings.Join(scopes, " ")
|
||||
}
|
||||
|
||||
func valueOr(ptr *string, fallback string) string {
|
||||
if ptr == nil {
|
||||
return fallback
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
func valueOrSlice(ptr *[]string, fallback []string) []string {
|
||||
if ptr == nil {
|
||||
return fallback
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
func derefSlice(ptr *[]string, fallback []string) []string {
|
||||
if ptr == nil {
|
||||
return fallback
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
func mergeMetadata(current map[string]interface{}, incoming *map[string]interface{}) map[string]interface{} {
|
||||
if incoming == nil {
|
||||
return current
|
||||
}
|
||||
merged := map[string]interface{}{}
|
||||
for k, v := range current {
|
||||
merged[k] = v
|
||||
}
|
||||
for k, v := range *incoming {
|
||||
merged[k] = v
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func resolveTokenAuthMethod(requested, fallback string) string {
|
||||
if strings.TrimSpace(requested) == "" {
|
||||
return fallback
|
||||
}
|
||||
return requested
|
||||
}
|
||||
|
||||
278
backend/internal/handler/tenant_handler.go
Normal file
278
backend/internal/handler/tenant_handler.go
Normal file
@@ -0,0 +1,278 @@
|
||||
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 err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
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 err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
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 err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
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 err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
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 err := requireAdmin(c); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user