package admin import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/shopspring/decimal" "go.uber.org/zap" "gorm.io/gorm" "openteam/server/internal/pkg/httpx" "openteam/server/internal/store" ) type modelHandler struct { db *gorm.DB log *zap.Logger } type modelInput struct { Name string `json:"name" binding:"required"` DisplayName string `json:"displayName"` InputPrice string `json:"inputPrice"` OutputPrice string `json:"outputPrice"` CacheReadPrice string `json:"cacheReadPrice"` Enabled *bool `json:"enabled"` Sort *int `json:"sort"` } // List handles GET /api/v1/admin/models. func (h *modelHandler) List(c *gin.Context) { var models []store.Model if err := h.db.Order("sort ASC, id ASC").Find(&models).Error; err != nil { h.log.Warn("list models failed", zap.Error(err)) httpx.Fail(c, http.StatusInternalServerError, "list models failed") return } out := make([]gin.H, 0, len(models)) for i := range models { out = append(out, modelDTO(&models[i])) } httpx.OK(c, out) } // Create handles POST /api/v1/admin/models. func (h *modelHandler) Create(c *gin.Context) { var in modelInput if !httpx.Bind(c, &in) { return } m := &store.Model{ Name: in.Name, DisplayName: in.DisplayName, InputPrice: parsePrice(in.InputPrice), OutputPrice: parsePrice(in.OutputPrice), CacheReadPrice: parsePrice(in.CacheReadPrice), Enabled: true, } if in.Enabled != nil { m.Enabled = *in.Enabled } if in.Sort != nil { m.Sort = *in.Sort } if err := h.db.Create(m).Error; err != nil { h.log.Warn("create model failed", zap.Error(err)) httpx.Fail(c, http.StatusInternalServerError, "create model failed") return } httpx.Created(c, modelDTO(m)) } // Update handles PUT /api/v1/admin/models/:id. func (h *modelHandler) Update(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { httpx.Fail(c, http.StatusBadRequest, "invalid model id") return } var m store.Model if err := h.db.First(&m, id).Error; err != nil { httpx.Fail(c, http.StatusNotFound, "model not found") return } var in modelInput if !httpx.Bind(c, &in) { return } updates := map[string]any{ "name": in.Name, } if in.DisplayName != "" { updates["display_name"] = in.DisplayName } if in.InputPrice != "" { updates["input_price"] = parsePrice(in.InputPrice) } if in.OutputPrice != "" { updates["output_price"] = parsePrice(in.OutputPrice) } if in.CacheReadPrice != "" { updates["cache_read_price"] = parsePrice(in.CacheReadPrice) } if in.Enabled != nil { updates["enabled"] = *in.Enabled } if in.Sort != nil { updates["sort"] = *in.Sort } if err := h.db.Model(&m).Updates(updates).Error; err != nil { h.log.Warn("update model failed", zap.Error(err)) httpx.Fail(c, http.StatusInternalServerError, "update model failed") return } h.db.First(&m, id) httpx.OK(c, modelDTO(&m)) } // UpdatePrice handles PUT /api/v1/admin/models/:id/price. func (h *modelHandler) UpdatePrice(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { httpx.Fail(c, http.StatusBadRequest, "invalid model id") return } var in struct { InputPrice string `json:"inputPrice"` OutputPrice string `json:"outputPrice"` CacheReadPrice string `json:"cacheReadPrice"` } if !httpx.Bind(c, &in) { return } updates := map[string]any{} if in.InputPrice != "" { updates["input_price"] = parsePrice(in.InputPrice) } if in.OutputPrice != "" { updates["output_price"] = parsePrice(in.OutputPrice) } if in.CacheReadPrice != "" { updates["cache_read_price"] = parsePrice(in.CacheReadPrice) } if len(updates) == 0 { httpx.Fail(c, http.StatusBadRequest, "no price fields provided") return } res := h.db.Model(&store.Model{}).Where("id = ?", id).Updates(updates) if res.RowsAffected == 0 { httpx.Fail(c, http.StatusNotFound, "model not found") return } httpx.OK(c, gin.H{"ok": true}) } func parsePrice(s string) decimal.Decimal { d, err := decimal.NewFromString(s) if err != nil { return decimal.Zero } return d.Round(8) } func modelDTO(m *store.Model) gin.H { return gin.H{ "id": m.ID, "name": m.Name, "displayName": m.DisplayName, "inputPrice": m.InputPrice.String(), "outputPrice": m.OutputPrice.String(), "cacheReadPrice": m.CacheReadPrice.String(), "enabled": m.Enabled, "sort": m.Sort, } }