- 新增管理端板块管理页面与后端接口(admin_board) - 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具 - 聊天用户卡片、板块图标等 UI 组件与界面优化 - 补充 about/公告/1Panel 部署等文档 - gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
199 lines
5.3 KiB
Go
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": "操作失败"})
|
|
}
|
|
}
|