package api import ( "net/http" "github.com/gin-gonic/gin" "github.com/hhs/camtalk/internal/logger" "github.com/hhs/camtalk/internal/models" "github.com/hhs/camtalk/internal/store" ) const ( MaxScenariosPerUser = 20 // 每个用户最多 20 个自建情景 MaxPromptLength = 2000 // Prompt 最大长度 ) // UserScenarioHandler 用户情景 API Handler。 type UserScenarioHandler struct { repo store.UserScenarioRepository } // NewUserScenarioHandler 创建用户情景 Handler。 func NewUserScenarioHandler(repo store.UserScenarioRepository) *UserScenarioHandler { return &UserScenarioHandler{repo: repo} } // List 获取用户的所有自建情景。 // GET /api/scenarios func (h *UserScenarioHandler) List(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) return } scenarios, err := h.repo.FindByUserID(c.Request.Context(), userID.(string)) if err != nil { logger.Log.Errorw("查询用户情景失败", "user_id", userID, "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"}) return } if scenarios == nil { scenarios = []*models.UserScenario{} } c.JSON(http.StatusOK, models.UserScenarioListResponse{ Scenarios: scenarios, Total: len(scenarios), }) } // Create 创建用户情景。 // POST /api/scenarios func (h *UserScenarioHandler) Create(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) return } var req models.CreateUserScenarioRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) return } // 检查用户是否已达上限 count, err := h.repo.CountByUserID(c.Request.Context(), userID.(string)) if err != nil { logger.Log.Errorw("统计用户情景数量失败", "user_id", userID, "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"}) return } if count >= MaxScenariosPerUser { c.JSON(http.StatusBadRequest, gin.H{"error": "已达创建上限(最多 20 个)"}) return } // 创建情景 scenario := &models.UserScenario{ UserID: userID.(string), Name: req.Name, Icon: req.Icon, Description: req.Description, Prompt: req.Prompt, Greeting: req.Greeting, Language: req.Language, } if err := h.repo.Create(c.Request.Context(), scenario); err != nil { logger.Log.Errorw("创建用户情景失败", "user_id", userID, "error", err) if err.Error() == "duplicate key value violates unique constraint" { c.JSON(http.StatusBadRequest, gin.H{"error": "情景名称已存在"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"}) return } logger.Log.Infow("创建用户情景成功", "user_id", userID, "scenario_id", scenario.ID) c.JSON(http.StatusCreated, scenario) } // Get 获取单个情景详情。 // GET /api/scenarios/:id func (h *UserScenarioHandler) Get(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) return } scenarioID := c.Param("id") scenario, err := h.repo.FindByIDAndUserID(c.Request.Context(), scenarioID, userID.(string)) if err != nil { logger.Log.Errorw("查询用户情景失败", "user_id", userID, "scenario_id", scenarioID, "error", err) c.JSON(http.StatusNotFound, gin.H{"error": "情景不存在或无权限"}) return } c.JSON(http.StatusOK, scenario) } // Update 更新用户情景。 // PATCH /api/scenarios/:id func (h *UserScenarioHandler) Update(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) return } scenarioID := c.Param("id") // 查询并校验所有权 scenario, err := h.repo.FindByIDAndUserID(c.Request.Context(), scenarioID, userID.(string)) if err != nil { logger.Log.Errorw("查询用户情景失败", "user_id", userID, "scenario_id", scenarioID, "error", err) c.JSON(http.StatusNotFound, gin.H{"error": "情景不存在或无权限"}) return } var req models.UpdateUserScenarioRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) return } // 更新字段 if req.Name != nil { scenario.Name = *req.Name } if req.Icon != nil { scenario.Icon = *req.Icon } if req.Description != nil { scenario.Description = *req.Description } if req.Prompt != nil { scenario.Prompt = *req.Prompt } if req.Greeting != nil { scenario.Greeting = *req.Greeting } if req.Language != nil { scenario.Language = *req.Language } if err := h.repo.Update(c.Request.Context(), scenario); err != nil { logger.Log.Errorw("更新用户情景失败", "user_id", userID, "scenario_id", scenarioID, "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"}) return } logger.Log.Infow("更新用户情景成功", "user_id", userID, "scenario_id", scenarioID) c.JSON(http.StatusOK, scenario) } // Delete 删除用户情景。 // DELETE /api/scenarios/:id func (h *UserScenarioHandler) Delete(c *gin.Context) { userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) return } scenarioID := c.Param("id") // 查询并校验所有权 _, err := h.repo.FindByIDAndUserID(c.Request.Context(), scenarioID, userID.(string)) if err != nil { logger.Log.Errorw("查询用户情景失败", "user_id", userID, "scenario_id", scenarioID, "error", err) c.JSON(http.StatusNotFound, gin.H{"error": "情景不存在或无权限"}) return } if err := h.repo.Delete(c.Request.Context(), scenarioID); err != nil { logger.Log.Errorw("删除用户情景失败", "user_id", userID, "scenario_id", scenarioID, "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"}) return } logger.Log.Infow("删除用户情景成功", "user_id", userID, "scenario_id", scenarioID) c.Status(http.StatusNoContent) }