import { and, count, desc, eq, inArray, lte, sql } 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 { const db = getDb(); await db.insert(notifications).values(row); return row as Notification; } export async function insertNotifications( rows: NewNotification[], ): Promise { if (rows.length === 0) return; const db = getDb(); await db.insert(notifications).values(rows); } export async function findById(id: string): Promise { 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 { const db = getDb(); const [row] = await db .select() .from(notifications) .where(eq(notifications.eventId, eventId)) .limit(1); return row; } /** * 批量查询已存在的 eventId(用于 sendBatch 幂等过滤)。 * 返回已存在的 eventId 集合。 */ export async function findExistingEventIds( eventIds: string[], ): Promise> { if (eventIds.length === 0) return new Set(); const db = getDb(); const rows = await db .select({ eventId: notifications.eventId }) .from(notifications) .where( and( inArray(notifications.eventId, eventIds), sql`${notifications.eventId} IS NOT NULL`, ), ); return new Set( rows .map((r) => r.eventId) .filter((id): id is string => id !== null && id !== undefined), ); } 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 { 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 { 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 { 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 { 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 { 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 { const db = getDb(); await db.delete(notifications).where(eq(notifications.id, id)); } /** 用于测试/重置 */ export async function deleteAllByUserId(userId: string): Promise { const db = getDb(); await db.delete(notifications).where(eq(notifications.userId, userId)); }