feat(msg): 完整实现 msg 消息服务

包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
This commit is contained in:
SpecialX
2026-07-10 19:09:52 +08:00
parent 21530dc7f6
commit 7b7abbb309
51 changed files with 5204 additions and 371 deletions

View File

@@ -0,0 +1,166 @@
import { and, count, desc, eq, inArray, lte } from "drizzle-orm";
import { getDb } from "../config/database.js";
import {
notifications,
type NewNotification,
type Notification,
} from "./notifications.schema.js";
/**
* NotificationsRepository —— 通知数据访问层。
*
* 职责:封装 MySQL 读写,与业务逻辑解耦。
* 仲裁依据 G10使用 getDb() 函数式获取 db 实例。
*/
export async function insertNotification(
row: NewNotification,
): Promise<Notification> {
const db = getDb();
await db.insert(notifications).values(row);
return row as Notification;
}
export async function insertNotifications(
rows: NewNotification[],
): Promise<void> {
if (rows.length === 0) return;
const db = getDb();
await db.insert(notifications).values(rows);
}
export async function findById(id: string): Promise<Notification | undefined> {
const db = getDb();
const [row] = await db
.select()
.from(notifications)
.where(eq(notifications.id, id))
.limit(1);
return row;
}
export async function findByEventId(
eventId: string,
): Promise<Notification | undefined> {
const db = getDb();
const [row] = await db
.select()
.from(notifications)
.where(eq(notifications.eventId, eventId))
.limit(1);
return row;
}
export async function listByUser(
userId: string,
options: {
onlyUnread?: boolean;
type?: string;
page: number;
pageSize: number;
},
): Promise<{ items: Notification[]; total: number }> {
const db = getDb();
const conditions = [eq(notifications.userId, userId)];
if (options.onlyUnread) {
conditions.push(eq(notifications.isRead, false));
}
if (options.type) {
conditions.push(
eq(notifications.type, options.type as Notification["type"]),
);
}
const where = and(...conditions);
const offset = (options.page - 1) * options.pageSize;
const items = await db
.select()
.from(notifications)
.where(where)
.orderBy(desc(notifications.createdAt))
.limit(options.pageSize)
.offset(offset);
const [countRow] = await db
.select({ value: count() })
.from(notifications)
.where(where);
return { items, total: countRow?.value ?? 0 };
}
export async function getUnreadCount(userId: string): Promise<number> {
const db = getDb();
const [row] = await db
.select({ value: count() })
.from(notifications)
.where(
and(eq(notifications.userId, userId), eq(notifications.isRead, false)),
);
return row?.value ?? 0;
}
export async function markAsRead(id: string, userId: string): Promise<void> {
const db = getDb();
await db
.update(notifications)
.set({ isRead: true, readAt: new Date(), status: "read" })
.where(and(eq(notifications.id, id), eq(notifications.userId, userId)));
}
export async function batchMarkAsRead(
ids: string[],
userId: string,
): Promise<void> {
const db = getDb();
await db
.update(notifications)
.set({ isRead: true, readAt: new Date(), status: "read" })
.where(
and(inArray(notifications.id, ids), eq(notifications.userId, userId)),
);
}
export async function markAllAsRead(
userId: string,
before?: number,
): Promise<number> {
const db = getDb();
const conditions = [
eq(notifications.userId, userId),
eq(notifications.isRead, false),
];
if (before !== undefined) {
conditions.push(lte(notifications.createdAt, new Date(before)));
}
const result = await db
.update(notifications)
.set({ isRead: true, readAt: new Date(), status: "read" })
.where(and(...conditions));
// mysql2 affectedRows
return (result as unknown as { affectedRows?: number }).affectedRows ?? 0;
}
export async function recallByGroup(groupId: string): Promise<number> {
const db = getDb();
const result = await db
.update(notifications)
.set({ status: "recalled" })
.where(eq(notifications.groupId, groupId));
return (result as unknown as { affectedRows?: number }).affectedRows ?? 0;
}
export async function deleteById(id: string): Promise<void> {
const db = getDb();
await db.delete(notifications).where(eq(notifications.id, id));
}
/** 用于测试/重置 */
export async function deleteAllByUserId(userId: string): Promise<void> {
const db = getDb();
await db.delete(notifications).where(eq(notifications.userId, userId));
}