Initial commit
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user