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" ) // Posts 获取帖子列表 func (h *Handlers) Posts(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) boardID, _ := strconv.ParseUint(c.Query("board_id"), 10, 64) sort := c.DefaultQuery("sort", "latest") keyword := c.Query("keyword") recommended := c.Query("recommended") == "true" items, total, err := h.Post.List(service.PostListQuery{ BoardID: uint(boardID), Page: page, Size: size, Sort: sort, Keyword: keyword, Recommended: recommended, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // 填充点赞状态(仅登录用户) if claims := middleware.CurrentUser(c); claims != nil { ids := make([]uint, 0, len(items)) for _, p := range items { ids = append(ids, p.ID) } likedMap := h.Like.BatchHasLiked(ids, claims.ID) for i := range items { items[i].Liked = likedMap[items[i].ID] } } c.JSON(http.StatusOK, gin.H{ "posts": items, "total": total, "page": page, "size": size, }) } // PostDetail 获取帖子详情 func (h *Handlers) PostDetail(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"}) return } claims := middleware.CurrentUser(c) var viewerID uint var loadActor func() *service.Actor if claims != nil { viewerID = claims.ID loadActor = h.actorLoader(claims.ID) // 懒加载:仅非已发布帖才查 DB } post, err := h.Post.GetByIDForViewer(uint(id), viewerID, loadActor) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"}) return } // 填充点赞状态(仅登录用户) if claims != nil { post.Liked = h.Like.HasLiked(post.ID, claims.ID) } c.JSON(http.StatusOK, gin.H{"post": post}) } // CreatePostRequest 发帖请求 type CreatePostRequest struct { BoardID uint `json:"board_id" binding:"required"` Title string `json:"title" binding:"required,min=1,max=256"` Content string `json:"content" binding:"required,min=1"` Tags string `json:"tags"` PostType string `json:"post_type"` } // CreatePost 创建帖子 func (h *Handlers) CreatePost(c *gin.Context) { claims := middleware.CurrentUser(c) var req CreatePostRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } postType := req.PostType if postType == "" { postType = "normal" } // 管理团队成员发帖免审直发;普通用户进入待审核队列。 // 角色变更会强制 JWT 失效(token_version 递增),claims.Role 可视为实时值 status := model.ContentStatusPending if model.IsStaff(model.Role(claims.Role)) { status = model.ContentStatusPublished } post, err := h.Post.Create(claims.ID, req.BoardID, req.Title, req.Content, req.Tags, postType, status) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"post": post}) } // UpdatePostRequest 更新帖子请求 type UpdatePostRequest struct { Title string `json:"title" binding:"omitempty,min=1,max=256"` Content string `json:"content" binding:"omitempty,min=1"` Tags string `json:"tags"` } // UpdatePost 编辑帖子 func (h *Handlers) UpdatePost(c *gin.Context) { claims := middleware.CurrentUser(c) id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"}) return } var req UpdatePostRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } actor := h.loadActor(claims.ID) post, err := h.Post.Update(actor, uint(id), claims.ID, req.Title, req.Content, req.Tags) if err != nil { respondPostModError(c, err) return } c.JSON(http.StatusOK, gin.H{"post": post}) } // DeletePost 删除帖子 func (h *Handlers) DeletePost(c *gin.Context) { claims := middleware.CurrentUser(c) id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"}) return } if err := h.Post.Delete(h.loadActor(claims.ID), uint(id), claims.ID); err != nil { respondPostModError(c, err) return } c.JSON(http.StatusOK, gin.H{"message": "已删除"}) } // TogglePin 切换帖子置顶(管理员及以上,板块管理员无此权限) func (h *Handlers) TogglePin(c *gin.Context) { if !h.requireAdminOrAbove(c) { return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"}) return } pinned, err := h.Post.TogglePin(uint(id)) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"pinned": pinned}) } // ToggleRecommend 切换帖子加精(管理员及以上,板块管理员无此权限) func (h *Handlers) ToggleRecommend(c *gin.Context) { if !h.requireAdminOrAbove(c) { return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的帖子 ID"}) return } recommended, err := h.Post.ToggleRecommend(uint(id)) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"recommended": recommended}) } // requireAdminOrAbove 置顶/加精仅管理员及以上可用;校验失败已写响应,返回 false func (h *Handlers) requireAdminOrAbove(c *gin.Context) bool { claims := middleware.CurrentUser(c) if model.RoleLevel(model.Role(claims.Role)) < model.RoleLevel(model.RoleAdmin) { c.JSON(http.StatusForbidden, gin.H{"error": "仅管理员可操作"}) return false } return true } // respondPostModError 帖子编辑/删除业务错误 → HTTP 状态码 func respondPostModError(c *gin.Context, err error) { switch { case errors.Is(err, service.ErrPostNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrPostForbidden): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }