mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
- Introduce `SyncOutboxService` and model to retry failed CP-to-Redis sync operations - Update `SyncService` to handle sync failures by enqueuing tasks to the outbox - Centralize provider group and API key validation logic into `ProviderGroupManager` - Refactor API handlers to utilize the new manager and robust sync methods - Add configuration options for sync outbox (interval, batch size, retries)
254 lines
8.2 KiB
Go
254 lines
8.2 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} model.ProviderGroup
|
|
// @Failure 400 {object} gin.H
|
|
// @Failure 500 {object} gin.H
|
|
// @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),
|
|
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 {array} model.ProviderGroup
|
|
// @Failure 500 {object} gin.H
|
|
// @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} model.ProviderGroup
|
|
// @Failure 400 {object} gin.H
|
|
// @Failure 404 {object} gin.H
|
|
// @Failure 500 {object} gin.H
|
|
// @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} model.ProviderGroup
|
|
// @Failure 400 {object} gin.H
|
|
// @Failure 404 {object} gin.H
|
|
// @Failure 500 {object} gin.H
|
|
// @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 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.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} gin.H
|
|
// @Failure 400 {object} gin.H
|
|
// @Failure 404 {object} gin.H
|
|
// @Failure 500 {object} gin.H
|
|
// @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"})
|
|
}
|