425 lines
13 KiB
Go
425 lines
13 KiB
Go
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))
|
||
}
|