Files
vfxreview/lib/notifications.ts
T
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

253 lines
6.4 KiB
TypeScript

import { db } from "@/lib/db";
import { NotificationType } from "@prisma/client";
export interface CreateNotificationPayload {
userId: string;
type: NotificationType;
title: string;
message: string;
data?: Record<string, unknown>;
}
/**
* Create a single in-app notification.
*/
export async function createNotification(
payload: CreateNotificationPayload
) {
return db.notification.create({
data: {
userId: payload.userId,
type: payload.type,
title: payload.title,
message: payload.message,
data: (payload.data ?? {}) as any,
},
});
}
/**
* Create notifications for multiple users at once.
*/
export async function createNotifications(
payloads: CreateNotificationPayload[]
) {
return db.notification.createMany({
data: payloads.map((p) => ({
userId: p.userId,
type: p.type,
title: p.title,
message: p.message,
data: (p.data ?? {}) as any,
})),
});
}
/**
* Notify all ADMIN, PRODUCER, and SUPERVISOR users about a new version upload.
*/
export async function notifyNewVersionUploaded(params: {
shotCode: string;
versionNumber: number;
projectId: string;
versionId: string;
artistName: string;
}) {
const staff = await db.user.findMany({
where: {
role: { in: ["ADMIN", "PRODUCER", "SUPERVISOR"] },
isActive: true,
},
select: { id: true },
});
const label = `v${String(params.versionNumber).padStart(3, "0")}`;
await createNotifications(
staff.map((u) => ({
userId: u.id,
type: NotificationType.VERSION_UPLOADED,
title: "New version uploaded",
message: `${params.shotCode} ${label} uploaded by ${params.artistName}`,
data: {
versionId: params.versionId,
shotCode: params.shotCode,
versionNumber: params.versionNumber,
},
}))
);
}
/**
* Notify the shot's artist about new feedback.
*/
export async function notifyFeedbackAdded(params: {
artistId: string;
shotCode: string;
frameNumber: number;
versionId: string;
authorName: string;
}) {
await createNotification({
userId: params.artistId,
type: NotificationType.FEEDBACK_ADDED,
title: "New feedback on your shot",
message: `${params.authorName} commented on ${params.shotCode} at frame ${params.frameNumber}`,
data: {
versionId: params.versionId,
shotCode: params.shotCode,
frameNumber: params.frameNumber,
},
});
}
/**
* Notify relevant users of an approval status change.
*/
export async function notifyApprovalChange(params: {
artistId: string | null;
shotCode: string;
versionId: string;
status: "APPROVED" | "REJECTED" | "NEEDS_CHANGES";
reviewerName: string;
}) {
if (!params.artistId) return;
const typeMap = {
APPROVED: NotificationType.SHOT_APPROVED,
REJECTED: NotificationType.SHOT_REJECTED,
NEEDS_CHANGES: NotificationType.REVISION_REQUESTED,
} as const;
const msgMap = {
APPROVED: `${params.shotCode} was approved by ${params.reviewerName}`,
REJECTED: `${params.shotCode} was rejected by ${params.reviewerName}`,
NEEDS_CHANGES: `${params.shotCode} needs changes — ${params.reviewerName}`,
} as const;
await createNotification({
userId: params.artistId,
type: typeMap[params.status],
title: msgMap[params.status],
message: msgMap[params.status],
data: { versionId: params.versionId, shotCode: params.shotCode },
});
}
/**
* Notify an artist that a task has been assigned to them.
*/
export async function notifyTaskAssigned(params: {
artistId: string;
taskTitle: string;
taskId: string;
contextCode: string | null;
assignedByName: string;
}) {
await createNotification({
userId: params.artistId,
type: NotificationType.TASK_ASSIGNED,
title: "New task assigned to you",
message: `${params.assignedByName} assigned you: ${params.taskTitle}${
params.contextCode ? ` (${params.contextCode})` : ""
}`,
data: { taskId: params.taskId },
});
}
/**
* Notify supervisors/producers that a task is ready for internal review.
*/
export async function notifyTaskReadyForReview(params: {
taskId: string;
taskTitle: string;
contextCode: string | null;
artistName: string;
projectId: string;
taskUrl: string;
}) {
const staff = await db.user.findMany({
where: {
role: { in: ["ADMIN", "PRODUCER", "SUPERVISOR"] },
isActive: true,
},
select: { id: true },
});
const label = params.contextCode ? `${params.contextCode}` : "";
await createNotifications(
staff.map((u) => ({
userId: u.id,
type: NotificationType.TASK_READY_FOR_REVIEW,
title: "Task ready for review",
message: `${label}${params.taskTitle} marked ready by ${params.artistName}`,
data: { taskId: params.taskId, taskUrl: params.taskUrl },
}))
);
}
/**
* Notify an artist that a task has been approved.
*/
export async function notifyTaskApproved(params: {
artistId: string;
taskTitle: string;
taskId: string;
contextCode: string | null;
reviewerName: string;
}) {
await createNotification({
userId: params.artistId,
type: NotificationType.TASK_APPROVED,
title: "Task approved",
message: `${params.reviewerName} approved: ${params.taskTitle}${
params.contextCode ? ` (${params.contextCode})` : ""
}`,
data: { taskId: params.taskId },
});
}
/**
* Notify an artist that changes were requested on their task.
*/
export async function notifyTaskChangesRequested(params: {
artistId: string;
taskTitle: string;
taskId: string;
contextCode: string | null;
reviewerName: string;
}) {
await createNotification({
userId: params.artistId,
type: NotificationType.TASK_CHANGES_REQUESTED,
title: "Changes requested on task",
message: `${params.reviewerName} requested changes: ${params.taskTitle}${
params.contextCode ? ` (${params.contextCode})` : ""
}`,
data: { taskId: params.taskId },
});
}
/**
* Notify a comment author about a new reply.
*/
export async function notifyCommentReply(params: {
commentAuthorId: string;
replierName: string;
shotCode: string;
frameNumber: number;
versionId: string;
}) {
await createNotification({
userId: params.commentAuthorId,
type: NotificationType.COMMENT_REPLY,
title: "New reply to your comment",
message: `${params.replierName} replied on ${params.shotCode} frame ${params.frameNumber}`,
data: {
versionId: params.versionId,
shotCode: params.shotCode,
frameNumber: params.frameNumber,
},
});
}