feat(announcements,messaging,notifications): 实现所有长期问题 — SSE 实时推送 + 通知日志持久化 + 优先级/归档 + 消息星标/草稿 + 公告已读回执/置顶 + 分类筛选/桌面推送 + 测试覆盖
P1-8 通知实时推送(SSE): - 新增 /api/notifications/stream SSE 端点(15 秒推送,5 分钟超时) - 新增 useNotificationStream Hook(SSE + 轮询降级) - NotificationDropdown 改用 SSE 实时推送 P2-12 测试覆盖: - notifications/dispatcher.test.ts(6 个测试,渠道选择逻辑) - notifications/channels/in-app-channel.test.ts(9 个测试,类型映射) - messaging/schema.test.ts(34 个测试,Zod 校验) - tests/e2e/messages.spec.ts(消息模块 E2E 测试) - vitest.unit.config.ts 添加 server-only stub P2-13a 通知发送日志持久化: - 新增 notification_logs 表(userId/title/channel/status/messageId/error/sentAt) - logNotificationSend 改为 async 写入 DB(失败降级 console) - dispatcher 传递 payload 用于持久化 P2-13b 通知优先级和归档: - messageNotifications 表新增 priority(low/normal/high/urgent)和 isArchived 字段 - getNotifications 支持归档和优先级筛选 - 新增 archiveNotificationAction - NotificationList 显示优先级 Badge 和归档按钮 P2-13c 消息星标和草稿: - messages 表新增 isStarred 字段 - 新增 message_drafts 表 - 新增 toggleMessageStar + 草稿 CRUD Server Actions - 新增 5 个草稿 data-access 函数 P2-13d 公告已读回执和置顶: - announcements 表新增 isPinned 字段 - 新增 announcement_reads 表(唯一索引保证幂等) - 新增 toggleAnnouncementPinAction + markAnnouncementAsReadAction - getAnnouncements 排序置顶优先 P2-13e 通知分类筛选和桌面推送: - NotificationList 添加按类型筛选按钮组 - 新增 useDesktopNotifications Hook(浏览器 Notification API) - NotificationDropdown 集成桌面推送(新通知触发) 架构图同步: - 004 和 005 均已更新(新增表、Action、Hook、组件描述)
This commit is contained in:
15
drizzle/0006_notification_logs.sql
Normal file
15
drizzle/0006_notification_logs.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE `notification_logs` (
|
||||
`id` varchar(128) PRIMARY KEY NOT NULL,
|
||||
`user_id` varchar(128) NOT NULL,
|
||||
`title` varchar(255) NOT NULL,
|
||||
`channel` varchar(32) NOT NULL,
|
||||
`status` varchar(16) NOT NULL,
|
||||
`message_id` varchar(255),
|
||||
`error` text,
|
||||
`sent_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
CONSTRAINT `notification_logs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action
|
||||
);
|
||||
CREATE INDEX `notification_logs_user_idx` ON `notification_logs`(`user_id`);
|
||||
CREATE INDEX `notification_logs_channel_idx` ON `notification_logs`(`channel`);
|
||||
CREATE INDEX `notification_logs_status_idx` ON `notification_logs`(`status`);
|
||||
CREATE INDEX `notification_logs_sent_at_idx` ON `notification_logs`(`sent_at`);
|
||||
4
drizzle/0007_notification_priority_archive.sql
Normal file
4
drizzle/0007_notification_priority_archive.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE `message_notifications` ADD COLUMN `priority` varchar(16) DEFAULT 'normal' NOT NULL;
|
||||
ALTER TABLE `message_notifications` ADD COLUMN `is_archived` boolean DEFAULT false NOT NULL;
|
||||
CREATE INDEX `message_notifications_priority_idx` ON `message_notifications`(`priority`);
|
||||
CREATE INDEX `message_notifications_user_archived_idx` ON `message_notifications`(`user_id`, `is_archived`);
|
||||
17
drizzle/0008_message_star_draft.sql
Normal file
17
drizzle/0008_message_star_draft.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
ALTER TABLE `messages` ADD COLUMN `is_starred` boolean DEFAULT false NOT NULL;
|
||||
CREATE INDEX `messages_receiver_starred_idx` ON `messages`(`receiver_id`, `is_starred`);
|
||||
|
||||
CREATE TABLE `message_drafts` (
|
||||
`id` varchar(128) PRIMARY KEY NOT NULL,
|
||||
`user_id` varchar(128) NOT NULL,
|
||||
`receiver_id` varchar(128),
|
||||
`subject` varchar(255),
|
||||
`content` text,
|
||||
`parent_message_id` varchar(128),
|
||||
`updated_at` timestamp DEFAULT (now()) ON UPDATE now() NOT NULL,
|
||||
`created_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
CONSTRAINT `message_drafts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action,
|
||||
CONSTRAINT `message_drafts_receiver_id_users_id_fk` FOREIGN KEY (`receiver_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action
|
||||
);
|
||||
CREATE INDEX `message_drafts_user_idx` ON `message_drafts`(`user_id`);
|
||||
CREATE INDEX `message_drafts_user_updated_idx` ON `message_drafts`(`user_id`, `updated_at`);
|
||||
14
drizzle/0009_announcement_pin_reads.sql
Normal file
14
drizzle/0009_announcement_pin_reads.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
ALTER TABLE `announcements` ADD COLUMN `is_pinned` boolean DEFAULT false NOT NULL;
|
||||
CREATE INDEX `announcements_status_pinned_idx` ON `announcements`(`status`, `is_pinned`);
|
||||
|
||||
CREATE TABLE `announcement_reads` (
|
||||
`id` varchar(128) PRIMARY KEY NOT NULL,
|
||||
`announcement_id` varchar(128) NOT NULL,
|
||||
`user_id` varchar(128) NOT NULL,
|
||||
`read_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
CONSTRAINT `announcement_reads_announcement_id_announcements_id_fk` FOREIGN KEY (`announcement_id`) REFERENCES `announcements`(`id`) ON DELETE cascade ON UPDATE no action,
|
||||
CONSTRAINT `announcement_reads_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action
|
||||
);
|
||||
CREATE INDEX `announcement_reads_announcement_idx` ON `announcement_reads`(`announcement_id`);
|
||||
CREATE INDEX `announcement_reads_user_idx` ON `announcement_reads`(`user_id`);
|
||||
CREATE UNIQUE INDEX `announcement_reads_unique_idx` ON `announcement_reads`(`announcement_id`, `user_id`);
|
||||
Reference in New Issue
Block a user