package api import ( "net/http" "strconv" "github.com/ez-api/ez-api/internal/dto" "github.com/ez-api/ez-api/internal/model" "github.com/ez-api/ez-api/internal/service" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type Handler struct { db *gorm.DB sync *service.SyncService logger *service.LogWriter } func NewHandler(db *gorm.DB, sync *service.SyncService, logger *service.LogWriter) *Handler { return &Handler{db: db, sync: sync, logger: logger} } func (h *Handler) CreateKey(c *gin.Context) { var req dto.KeyDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } key := model.Key{ ProviderID: &req.ProviderID, KeySecret: req.KeySecret, Balance: req.Balance, Status: req.Status, Weight: req.Weight, } if err := h.db.Create(&key).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create key", "details": err.Error()}) return } // Write auth hash and refresh snapshots if err := h.sync.SyncAll(h.db); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync key to Redis", "details": err.Error()}) return } c.JSON(http.StatusCreated, key) } func (h *Handler) CreateProvider(c *gin.Context) { var req dto.ProviderDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } provider := model.Provider{ Name: req.Name, Type: req.Type, BaseURL: req.BaseURL, APIKey: req.APIKey, } if err := h.db.Create(&provider).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider", "details": err.Error()}) return } if err := h.sync.SyncAll(h.db); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync provider", "details": err.Error()}) return } c.JSON(http.StatusCreated, provider) } func (h *Handler) CreateModel(c *gin.Context) { var req dto.ModelDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } modelReq := model.Model{ Name: req.Name, ContextWindow: req.ContextWindow, CostPerToken: req.CostPerToken, SupportsVision: req.SupportsVision, SupportsFunctions: req.SupportsFunctions, SupportsToolChoice: req.SupportsToolChoice, SupportsFIM: req.SupportsFIM, MaxOutputTokens: req.MaxOutputTokens, } if err := h.db.Create(&modelReq).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create model", "details": err.Error()}) return } if err := h.sync.SyncAll(h.db); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync model", "details": err.Error()}) return } c.JSON(http.StatusCreated, modelReq) } func (h *Handler) ListModels(c *gin.Context) { var models []model.Model if err := h.db.Find(&models).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list models", "details": err.Error()}) return } c.JSON(http.StatusOK, models) } func (h *Handler) UpdateModel(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 req dto.ModelDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var existing model.Model if err := h.db.First(&existing, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "model not found"}) return } existing.Name = req.Name existing.ContextWindow = req.ContextWindow existing.CostPerToken = req.CostPerToken existing.SupportsVision = req.SupportsVision existing.SupportsFunctions = req.SupportsFunctions existing.SupportsToolChoice = req.SupportsToolChoice existing.SupportsFIM = req.SupportsFIM existing.MaxOutputTokens = req.MaxOutputTokens if err := h.db.Save(&existing).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update model", "details": err.Error()}) return } if err := h.sync.SyncAll(h.db); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync model", "details": err.Error()}) return } c.JSON(http.StatusOK, existing) } func (h *Handler) SyncSnapshot(c *gin.Context) { if err := h.sync.SyncAll(h.db); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync snapshots", "details": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "synced"}) } // IngestLog accepts log records from data plane or other services. func (h *Handler) IngestLog(c *gin.Context) { var rec model.LogRecord if err := c.ShouldBindJSON(&rec); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // By default, only metadata is expected; payload fields may be empty. h.logger.Write(rec) c.JSON(http.StatusAccepted, gin.H{"status": "queued"}) }