1204c186ef
- Notification fan-out: replace sequential for loops with Promise.all (allocation-effects, notification-broadcast, create-notification) - Public holiday batch: group resources by location combo, resolve holidays once per group, replace per-holiday delete/findFirst/create with 3 batched queries (~18K → ~5 queries) - Add take guards to unbounded findMany calls (resource-analytics: 5000, resource-marketplace: 2000, resource-capacity: 1000, chargeability-report: 2000) - auto-staffing: add select with only needed fields + take: 5000 - schema.prisma: add 5 missing indexes (ManagementLevel.groupId, Blueprint.isActive/target, Comment.parentId, Vacation.requestedById, Resource.managementLevelGroupId) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
2.9 KiB
TypeScript
100 lines
2.9 KiB
TypeScript
import { emitNotificationCreated } from "../sse/event-bus.js";
|
|
|
|
export interface CreateNotificationParams {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
db: { notification: { create: (args: any) => Promise<{ id: string; userId: string }> } };
|
|
userId: string;
|
|
type: string;
|
|
title: string;
|
|
body?: string | undefined;
|
|
link?: string | undefined;
|
|
entityId?: string | undefined;
|
|
entityType?: string | undefined;
|
|
category?: string | undefined;
|
|
priority?: string | undefined;
|
|
senderId?: string | undefined;
|
|
channel?: string | undefined;
|
|
taskStatus?: string | undefined;
|
|
taskAction?: string | undefined;
|
|
assigneeId?: string | undefined;
|
|
dueDate?: Date | undefined;
|
|
sourceId?: string | undefined;
|
|
/** Set to false to suppress the SSE emitNotificationCreated call. Default: true. */
|
|
emit?: boolean | undefined;
|
|
}
|
|
|
|
/**
|
|
* Create a single in-app notification and optionally emit an SSE event.
|
|
*
|
|
* Handles the `exactOptionalPropertyTypes` spread pattern internally so
|
|
* callers do not need to repeat the `...(val !== undefined ? { key: val } : {})` boilerplate.
|
|
*
|
|
* Returns the created notification's ID.
|
|
*/
|
|
export async function createNotification(
|
|
params: CreateNotificationParams,
|
|
): Promise<string> {
|
|
const {
|
|
db,
|
|
userId,
|
|
type,
|
|
title,
|
|
body,
|
|
link,
|
|
entityId,
|
|
entityType,
|
|
category,
|
|
priority,
|
|
senderId,
|
|
channel,
|
|
taskStatus,
|
|
taskAction,
|
|
assigneeId,
|
|
dueDate,
|
|
sourceId,
|
|
emit = true,
|
|
} = params;
|
|
|
|
const notification = await db.notification.create({
|
|
data: {
|
|
userId,
|
|
type,
|
|
title,
|
|
...(body !== undefined ? { body } : {}),
|
|
...(link !== undefined ? { link } : {}),
|
|
...(entityId !== undefined ? { entityId } : {}),
|
|
...(entityType !== undefined ? { entityType } : {}),
|
|
...(category !== undefined ? { category } : {}),
|
|
...(priority !== undefined ? { priority } : {}),
|
|
...(senderId !== undefined ? { senderId } : {}),
|
|
...(channel !== undefined ? { channel } : {}),
|
|
...(taskStatus !== undefined ? { taskStatus } : {}),
|
|
...(taskAction !== undefined ? { taskAction } : {}),
|
|
...(assigneeId !== undefined ? { assigneeId } : {}),
|
|
...(dueDate !== undefined ? { dueDate } : {}),
|
|
...(sourceId !== undefined ? { sourceId } : {}),
|
|
},
|
|
});
|
|
|
|
if (emit) {
|
|
emitNotificationCreated(userId, notification.id);
|
|
}
|
|
|
|
return notification.id;
|
|
}
|
|
|
|
/**
|
|
* Create one notification per user ID.
|
|
*
|
|
* Useful for fan-out scenarios (e.g. notifying all managers).
|
|
* Returns the count of notifications created.
|
|
*/
|
|
export async function createNotificationsForUsers(
|
|
params: Omit<CreateNotificationParams, "userId"> & { userIds: string[] },
|
|
): Promise<number> {
|
|
const { userIds, ...rest } = params;
|
|
if (userIds.length === 0) return 0;
|
|
await Promise.all(userIds.map((userId) => createNotification({ ...rest, userId })));
|
|
return userIds.length;
|
|
}
|