feat: 帖子分级置顶(板块/全站)与板块管理员置顶权限、消息管理权限调整
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
var (
|
||||
ErrContentNotDeleted = errors.New("仅已删除内容可彻底清除")
|
||||
ErrContentNotFound = errors.New("内容不存在")
|
||||
ErrPinOrderForbidden = errors.New("无权配置该层级置顶顺序")
|
||||
)
|
||||
|
||||
func (s *ModerationService) authorRole(userID uint) (model.Role, error) {
|
||||
@@ -511,3 +512,105 @@ func (s *ModerationService) PurgeComment(actor *Actor, id uint) error {
|
||||
return tx.Unscoped().Delete(&model.Comment{}, id).Error
|
||||
})
|
||||
}
|
||||
|
||||
// ===== 置顶顺序配置(后台可拖拽排序,同层级 pin_order 越小越靠前) =====
|
||||
|
||||
// PinnedPostItem 置顶帖排序列表项
|
||||
type PinnedPostItem struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Pinned int `json:"pinned"`
|
||||
PinOrder int `json:"pin_order"`
|
||||
BoardID uint `json:"board_id"`
|
||||
BoardName string `json:"board_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// assertPinScopePermission 校验 actor 对指定置顶层级/板块的排序权限:
|
||||
// 全局置顶仅管理员及以上;板块置顶允许管理员或管辖该板块的板管(board_id 必填)。
|
||||
func (s *ModerationService) assertPinScopePermission(actor *Actor, level int, boardID uint) error {
|
||||
if actor == nil {
|
||||
return ErrPinOrderForbidden
|
||||
}
|
||||
if model.RoleLevel(actor.Role) >= model.RoleLevel(model.RoleAdmin) {
|
||||
return nil
|
||||
}
|
||||
if level == model.PinGlobal {
|
||||
return ErrPinOrderForbidden
|
||||
}
|
||||
if level == model.PinBoard {
|
||||
if boardID == 0 {
|
||||
return ErrPinOrderForbidden // 板管必须指定具体板块
|
||||
}
|
||||
if actor.CanModerateBoard(boardID) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrPinOrderForbidden
|
||||
}
|
||||
|
||||
// ListPinnedPosts 列出指定层级的置顶帖(当前顺序:pin_order 升序,未配置按发帖时间倒序兜底)。
|
||||
// level=2 全站置顶;level=1 需带 board_id(管理员可省=全部板块的板块置顶)。
|
||||
func (s *ModerationService) ListPinnedPosts(actor *Actor, level int, boardID uint) ([]PinnedPostItem, error) {
|
||||
if err := s.assertPinScopePermission(actor, level, boardID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := s.db.Model(&model.Post{}).
|
||||
Where("pinned = ? AND deleted_at IS NULL AND status = ?", level, model.ContentStatusPublished)
|
||||
if boardID > 0 {
|
||||
q = q.Where("board_id = ?", boardID)
|
||||
}
|
||||
var posts []model.Post
|
||||
if err := q.Order("(pin_order = 0), pin_order ASC, created_at DESC").
|
||||
Preload("Board").Find(&posts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]PinnedPostItem, 0, len(posts))
|
||||
for _, p := range posts {
|
||||
items = append(items, PinnedPostItem{
|
||||
ID: p.ID,
|
||||
Title: p.Title,
|
||||
Pinned: p.Pinned,
|
||||
PinOrder: p.PinOrder,
|
||||
BoardID: p.BoardID,
|
||||
BoardName: p.Board.Name,
|
||||
CreatedAt: p.CreatedAt,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// SavePinOrder 按传入 ID 顺序重写 pin_order(从 1 递增)。
|
||||
// 安全约束:ids 必须全部属于该层级且在权限范围内,否则整体拒绝,避免半截写入。
|
||||
func (s *ModerationService) SavePinOrder(actor *Actor, level int, boardID uint, ids []uint) error {
|
||||
if err := s.assertPinScopePermission(actor, level, boardID); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(ids) > 200 {
|
||||
return errors.New("单次最多排序 200 条")
|
||||
}
|
||||
var count int64
|
||||
q := s.db.Model(&model.Post{}).
|
||||
Where("id IN ? AND pinned = ? AND deleted_at IS NULL", ids, level)
|
||||
if boardID > 0 {
|
||||
q = q.Where("board_id = ?", boardID)
|
||||
}
|
||||
if err := q.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count != int64(len(ids)) {
|
||||
return ErrPinOrderForbidden // 含越权/已取消置顶/已删除的 ID
|
||||
}
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
for i, id := range ids {
|
||||
if err := tx.Model(&model.Post{}).Where("id = ?", id).
|
||||
UpdateColumn("pin_order", i+1).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user