package handler import ( "errors" "net/http" "strconv" "github.com/freefire/jiang13-bbs/middleware" "github.com/freefire/jiang13-bbs/model" "github.com/freefire/jiang13-bbs/service" "github.com/gin-gonic/gin" ) // snippetError 统一错误映射 func snippetError(c *gin.Context, err error) { switch { case errors.Is(err, service.ErrSnippetNotFound): c.JSON(http.StatusNotFound, gin.H{"error": "代码片段不存在"}) case errors.Is(err, service.ErrSnippetForbidden): c.JSON(http.StatusForbidden, gin.H{"error": "无权操作该代码片段"}) case errors.Is(err, service.ErrSnippetBadInput): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrCommentNotFound): c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"}) case errors.Is(err, service.ErrCommentForbidden): c.JSON(http.StatusForbidden, gin.H{"error": "无权操作该评论"}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器错误"}) } } // SnippetList 公开片段广场 func (h *Handlers) SnippetList(c *gin.Context) { q := c.Query("q") lang := c.Query("lang") sort := c.Query("sort") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "12")) items, total, err := h.SnippetSvc.ListPublic(q, lang, sort, page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取片段列表失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // SnippetLanguages 语言白名单 func (h *Handlers) SnippetLanguages(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"languages": service.SnippetLanguages()}) } // SnippetStats 广场聚合统计 func (h *Handlers) SnippetStats(c *gin.Context) { stats, err := h.SnippetSvc.Stats() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取统计失败"}) return } c.JSON(http.StatusOK, gin.H{"stats": stats}) } // SnippetDetail 公开片段详情 func (h *Handlers) SnippetDetail(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } var viewerID uint if claims := middleware.CurrentUser(c); claims != nil { viewerID = claims.ID } isStaff := h.isStaff(c) detail, err := h.SnippetSvc.GetPublic(uint(id), viewerID, isStaff) if err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"snippet": detail}) } // SnippetMine 我的片段列表 func (h *Handlers) SnippetMine(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } q := c.Query("q") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "12")) items, total, err := h.SnippetSvc.ListMine(claims.ID, q, page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取片段列表失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // SnippetCreate 创建片段 func (h *Handlers) SnippetCreate(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } var in service.SnippetInput if err := c.ShouldBindJSON(&in); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } snippet, err := h.SnippetSvc.Create(&in, claims.ID) if err != nil { snippetError(c, err) return } c.JSON(http.StatusCreated, gin.H{"snippet": snippet}) } // SnippetUpdate 更新片段 func (h *Handlers) SnippetUpdate(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } var in service.SnippetInput if err := c.ShouldBindJSON(&in); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } snippet, err := h.SnippetSvc.Update(uint(id), &in, claims.ID, h.isStaff(c)) if err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"snippet": snippet}) } // SnippetDelete 删除片段(owner 或 staff) func (h *Handlers) SnippetDelete(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } if err := h.SnippetSvc.Delete(uint(id), claims.ID, h.isStaff(c)); err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // SnippetFork Fork 公开片段 func (h *Handlers) SnippetFork(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } snippet, err := h.SnippetSvc.Fork(uint(id), claims.ID) if err != nil { snippetError(c, err) return } c.JSON(http.StatusCreated, gin.H{"snippet": snippet}) } // SnippetCopy 记录复制(节流由 service 内部处理) func (h *Handlers) SnippetCopy(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } key := c.ClientIP() if claims := middleware.CurrentUser(c); claims != nil { key = "user:" + strconv.FormatUint(uint64(claims.ID), 10) } if err := h.SnippetSvc.RecordCopy(uint(id), key); err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // SnippetHistory 片段编辑历史 func (h *Handlers) SnippetHistory(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) items, total, err := h.SnippetSvc.ListHistory(uint(id), page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取历史失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // SnippetForks 公开 fork 列表 func (h *Handlers) SnippetForks(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "12")) items, total, err := h.SnippetSvc.ListForks(uint(id), page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取 Fork 列表失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // SnippetComments 片段评论列表 func (h *Handlers) SnippetComments(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) items, total, err := h.SnippetSvc.ListComments(uint(id), page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // SnippetCommentCreate 创建片段评论 func (h *Handlers) SnippetCommentCreate(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } snippetID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } var in struct { Content string `json:"content"` ParentID *uint `json:"parent_id"` } if err := c.ShouldBindJSON(&in); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } comment, err := h.SnippetSvc.CreateComment(claims.ID, uint(snippetID), in.Content, in.ParentID) if err != nil { snippetError(c, err) return } c.JSON(http.StatusCreated, gin.H{"comment": comment}) } // SnippetCommentUpdate 更新片段评论 func (h *Handlers) SnippetCommentUpdate(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } cid, err := strconv.ParseUint(c.Param("cid"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的评论 ID"}) return } var in struct { Content string `json:"content"` } if err := c.ShouldBindJSON(&in); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } comment, err := h.SnippetSvc.UpdateComment(uint(cid), claims.ID, in.Content) if err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"comment": comment}) } // SnippetCommentDelete 删除片段评论 func (h *Handlers) SnippetCommentDelete(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } cid, err := strconv.ParseUint(c.Param("cid"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的评论 ID"}) return } if err := h.SnippetSvc.DeleteComment(uint(cid), claims.ID, h.isStaff(c)); err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // SnippetCommentHistory 片段评论编辑历史(登录即可见;分页) func (h *Handlers) SnippetCommentHistory(c *gin.Context) { claims := middleware.CurrentUser(c) cid, err := strconv.ParseUint(c.Param("cid"), 10, 64) if err != nil || cid == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的评论 ID"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "10")) items, total, err := h.SnippetSvc.ListCommentHistory(h.loadActor(claims.ID), uint(cid), page, size) if err != nil { switch { case errors.Is(err, service.ErrCommentNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrCommentForbidden): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "获取编辑历史失败"}) } return } if page < 1 { page = 1 } if size < 1 || size > 50 { size = 10 } c.JSON(http.StatusOK, gin.H{ "items": items, "total": total, "page": page, "size": size, }) } // AdminSnippetList 管理端片段列表 func (h *Handlers) AdminSnippetList(c *gin.Context) { q := c.Query("q") visibility := c.Query("visibility") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) items, total, err := h.SnippetSvc.AdminList(q, visibility, page, size) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取片段列表失败"}) return } c.JSON(http.StatusOK, gin.H{"items": nonNilSlice(items), "total": total}) } // AdminSnippetDelete 管理端软删片段 func (h *Handlers) AdminSnippetDelete(c *gin.Context) { claims := middleware.CurrentUser(c) if claims == nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } if err := h.SnippetSvc.AdminDelete(uint(id), claims.ID); err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // AdminSnippetSetVisibility 管理端强制修改可见性 func (h *Handlers) AdminSnippetSetVisibility(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的片段 ID"}) return } var in struct { Visibility string `json:"visibility"` } if err := c.ShouldBindJSON(&in); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"}) return } if err := h.SnippetSvc.AdminSetVisibility(uint(id), in.Visibility); err != nil { snippetError(c, err) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // isStaff 判断当前用户是否为管理角色 func (h *Handlers) isStaff(c *gin.Context) bool { claims := middleware.CurrentUser(c) if claims == nil { return false } return model.IsStaff(model.Role(claims.Role)) }