Files
jiang13-bbs/backend/handler/admin_board.go
freefire 3e55b5d230 feat: 管理端板块管理与旧版数据导入,补充部署运营文档
- 新增管理端板块管理页面与后端接口(admin_board)
- 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具
- 聊天用户卡片、板块图标等 UI 组件与界面优化
- 补充 about/公告/1Panel 部署等文档
- gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
2026-09-24 03:37:44 +08:00

199 lines
5.3 KiB
Go

package handler
import (
"errors"
"net/http"
"strconv"
"github.com/freefire/jiang13-bbs/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// ===== 板块管理(仅站长;路由层 RequirePerm(PermBoards) 兜底) =====
// adminBoardBody 板块新建/更新请求体
type adminBoardBody struct {
Name string `json:"name"`
Description string `json:"description"`
Icon string `json:"icon"`
ColorIndex *int `json:"color_index"`
SortOrder *int `json:"sort_order"`
PostPolicy string `json:"post_policy"`
Visible *bool `json:"visible"`
}
// toBoardInput 把请求体转 service 入参
func toBoardInput(b adminBoardBody) service.BoardInput {
return service.BoardInput{
Name: b.Name,
Description: b.Description,
Icon: b.Icon,
ColorIndex: b.ColorIndex,
SortOrder: b.SortOrder,
PostPolicy: b.PostPolicy,
Visible: b.Visible,
}
}
// AdminListBoards 板块列表(管理端:含隐藏板块;附帖子计数)
func (h *Handlers) AdminListBoards(c *gin.Context) {
boards, err := h.Board.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取板块列表失败"})
return
}
c.JSON(http.StatusOK, gin.H{"boards": nonNilSlice(boards)})
}
// AdminCreateBoard 创建板块
func (h *Handlers) AdminCreateBoard(c *gin.Context) {
var body adminBoardBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
board, err := h.Board.Create(toBoardInput(body))
if err != nil {
respondBoardError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"board": board})
}
// AdminUpdateBoard 更新板块
func (h *Handlers) AdminUpdateBoard(c *gin.Context) {
id, ok := parseBoardID(c)
if !ok {
return
}
var body adminBoardBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
board, err := h.Board.Update(id, toBoardInput(body))
if err != nil {
respondBoardError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"board": board})
}
// AdminDeleteBoard 软删板块(仍有帖子则拒绝)
func (h *Handlers) AdminDeleteBoard(c *gin.Context) {
id, ok := parseBoardID(c)
if !ok {
return
}
if err := h.Board.Delete(id); err != nil {
respondBoardError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// adminReorderBody 排序请求体
type adminReorderBody struct {
IDs []uint `json:"ids"`
}
// AdminReorderBoards 批量设置 sort_order
func (h *Handlers) AdminReorderBoards(c *gin.Context) {
var body adminReorderBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
if err := h.Board.Reorder(body.IDs); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "排序失败"})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// AdminListBoardModerators 板块版主列表
func (h *Handlers) AdminListBoardModerators(c *gin.Context) {
id, ok := parseBoardID(c)
if !ok {
return
}
mods, err := h.Board.ListModerators(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取版主失败"})
return
}
c.JSON(http.StatusOK, gin.H{"moderators": nonNilSlice(mods)})
}
// adminBoardModBody 添加版主请求体(按用户名定位)
type adminBoardModBody struct {
Username string `json:"username"`
}
// AdminAddBoardModerator 按用户名添加板块版主
func (h *Handlers) AdminAddBoardModerator(c *gin.Context) {
boardID, ok := parseBoardID(c)
if !ok {
return
}
var body adminBoardModBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
userID := h.Auth.GetUserIDByUsername(body.Username)
if userID == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
if err := h.Board.AddModerator(boardID, userID); err != nil {
respondBoardError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// AdminRemoveBoardModerator 移除版主
func (h *Handlers) AdminRemoveBoardModerator(c *gin.Context) {
boardID, ok := parseBoardID(c)
if !ok {
return
}
uid, err := strconv.ParseUint(c.Param("uid"), 10, 64)
if err != nil || uid == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户 ID"})
return
}
if err := h.Board.RemoveModerator(boardID, uint(uid)); err != nil {
respondBoardError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// parseBoardID 解析路径中的板块 ID
func parseBoardID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的板块 ID"})
return 0, false
}
return uint(id), true
}
// respondBoardError 板块业务错误映射
func respondBoardError(c *gin.Context, err error) {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": "板块不存在"})
case errors.Is(err, service.ErrBoardNameRequired),
errors.Is(err, service.ErrProtectedOwner):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
case errors.Is(err, service.ErrBoardInUse):
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "操作失败"})
}
}