feat(msg): 完整实现 msg 消息服务
包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
This commit is contained in:
166
services/msg/src/notifications/notifications.repository.ts
Normal file
166
services/msg/src/notifications/notifications.repository.ts
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user