[fix]:[FL-145][系统消息要支持删除]
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -3,6 +3,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from ...core.database import get_db
|
||||
from ...core.dependencies import CurrentUser, get_current_user, require_permission
|
||||
from ...schemas.auth import MessageResponse
|
||||
from ...schemas.system_message import (
|
||||
SystemMessageCreateRequest,
|
||||
SystemMessageListResponse,
|
||||
@@ -11,6 +12,7 @@ from ...schemas.system_message import (
|
||||
)
|
||||
from ...services.system_message_service import (
|
||||
create_system_message,
|
||||
delete_system_message,
|
||||
get_unread_count,
|
||||
list_user_messages,
|
||||
mark_messages_as_read,
|
||||
@@ -72,3 +74,16 @@ def mark_my_messages_read(
|
||||
"""标记消息为已读"""
|
||||
affected = mark_messages_as_read(db, current_user.user.id, payload.message_ids)
|
||||
return {"affected": affected}
|
||||
|
||||
|
||||
@router.delete("/{message_id}", response_model=MessageResponse)
|
||||
def remove_system_message(
|
||||
message_id: str,
|
||||
_: CurrentUser = Depends(require_permission("admin.system_message")),
|
||||
db: Session = Depends(get_db),
|
||||
) -> MessageResponse:
|
||||
"""删除系统消息(需要admin.system_message权限)"""
|
||||
deleted = delete_system_message(db, message_id)
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="System message not found")
|
||||
return MessageResponse(message="System message deleted")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import func, select, update
|
||||
from sqlalchemy import delete, func, select, update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models.system_message import SystemMessage
|
||||
@@ -86,6 +86,14 @@ def mark_messages_as_read(
|
||||
return result.rowcount or 0
|
||||
|
||||
|
||||
def delete_system_message(db: Session, message_id: str) -> bool:
|
||||
"""删除系统消息"""
|
||||
stmt = delete(SystemMessage).where(SystemMessage.id == message_id)
|
||||
result = db.execute(stmt)
|
||||
db.commit()
|
||||
return (result.rowcount or 0) > 0
|
||||
|
||||
|
||||
def get_unread_count(db: Session, user_id: str) -> int:
|
||||
"""获取用户未读消息数量"""
|
||||
query = select(func.count()).select_from(SystemMessage).where(
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import Base
|
||||
from app.models.system_message import SystemMessage
|
||||
from app.schemas.system_message import SystemMessageCreateRequest
|
||||
from app.services.system_message_service import create_system_message, delete_system_message
|
||||
|
||||
|
||||
def test_delete_system_message_removes_existing_message() -> None:
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
Base.metadata.create_all(bind=engine, tables=[SystemMessage.__table__])
|
||||
|
||||
with Session(engine) as db:
|
||||
message = create_system_message(
|
||||
db,
|
||||
SystemMessageCreateRequest(
|
||||
title="系统通知",
|
||||
content="测试内容",
|
||||
message_type="info",
|
||||
),
|
||||
)
|
||||
|
||||
deleted = delete_system_message(db, message.id)
|
||||
|
||||
assert deleted is True
|
||||
assert db.scalar(select(SystemMessage).where(SystemMessage.id == message.id)) is None
|
||||
|
||||
|
||||
def test_delete_system_message_returns_false_when_missing() -> None:
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
Base.metadata.create_all(bind=engine, tables=[SystemMessage.__table__])
|
||||
|
||||
with Session(engine) as db:
|
||||
deleted = delete_system_message(db, "missing-message-id")
|
||||
|
||||
assert deleted is False
|
||||
@@ -14,3 +14,21 @@
|
||||
|
||||
- 风险与关注点:
|
||||
- 改动仅影响系统消息公开响应 schema 的序列化方式,不改变接口字段、数据库结构或查询逻辑。
|
||||
|
||||
## Work Log - 系统消息支持删除(FL-145)
|
||||
|
||||
- 背景:
|
||||
- 系统消息管理页已有创建、查看、标记已读能力,但缺少删除入口。
|
||||
|
||||
- 本次处理:
|
||||
- 后端新增 `DELETE /api/v1/system-messages/{message_id}`,复用 `admin.system_message` 权限,删除系统消息记录。
|
||||
- 前端系统消息列表新增删除操作,使用二次确认,并在删除后刷新列表与未读统计。
|
||||
- 新增系统消息删除服务测试,覆盖存在记录删除与缺失记录返回 `False`。
|
||||
|
||||
- 验证:
|
||||
- `UV_CACHE_DIR=/tmp/fquiz-uv-cache uv run --with pytest --with sqlalchemy --with pydantic --with pydantic-settings --with email-validator python -m pytest api/tests/test_system_message_schema.py api/tests/test_system_message_service.py` 通过。
|
||||
- `npm --workspace web exec eslint src/app/admin/system-messages/page.tsx` 通过。
|
||||
- `npm --workspace web run build` 通过。
|
||||
|
||||
- 风险与关注点:
|
||||
- 当前删除为管理端物理删除系统消息记录;广播消息删除后对所有用户不可见。
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
@@ -72,6 +73,7 @@ export default function AdminSystemMessagesPage() {
|
||||
const [formApi] = Form.useForm<CreateMessageValues>();
|
||||
const [messageTypeFilter, setMessageTypeFilter] = useState<SystemMessageType | "all">("all");
|
||||
const [unreadOnly, setUnreadOnly] = useState(false);
|
||||
const [deletingMessageId, setDeletingMessageId] = useState<string | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
|
||||
@@ -160,6 +162,39 @@ export default function AdminSystemMessagesPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: async (messageId: string) => {
|
||||
if (!canManage) {
|
||||
throw new Error("缺少 admin.system_message 权限");
|
||||
}
|
||||
|
||||
const response = await fetchWithAuth(`/api/v1/system-messages/${messageId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await readApiError(response));
|
||||
}
|
||||
return response.json() as Promise<{ message: string }>;
|
||||
},
|
||||
onMutate: (messageId) => {
|
||||
setDeletingMessageId(messageId);
|
||||
setSuccess("");
|
||||
setError("");
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setError("");
|
||||
setSuccess("系统消息已删除");
|
||||
await refreshMessages();
|
||||
},
|
||||
onError: (candidate) => {
|
||||
setSuccess("");
|
||||
setError(candidate instanceof Error ? candidate.message : "删除失败");
|
||||
},
|
||||
onSettled: () => {
|
||||
setDeletingMessageId(null);
|
||||
},
|
||||
});
|
||||
|
||||
useToastFeedback({
|
||||
errorMessage: error,
|
||||
successMessage: success,
|
||||
@@ -233,9 +268,13 @@ export default function AdminSystemMessagesPage() {
|
||||
key: "actions",
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
render: (_, item) => (
|
||||
render: (_, item) => {
|
||||
const isDeleting = deletingMessageId === item.id;
|
||||
|
||||
return (
|
||||
<Space size="small">
|
||||
<Button
|
||||
disabled={item.is_read}
|
||||
disabled={item.is_read || isDeleting}
|
||||
loading={markReadMutation.isPending && markReadMutation.variables?.includes(item.id)}
|
||||
size="small"
|
||||
type="link"
|
||||
@@ -243,10 +282,26 @@ export default function AdminSystemMessagesPage() {
|
||||
>
|
||||
标记已读
|
||||
</Button>
|
||||
),
|
||||
{canManage && (
|
||||
<Popconfirm
|
||||
title="删除系统消息"
|
||||
description={`确认删除系统消息「${item.title}」吗?`}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true, loading: isDeleting }}
|
||||
onConfirm={() => deleteMutation.mutate(item.id)}
|
||||
>
|
||||
<Button danger loading={isDeleting} size="small" type="link">
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[markReadMutation],
|
||||
[canManage, deleteMutation, deletingMessageId, markReadMutation],
|
||||
);
|
||||
|
||||
if (initializing) {
|
||||
|
||||
Reference in New Issue
Block a user