完善角色与消息权限:收紧板管内容处置,彻底删除仅站长,并统一管理删帖/评弹窗。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -73,6 +73,27 @@ func (h *Handlers) AdminUpdateUserRole(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"user": user})
|
||||
}
|
||||
|
||||
// AdminSetUserMessages 站长授予/撤销站点消息管理
|
||||
func (h *Handlers) AdminSetUserMessages(c *gin.Context) {
|
||||
id, ok := parseAdminUserID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
CanManageMessages bool `json:"can_manage_messages"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
user, err := h.AdminUser.SetCanManageMessages(middleware.CurrentActor(c), id, body.CanManageMessages)
|
||||
if err != nil {
|
||||
respondAdminUserError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"user": user})
|
||||
}
|
||||
|
||||
// AdminSetUserBan 封禁 / 解封用户
|
||||
func (h *Handlers) AdminSetUserBan(c *gin.Context) {
|
||||
id, ok := parseAdminUserID(c)
|
||||
|
||||
@@ -301,14 +301,15 @@ func meUserBody(user *model.User, boardIDs []uint) gin.H {
|
||||
boardIDs = []uint{}
|
||||
}
|
||||
return gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"signature": user.Signature,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"board_ids": boardIDs,
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"nickname": user.Nickname,
|
||||
"avatar": user.Avatar,
|
||||
"signature": user.Signature,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"board_ids": boardIDs,
|
||||
"can_manage_messages": user.CanManageMessages,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +347,15 @@ func (h *Handlers) Me(c *gin.Context) {
|
||||
if user.Role == model.RoleBoardAdmin {
|
||||
boardIDs, _ = h.Auth.GetUserBoardIDs(claims.ID)
|
||||
}
|
||||
body := meUserBody(user, boardIDs)
|
||||
// 群管理员数量:非站长/超管用于后台消息入口判定
|
||||
if h.Chat != nil && user.Role != model.RoleOwner && user.Role != model.RoleSuperAdmin {
|
||||
if n, err := h.Chat.CountAdminRooms(user.ID); err == nil {
|
||||
body["chat_admin_room_count"] = n
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": meUserBody(user, boardIDs),
|
||||
"user": body,
|
||||
"unread_count": unread,
|
||||
"chat_unread_count": chatUnread,
|
||||
})
|
||||
|
||||
@@ -21,9 +21,12 @@ func chatErrToStatus(err error) int {
|
||||
errors.Is(err, service.ErrChatOwnerOnly), errors.Is(err, service.ErrChatPrivateInvite),
|
||||
errors.Is(err, service.ErrChatRecallDenied), errors.Is(err, service.ErrChatDefaultLeave),
|
||||
errors.Is(err, service.ErrChatDefaultDissolve), errors.Is(err, service.ErrChatDMDissolve),
|
||||
errors.Is(err, service.ErrChatHallPinFixed):
|
||||
errors.Is(err, service.ErrChatHallPinFixed), errors.Is(err, service.ErrChatSiteOwnerOnly),
|
||||
errors.Is(err, service.ErrChatMuteDenied), errors.Is(err, service.ErrChatCannotMuteOwner),
|
||||
errors.Is(err, service.ErrChatAdminAccess):
|
||||
return http.StatusForbidden
|
||||
case errors.Is(err, service.ErrChatDMSelf), errors.Is(err, service.ErrChatUserGone):
|
||||
case errors.Is(err, service.ErrChatDMSelf), errors.Is(err, service.ErrChatUserGone),
|
||||
errors.Is(err, service.ErrChatInvalidRole):
|
||||
return http.StatusBadRequest
|
||||
default:
|
||||
return http.StatusBadRequest
|
||||
@@ -307,7 +310,7 @@ func (h *Handlers) KickChatMember(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
|
||||
return
|
||||
}
|
||||
if err := h.Chat.Kick(claims.ID, roomID, uint(targetID)); err != nil {
|
||||
if err := h.Chat.Kick(claims.ID, roomID, uint(targetID), h.chatOversee(claims.ID)); err != nil {
|
||||
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
@@ -319,6 +322,63 @@ func (h *Handlers) KickChatMember(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// SetChatMemberMute 禁言/解禁成员
|
||||
func (h *Handlers) SetChatMemberMute(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
roomID, ok := chatRoomID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
targetID, err := strconv.ParseUint(c.Param("uid"), 10, 64)
|
||||
if err != nil || targetID == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Muted bool `json:"muted"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
if err := h.Chat.SetMemberMute(claims.ID, roomID, uint(targetID), body.Muted, h.chatOversee(claims.ID)); err != nil {
|
||||
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "muted": body.Muted})
|
||||
}
|
||||
|
||||
// SetChatMemberRole 站长任命/撤销群管理员
|
||||
func (h *Handlers) SetChatMemberRole(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
roomID, ok := chatRoomID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
targetID, err := strconv.ParseUint(c.Param("uid"), 10, 64)
|
||||
if err != nil || targetID == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
actor := h.loadActor(claims.ID)
|
||||
role := model.RoleUser
|
||||
if actor != nil {
|
||||
role = actor.Role
|
||||
}
|
||||
if err := h.Chat.SetMemberRole(role, roomID, uint(targetID), body.Role); err != nil {
|
||||
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "role": body.Role})
|
||||
}
|
||||
|
||||
// ListChatMessages 历史消息
|
||||
func (h *Handlers) ListChatMessages(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
@@ -474,11 +534,21 @@ func (h *Handlers) ChatUnreadSummary(c *gin.Context) {
|
||||
|
||||
// AdminChatMessages 管理端消息监管队列;room_type=group|direct
|
||||
func (h *Handlers) AdminChatMessages(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
actor := h.loadActor(claims.ID)
|
||||
if actor == nil || !h.Chat.CanAccessAdminMessages(actor) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理消息"})
|
||||
return
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
q := c.Query("q")
|
||||
roomType := c.Query("room_type")
|
||||
msgs, total, err := h.Chat.AdminListMessages(page, q, roomType)
|
||||
msgs, total, err := h.Chat.AdminListMessages(actor, page, q, roomType)
|
||||
if err != nil {
|
||||
if errors.Is(err, service.ErrChatAdminAccess) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err.Error() == "房间类型无效" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -486,12 +556,24 @@ func (h *Handlers) AdminChatMessages(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载聊天消息失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"messages": msgs, "total": total, "page": page})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"messages": msgs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"can_oversee": service.CanOverseeChat(actor.Role),
|
||||
"can_recall": true,
|
||||
"site_messages": actor.HasSiteMessagePerm(),
|
||||
})
|
||||
}
|
||||
|
||||
// AdminRecallChatMessage 管理端撤回群聊消息(staff;监管者可撤任意)
|
||||
// AdminRecallChatMessage 管理端撤回群聊消息
|
||||
func (h *Handlers) AdminRecallChatMessage(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
actor := h.loadActor(claims.ID)
|
||||
if actor == nil || !h.Chat.CanAccessAdminMessages(actor) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理消息"})
|
||||
return
|
||||
}
|
||||
roomID, err := strconv.ParseUint(c.Param("roomId"), 10, 64)
|
||||
if err != nil || roomID == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "房间 ID 无效"})
|
||||
@@ -502,8 +584,7 @@ func (h *Handlers) AdminRecallChatMessage(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "消息 ID 无效"})
|
||||
return
|
||||
}
|
||||
oversee := h.chatOversee(claims.ID)
|
||||
// 非监管 staff:仅允许撤回自己发送的(与前台一致)
|
||||
oversee := service.CanOverseeChat(actor.Role)
|
||||
msg, newly, err := h.Chat.RecallMessage(claims.ID, uint(roomID), uint(mid), oversee)
|
||||
if err != nil {
|
||||
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
|
||||
|
||||
@@ -176,7 +176,8 @@ func (h *Handlers) DeleteComment(c *gin.Context) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrCommentNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrCommentForbidden):
|
||||
case errors.Is(err, service.ErrCommentForbidden),
|
||||
errors.Is(err, service.ErrAuthorProtected):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrCommentDeleteMeta),
|
||||
errors.Is(err, service.ErrCommentInvalidType):
|
||||
@@ -206,7 +207,8 @@ func (h *Handlers) RestoreComment(c *gin.Context) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrCommentNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrCommentForbidden):
|
||||
case errors.Is(err, service.ErrCommentForbidden),
|
||||
errors.Is(err, service.ErrAuthorProtected):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权限恢复此评论"})
|
||||
case errors.Is(err, service.ErrCommentNotDeleted):
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
@@ -218,7 +220,7 @@ func (h *Handlers) RestoreComment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "已恢复"})
|
||||
}
|
||||
|
||||
// PurgeComment 硬删评论及子树(仅版主;不占位)
|
||||
// PurgeComment 硬删评论及子树(仅站长;不占位)
|
||||
func (h *Handlers) PurgeComment(c *gin.Context) {
|
||||
claims := middleware.CurrentUser(c)
|
||||
cid, err := strconv.ParseUint(c.Param("cid"), 10, 64)
|
||||
@@ -230,7 +232,8 @@ func (h *Handlers) PurgeComment(c *gin.Context) {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrCommentNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrCommentForbidden):
|
||||
case errors.Is(err, service.ErrCommentForbidden),
|
||||
errors.Is(err, service.ErrPurgeOwnerOnly):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
|
||||
@@ -247,7 +247,9 @@ func (h *Handlers) writeModerationError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, gorm.ErrRecordNotFound), errors.Is(err, service.ErrContentNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "内容不存在"})
|
||||
case errors.Is(err, service.ErrModerationForbidden):
|
||||
case errors.Is(err, service.ErrModerationForbidden),
|
||||
errors.Is(err, service.ErrAuthorProtected),
|
||||
errors.Is(err, service.ErrPurgeOwnerOnly):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrNotPending),
|
||||
errors.Is(err, service.ErrContentNotDeleted):
|
||||
|
||||
@@ -536,7 +536,8 @@ 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):
|
||||
case errors.Is(err, service.ErrPostForbidden),
|
||||
errors.Is(err, service.ErrAuthorProtected):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
case errors.Is(err, service.ErrPollOptionsLocked),
|
||||
errors.Is(err, service.ErrLotteryPrizesLocked):
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/middleware"
|
||||
"github.com/freefire/jiang13-bbs/model"
|
||||
"github.com/freefire/jiang13-bbs/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -47,7 +48,13 @@ func (h *Handlers) TelemetryPageView(c *gin.Context) {
|
||||
// GET /api/admin/analytics/overview?range=7d|30d
|
||||
func (h *Handlers) AdminAnalyticsOverview(c *gin.Context) {
|
||||
rangeQ := c.DefaultQuery("range", "7d")
|
||||
data, err := h.Analytics.Overview(rangeQ)
|
||||
actor := middleware.CurrentActor(c)
|
||||
scoped := actor != nil && actor.Role == model.RoleBoardAdmin
|
||||
var boardIDs []uint
|
||||
if scoped {
|
||||
boardIDs = actor.BoardIDs
|
||||
}
|
||||
data, err := h.Analytics.Overview(rangeQ, boardIDs, scoped)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取分析数据失败"})
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user