package api import ( "errors" "net/http" "strconv" "strings" "github.com/ez-api/ez-api/internal/service" "github.com/gin-gonic/gin" ) type AdminHandler struct { masterService *service.MasterService syncService *service.SyncService } func NewAdminHandler(masterService *service.MasterService, syncService *service.SyncService) *AdminHandler { return &AdminHandler{masterService: masterService, syncService: syncService} } type CreateMasterRequest struct { Name string `json:"name" binding:"required"` Group string `json:"group" binding:"required"` MaxChildKeys int `json:"max_child_keys"` GlobalQPS int `json:"global_qps"` } // CreateMaster godoc // @Summary Create a new master tenant // @Description Create a new master account (tenant) // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param master body CreateMasterRequest true "Master Info" // @Success 201 {object} gin.H // @Failure 400 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/masters [post] func (h *AdminHandler) CreateMaster(c *gin.Context) { var req CreateMasterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Use defaults if not provided if req.MaxChildKeys == 0 { req.MaxChildKeys = 5 } if req.GlobalQPS == 0 { req.GlobalQPS = 3 } master, rawMasterKey, err := h.masterService.CreateMaster(req.Name, req.Group, req.MaxChildKeys, req.GlobalQPS) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create master key", "details": err.Error()}) return } if err := h.syncService.SyncMaster(master); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync master key", "details": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "id": master.ID, "name": master.Name, "group": master.Group, "master_key": rawMasterKey, // Only show this on creation "max_child_keys": master.MaxChildKeys, "global_qps": master.GlobalQPS, }) } // IssueChildKeyForMaster godoc // @Summary Issue a child key on behalf of a master // @Description Issue a new access token (child key) for a specified master. The key still belongs to the master; issuer is recorded as admin for audit. // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param id path int true "Master ID" // @Param request body IssueChildKeyRequest true "Key Request" // @Success 201 {object} gin.H // @Failure 400 {object} gin.H // @Failure 403 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/masters/{id}/keys [post] func (h *AdminHandler) IssueChildKeyForMaster(c *gin.Context) { idRaw := strings.TrimSpace(c.Param("id")) if idRaw == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "master id required"}) return } idU64, err := strconv.ParseUint(idRaw, 10, 64) if err != nil || idU64 == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"}) return } masterID := uint(idU64) var req IssueChildKeyRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } key, rawChildKey, err := h.masterService.IssueChildKeyAsAdmin(masterID, req.Group, req.Scopes) if err != nil { switch { case errors.Is(err, service.ErrMasterNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrMasterNotActive): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrChildKeyGroupForbidden): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrChildKeyLimitReached): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to issue child key", "details": err.Error()}) } return } if err := h.syncService.SyncKey(key); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync child key", "details": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "id": key.ID, "key_secret": rawChildKey, "group": key.Group, "scopes": key.Scopes, "issued_by": key.IssuedBy, }) }