完善角色与消息权限:收紧板管内容处置,彻底删除仅站长,并统一管理删帖/评弹窗。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,6 +44,10 @@ export interface User {
|
||||
role: string;
|
||||
// 板块管理员被授权的板块(仅 /api/me 返回;前端据此渲染审核入口/按钮)
|
||||
board_ids?: number[];
|
||||
// 站点消息管理(站长授予;站长/超管固有监管不必依赖此字段)
|
||||
can_manage_messages?: boolean;
|
||||
// 作为群管理员的群数量(/me;用于后台消息入口)
|
||||
chat_admin_room_count?: number;
|
||||
// 仅 /api/me 与 /api/profile 响应中返回(用户自身敏感信息)
|
||||
email?: string;
|
||||
signature?: string;
|
||||
@@ -1900,6 +1904,7 @@ export interface AdminUser {
|
||||
role: string; // user | board_admin | admin | super_admin | owner
|
||||
board_ids: number[]; // 板块管理员的授权板块
|
||||
banned: boolean;
|
||||
can_manage_messages?: boolean;
|
||||
points?: number;
|
||||
post_count: number;
|
||||
comment_count: number;
|
||||
@@ -2026,6 +2031,21 @@ export async function apiAdminSetUserRole(
|
||||
return data as { user: AdminUser };
|
||||
}
|
||||
|
||||
// 站长授予/撤销站点消息管理权限
|
||||
export async function apiAdminSetUserMessages(
|
||||
id: number,
|
||||
canManageMessages: boolean
|
||||
): Promise<{ user: AdminUser }> {
|
||||
const res = await fetchWithRefresh(`/api/admin/users/${id}/messages`, {
|
||||
method: "PUT",
|
||||
headers: clientHeaders({ "Content-Type": "application/json" }),
|
||||
body: JSON.stringify({ can_manage_messages: canManageMessages }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error || "消息权限更新失败");
|
||||
return data as { user: AdminUser };
|
||||
}
|
||||
|
||||
// 某用户的登录历史(IP/UA/成败)
|
||||
export async function apiAdminUserLoginLogs(
|
||||
id: number,
|
||||
@@ -2546,7 +2566,7 @@ export interface ChatRoom {
|
||||
// 列表/详情聚合视图:扁平房间 + 当前访问者相关字段
|
||||
export interface ChatRoomView extends ChatRoom {
|
||||
joined: boolean;
|
||||
my_role: "" | "owner" | "member" | "overseer";
|
||||
my_role: "" | "owner" | "admin" | "member" | "overseer";
|
||||
unread_count: number;
|
||||
pinned?: boolean;
|
||||
pin_forced?: boolean;
|
||||
@@ -2558,7 +2578,7 @@ export interface ChatRoomMember {
|
||||
id: number;
|
||||
room_id: number;
|
||||
user_id: number;
|
||||
role: "owner" | "member";
|
||||
role: "owner" | "admin" | "member";
|
||||
last_read_message_id: number;
|
||||
muted: boolean;
|
||||
created_at: string;
|
||||
@@ -2824,7 +2844,7 @@ export async function apiListChatMembers(
|
||||
return data as ChatMembersResponse;
|
||||
}
|
||||
|
||||
// 客户端:群主踢人
|
||||
// 客户端:群主/群管踢人
|
||||
export async function apiKickChatMember(
|
||||
roomId: number,
|
||||
userId: number
|
||||
@@ -2838,7 +2858,45 @@ export async function apiKickChatMember(
|
||||
return data as { ok: boolean };
|
||||
}
|
||||
|
||||
// 客户端:邀请成员(用户名列表,群主/监管)
|
||||
// 客户端:禁言/解禁
|
||||
export async function apiMuteChatMember(
|
||||
roomId: number,
|
||||
userId: number,
|
||||
muted: boolean
|
||||
): Promise<{ ok: boolean; muted: boolean }> {
|
||||
const res = await fetchWithRefresh(
|
||||
`/api/chat/rooms/${roomId}/members/${userId}/mute`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: clientHeaders({ "Content-Type": "application/json" }),
|
||||
body: JSON.stringify({ muted }),
|
||||
}
|
||||
);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error || "禁言操作失败");
|
||||
return data as { ok: boolean; muted: boolean };
|
||||
}
|
||||
|
||||
// 客户端:站长任命/撤销群管理员
|
||||
export async function apiSetChatMemberRole(
|
||||
roomId: number,
|
||||
userId: number,
|
||||
role: "admin" | "member"
|
||||
): Promise<{ ok: boolean; role: string }> {
|
||||
const res = await fetchWithRefresh(
|
||||
`/api/chat/rooms/${roomId}/members/${userId}/role`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: clientHeaders({ "Content-Type": "application/json" }),
|
||||
body: JSON.stringify({ role }),
|
||||
}
|
||||
);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error || "设置群角色失败");
|
||||
return data as { ok: boolean; role: string };
|
||||
}
|
||||
|
||||
// 客户端:邀请成员(用户名列表,群主/群管/监管)
|
||||
export async function apiInviteChatMembers(
|
||||
roomId: number,
|
||||
usernames: string[]
|
||||
|
||||
@@ -44,15 +44,23 @@ export function isOwner(role?: string | null): boolean {
|
||||
return role === ROLES.OWNER;
|
||||
}
|
||||
|
||||
/** 站长/超管:群聊监管旁路(可见全部群、撤回任意消息) */
|
||||
/** 站长/超管:群聊监管旁路(可见全部群含私聊、撤回任意消息) */
|
||||
export function canOverseeChat(role?: string | null): boolean {
|
||||
return role === ROLES.OWNER || role === ROLES.SUPER_ADMIN;
|
||||
}
|
||||
|
||||
/** 携带板块授权的最小用户形状(/me、帖子详情作者等均满足) */
|
||||
/** 彻底删除帖/评:仅站长 */
|
||||
export function canPurgeContent(role?: string | null): boolean {
|
||||
return role === ROLES.OWNER;
|
||||
}
|
||||
|
||||
/** 携带板块授权与消息权限的最小用户形状 */
|
||||
export interface RoleUserLike {
|
||||
role?: string | null;
|
||||
board_ids?: number[] | null;
|
||||
can_manage_messages?: boolean | null;
|
||||
/** 是否至少管理一个群(/me 可带;后台入口用) */
|
||||
chat_admin_room_count?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +77,29 @@ export function canModerateBoard(
|
||||
return (user.board_ids ?? []).includes(boardId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 板管不可软删管理员及以上作者的内容;作者本人不受此限(由调用方先判断自删)
|
||||
*/
|
||||
export function canModerateAuthor(
|
||||
actor: RoleUserLike | null | undefined,
|
||||
authorRole?: string | null
|
||||
): boolean {
|
||||
if (!actor) return false;
|
||||
if (actor.role === ROLES.BOARD_ADMIN) {
|
||||
return roleLevel(authorRole) < ROLE_LEVEL[ROLES.ADMIN];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 是否可软删他人内容(板块范围 + 作者角色保护) */
|
||||
export function canStaffSoftDelete(
|
||||
actor: RoleUserLike | null | undefined,
|
||||
boardId: number | null | undefined,
|
||||
authorRole?: string | null
|
||||
): boolean {
|
||||
return canModerateBoard(actor, boardId) && canModerateAuthor(actor, authorRole);
|
||||
}
|
||||
|
||||
/** 是否拥有任意板块的内容审核权(后台「内容管理」入口) */
|
||||
export function canModerateAny(user: RoleUserLike | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
@@ -76,6 +107,20 @@ export function canModerateAny(user: RoleUserLike | null | undefined): boolean {
|
||||
return user.role === ROLES.BOARD_ADMIN && (user.board_ids?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
/** 站点级消息管理(站长/超管固有,或 can_manage_messages) */
|
||||
export function hasSiteMessagePerm(user: RoleUserLike | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
if (canOverseeChat(user.role)) return true;
|
||||
return !!user.can_manage_messages;
|
||||
}
|
||||
|
||||
/** 后台「消息管理」入口:站点消息权或至少是一个群的管理员 */
|
||||
export function canAccessAdminMessages(user: RoleUserLike | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
if (hasSiteMessagePerm(user)) return true;
|
||||
return (user.chat_admin_room_count ?? 0) > 0;
|
||||
}
|
||||
|
||||
/** 角色图标 key(RoleBadge 负责映射到具体图标,集中配置便于替换) */
|
||||
export type RoleIconKey = "crown" | "shield-star" | "shield" | "shield-board" | "user";
|
||||
|
||||
@@ -102,7 +147,7 @@ export const ROLE_META: Record<RoleKey, RoleMeta> = {
|
||||
colorToken: "--gold",
|
||||
softToken: "--gold-soft",
|
||||
grantDesc:
|
||||
"站点所有者:全部后台能力、群聊监管、用户内容档案;身份不可授予或降级。",
|
||||
"站点所有者:全部后台能力、群聊监管、用户内容档案、彻底删除内容;身份不可授予或降级。",
|
||||
},
|
||||
[ROLES.SUPER_ADMIN]: {
|
||||
key: ROLES.SUPER_ADMIN,
|
||||
@@ -111,7 +156,7 @@ export const ROLE_META: Record<RoleKey, RoleMeta> = {
|
||||
colorToken: "--accent",
|
||||
softToken: "--accent-soft",
|
||||
grantDesc:
|
||||
"用户与角色管理、封禁、外观主题、公告;全站内容审核与置顶/加精;群聊监管(可见全部会话、可撤回任意消息)。不可动站长账号,亦无站长专属的用户内容档案。",
|
||||
"用户与角色管理、封禁、外观主题、公告;全站内容软删与置顶/加精;群聊监管(可见全部会话、可撤回任意消息)。不可彻底删除内容,不可动站长账号,亦无站长专属的用户内容档案。",
|
||||
},
|
||||
[ROLES.ADMIN]: {
|
||||
key: ROLES.ADMIN,
|
||||
@@ -120,7 +165,7 @@ export const ROLE_META: Record<RoleKey, RoleMeta> = {
|
||||
colorToken: "--accent",
|
||||
softToken: "--accent-soft",
|
||||
grantDesc:
|
||||
"公告管理;全站帖子/评论审核;前台置顶与加精。不含用户权限、外观设置与群聊监管旁路。",
|
||||
"公告管理;全站帖子/评论审核与软删;前台置顶与加精。不含用户权限、外观设置、彻底删除与默认的群聊监管(可由站长另授消息管理或任命群管理员)。",
|
||||
},
|
||||
[ROLES.BOARD_ADMIN]: {
|
||||
key: ROLES.BOARD_ADMIN,
|
||||
@@ -129,7 +174,7 @@ export const ROLE_META: Record<RoleKey, RoleMeta> = {
|
||||
colorToken: "--accent",
|
||||
softToken: "--accent-soft",
|
||||
grantDesc:
|
||||
"仅审核并管理被授权板块内的帖子与评论(含删除违规内容)。无公告、置顶/加精、用户管理与外观设置;须至少勾选一个板块。",
|
||||
"仅审核并软删被授权板块内普通用户的帖子与评论;不可处置管理员/站长发布的内容,无消息管理、公告、置顶/加精、用户管理与外观设置;须至少勾选一个板块。",
|
||||
},
|
||||
[ROLES.USER]: {
|
||||
key: ROLES.USER,
|
||||
|
||||
Reference in New Issue
Block a user