Files
ez-api/internal/api/provider_group_handler.go
zenfun 2098bc4abe refactor(api): standardize DTOs and update swagger
Decouple API contract from internal models by introducing dedicated DTOs for requests and responses.
- Add Response DTOs for all resources (API Keys, Bindings, Models, Namespaces, etc.)
- Update Swagger annotations to use DTOs with field examples instead of internal models
- Refactor handlers to bind and return DTO structures
- Consolidate request/response definitions in the dto package
2026-01-10 02:05:55 +08:00

264 lines
9.0 KiB
Go

package api
import (
"net/http"
"strconv"
"strings"
"github.com/ez-api/ez-api/internal/dto"
"github.com/ez-api/ez-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// CreateProviderGroup godoc
// @Summary Create a provider group
// @Description Create a provider group definition
// @Tags admin
// @Accept json
// @Produce json
// @Security AdminAuth
// @Param group body dto.ProviderGroupDTO true "Provider group payload"
// @Success 201 {object} ResponseEnvelope{data=dto.ProviderGroupResponse}
// @Failure 400 {object} ResponseEnvelope{data=MapData}
// @Failure 500 {object} ResponseEnvelope{data=MapData}
// @Router /admin/provider-groups [post]
func (h *Handler) CreateProviderGroup(c *gin.Context) {
var req dto.ProviderGroupDTO
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
name := strings.TrimSpace(req.Name)
if name == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "name required"})
return
}
group := model.ProviderGroup{
Name: name,
Type: strings.TrimSpace(req.Type),
BaseURL: strings.TrimSpace(req.BaseURL),
GoogleProject: strings.TrimSpace(req.GoogleProject),
GoogleLocation: strings.TrimSpace(req.GoogleLocation),
StaticHeaders: strings.TrimSpace(req.StaticHeaders),
HeadersProfile: strings.TrimSpace(req.HeadersProfile),
Models: strings.Join(req.Models, ","),
Status: strings.TrimSpace(req.Status),
}
normalized, err := h.groupManager.NormalizeGroup(group)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.db.Create(&normalized).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider group", "details": err.Error()})
return
}
if err := h.sync.SyncProvidersForGroup(h.db, normalized.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()})
return
}
if err := h.sync.SyncBindingsForGroup(h.db, normalized.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
return
}
c.JSON(http.StatusCreated, normalized)
}
// ListProviderGroups godoc
// @Summary List provider groups
// @Description List all provider groups
// @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 search query string false "search by name/type"
// @Success 200 {object} ResponseEnvelope{data=[]dto.ProviderGroupResponse}
// @Failure 500 {object} ResponseEnvelope{data=MapData}
// @Router /admin/provider-groups [get]
func (h *Handler) ListProviderGroups(c *gin.Context) {
var groups []model.ProviderGroup
q := h.db.Model(&model.ProviderGroup{}).Order("id desc")
query := parseListQuery(c)
q = applyListSearch(q, query.Search, "name", "type")
q = applyListPagination(q, query)
if err := q.Find(&groups).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list provider groups", "details": err.Error()})
return
}
c.JSON(http.StatusOK, groups)
}
// GetProviderGroup godoc
// @Summary Get provider group
// @Description Get a provider group by id
// @Tags admin
// @Produce json
// @Security AdminAuth
// @Param id path int true "ProviderGroup ID"
// @Success 200 {object} ResponseEnvelope{data=dto.ProviderGroupResponse}
// @Failure 400 {object} ResponseEnvelope{data=MapData}
// @Failure 404 {object} ResponseEnvelope{data=MapData}
// @Failure 500 {object} ResponseEnvelope{data=MapData}
// @Router /admin/provider-groups/{id} [get]
func (h *Handler) GetProviderGroup(c *gin.Context) {
id, ok := parseUintParam(c, "id")
if !ok {
return
}
var group model.ProviderGroup
if err := h.db.First(&group, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"})
return
}
c.JSON(http.StatusOK, group)
}
// UpdateProviderGroup godoc
// @Summary Update provider group
// @Description Update a provider group
// @Tags admin
// @Accept json
// @Produce json
// @Security AdminAuth
// @Param id path int true "ProviderGroup ID"
// @Param group body dto.ProviderGroupDTO true "Provider group payload"
// @Success 200 {object} ResponseEnvelope{data=dto.ProviderGroupResponse}
// @Failure 400 {object} ResponseEnvelope{data=MapData}
// @Failure 404 {object} ResponseEnvelope{data=MapData}
// @Failure 500 {object} ResponseEnvelope{data=MapData}
// @Router /admin/provider-groups/{id} [put]
func (h *Handler) UpdateProviderGroup(c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var group model.ProviderGroup
if err := h.db.First(&group, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"})
return
}
var req dto.ProviderGroupDTO
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
next := group
if strings.TrimSpace(req.Name) != "" {
next.Name = strings.TrimSpace(req.Name)
}
if strings.TrimSpace(req.Type) != "" {
next.Type = strings.TrimSpace(req.Type)
}
if strings.TrimSpace(req.BaseURL) != "" {
next.BaseURL = strings.TrimSpace(req.BaseURL)
}
if strings.TrimSpace(req.GoogleProject) != "" {
next.GoogleProject = strings.TrimSpace(req.GoogleProject)
}
if strings.TrimSpace(req.GoogleLocation) != "" {
next.GoogleLocation = strings.TrimSpace(req.GoogleLocation)
}
if strings.TrimSpace(req.StaticHeaders) != "" {
next.StaticHeaders = strings.TrimSpace(req.StaticHeaders)
}
if strings.TrimSpace(req.HeadersProfile) != "" {
next.HeadersProfile = strings.TrimSpace(req.HeadersProfile)
}
if req.Models != nil {
next.Models = strings.Join(req.Models, ",")
}
if strings.TrimSpace(req.Status) != "" {
next.Status = strings.TrimSpace(req.Status)
}
normalized, err := h.groupManager.NormalizeGroup(next)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
group.Name = normalized.Name
group.Type = normalized.Type
group.BaseURL = normalized.BaseURL
group.GoogleProject = normalized.GoogleProject
group.GoogleLocation = normalized.GoogleLocation
group.StaticHeaders = normalized.StaticHeaders
group.HeadersProfile = normalized.HeadersProfile
group.Models = normalized.Models
group.Status = normalized.Status
if err := h.db.Save(&group).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update provider group", "details": err.Error()})
return
}
if err := h.sync.SyncProvidersForGroup(h.db, group.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()})
return
}
if err := h.sync.SyncBindingsForGroup(h.db, group.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
return
}
c.JSON(http.StatusOK, group)
}
// DeleteProviderGroup godoc
// @Summary Delete provider group
// @Description Delete a provider group and its api keys/bindings
// @Tags admin
// @Produce json
// @Security AdminAuth
// @Param id path int true "ProviderGroup ID"
// @Success 200 {object} ResponseEnvelope{data=MapData}
// @Failure 400 {object} ResponseEnvelope{data=MapData}
// @Failure 404 {object} ResponseEnvelope{data=MapData}
// @Failure 500 {object} ResponseEnvelope{data=MapData}
// @Router /admin/provider-groups/{id} [delete]
func (h *Handler) DeleteProviderGroup(c *gin.Context) {
id, ok := parseUintParam(c, "id")
if !ok {
return
}
var group model.ProviderGroup
if err := h.db.First(&group, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"})
return
}
if err := h.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("group_id = ?", group.ID).Delete(&model.APIKey{}).Error; err != nil {
return err
}
if err := tx.Where("group_id = ?", group.ID).Delete(&model.Binding{}).Error; err != nil {
return err
}
return tx.Delete(&group).Error
}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete provider group", "details": err.Error()})
return
}
if err := h.sync.SyncProvidersForGroup(h.db, group.ID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()})
return
}
if err := h.sync.SyncBindingsForGroup(h.db, group.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"})
}