package api import ( "net/http" "strings" "github.com/ez-api/ez-api/internal/dto" "github.com/ez-api/ez-api/internal/model" "github.com/gin-gonic/gin" ) // CreateAPIKey godoc // @Summary Create an API key // @Description Create an API key for a provider group // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param key body dto.APIKeyDTO true "API key payload" // @Success 201 {object} model.APIKey // @Failure 400 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/api-keys [post] func (h *Handler) CreateAPIKey(c *gin.Context) { var req dto.APIKeyDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.GroupID == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "group_id required"}) return } var group model.ProviderGroup if err := h.db.First(&group, req.GroupID).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "provider group not found"}) return } apiKey := strings.TrimSpace(req.APIKey) status := strings.TrimSpace(req.Status) if status == "" { status = "active" } autoBan := true if req.AutoBan != nil { autoBan = *req.AutoBan } key := model.APIKey{ GroupID: req.GroupID, APIKey: apiKey, AccessToken: strings.TrimSpace(req.AccessToken), RefreshToken: strings.TrimSpace(req.RefreshToken), AccountID: strings.TrimSpace(req.AccountID), ProjectID: strings.TrimSpace(req.ProjectID), Weight: normalizeWeight(req.Weight), Status: status, AutoBan: autoBan, BanReason: strings.TrimSpace(req.BanReason), } if !req.ExpiresAt.IsZero() { tu := req.ExpiresAt.UTC() key.ExpiresAt = &tu } if !req.BanUntil.IsZero() { tu := req.BanUntil.UTC() key.BanUntil = &tu } if err := h.groupManager.ValidateAPIKey(group, key); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.db.Create(&key).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create api key", "details": err.Error()}) return } if err := h.sync.SyncProvidersForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()}) return } c.JSON(http.StatusCreated, key) } // ListAPIKeys godoc // @Summary List API keys // @Description List API keys // @Tags admin // @Produce json // @Security AdminAuth // @Param page query int false "page (1-based)" // @Param limit query int false "limit (default 50, max 200)" // @Param group_id query int false "filter by group_id" // @Success 200 {array} model.APIKey // @Failure 500 {object} gin.H // @Router /admin/api-keys [get] func (h *Handler) ListAPIKeys(c *gin.Context) { var keys []model.APIKey q := h.db.Model(&model.APIKey{}).Order("id desc") if groupID := strings.TrimSpace(c.Query("group_id")); groupID != "" { q = q.Where("group_id = ?", groupID) } query := parseListQuery(c) q = applyListPagination(q, query) if err := q.Find(&keys).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list api keys", "details": err.Error()}) return } c.JSON(http.StatusOK, keys) } // GetAPIKey godoc // @Summary Get API key // @Description Get an API key by id // @Tags admin // @Produce json // @Security AdminAuth // @Param id path int true "APIKey ID" // @Success 200 {object} model.APIKey // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/api-keys/{id} [get] func (h *Handler) GetAPIKey(c *gin.Context) { id, ok := parseUintParam(c, "id") if !ok { return } var key model.APIKey if err := h.db.First(&key, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "api key not found"}) return } c.JSON(http.StatusOK, key) } // UpdateAPIKey godoc // @Summary Update API key // @Description Update an API key // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param id path int true "APIKey ID" // @Param key body dto.APIKeyDTO true "API key payload" // @Success 200 {object} model.APIKey // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/api-keys/{id} [put] func (h *Handler) UpdateAPIKey(c *gin.Context) { id, ok := parseUintParam(c, "id") if !ok { return } var key model.APIKey if err := h.db.First(&key, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "api key not found"}) return } var req dto.APIKeyDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } update := map[string]any{} groupID := key.GroupID if req.GroupID != 0 { groupID = req.GroupID } var group model.ProviderGroup if err := h.db.First(&group, groupID).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "provider group not found"}) return } if req.GroupID != 0 { update["group_id"] = req.GroupID } if strings.TrimSpace(req.APIKey) != "" { update["api_key"] = strings.TrimSpace(req.APIKey) } if strings.TrimSpace(req.AccessToken) != "" { update["access_token"] = strings.TrimSpace(req.AccessToken) } if strings.TrimSpace(req.RefreshToken) != "" { update["refresh_token"] = strings.TrimSpace(req.RefreshToken) } if !req.ExpiresAt.IsZero() { tu := req.ExpiresAt.UTC() update["expires_at"] = &tu } if strings.TrimSpace(req.AccountID) != "" { update["account_id"] = strings.TrimSpace(req.AccountID) } if strings.TrimSpace(req.ProjectID) != "" { update["project_id"] = strings.TrimSpace(req.ProjectID) } if req.Weight > 0 { update["weight"] = normalizeWeight(req.Weight) } if strings.TrimSpace(req.Status) != "" { update["status"] = strings.TrimSpace(req.Status) } if req.AutoBan != nil { update["auto_ban"] = *req.AutoBan } if req.BanReason != "" || strings.TrimSpace(req.Status) == "active" { update["ban_reason"] = strings.TrimSpace(req.BanReason) } if !req.BanUntil.IsZero() { tu := req.BanUntil.UTC() update["ban_until"] = &tu } if req.BanUntil.IsZero() && strings.TrimSpace(req.Status) == "active" { update["ban_until"] = nil } if req.GroupID != 0 || strings.TrimSpace(req.APIKey) != "" || strings.TrimSpace(req.AccessToken) != "" || strings.TrimSpace(req.RefreshToken) != "" { nextKey := key if v, ok := update["api_key"].(string); ok { nextKey.APIKey = v } if v, ok := update["access_token"].(string); ok { nextKey.AccessToken = v } if v, ok := update["refresh_token"].(string); ok { nextKey.RefreshToken = v } if req.GroupID != 0 { nextKey.GroupID = req.GroupID } if err := h.groupManager.ValidateAPIKey(group, nextKey); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } } if err := h.db.Model(&key).Updates(update).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update api key", "details": err.Error()}) return } if err := h.db.First(&key, id).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload api key", "details": err.Error()}) return } if err := h.sync.SyncProvidersForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()}) return } c.JSON(http.StatusOK, key) } // DeleteAPIKey godoc // @Summary Delete API key // @Description Delete an API key // @Tags admin // @Produce json // @Security AdminAuth // @Param id path int true "APIKey ID" // @Success 200 {object} gin.H // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/api-keys/{id} [delete] func (h *Handler) DeleteAPIKey(c *gin.Context) { id, ok := parseUintParam(c, "id") if !ok { return } var key model.APIKey if err := h.db.First(&key, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "api key not found"}) return } if err := h.db.Delete(&key).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete api key", "details": err.Error()}) return } if err := h.sync.SyncProvidersForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForAPIKey(h.db, key.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "deleted"}) }