1
8import type { PrismaClient } from "@prisma/client";
9import { asJson } from "./prisma.js";
10
11import type { Redis } from "ioredis";
12import { getLogger } from "../logger.js";
13import { QUEUES, enqueue } from "../queues.js";
14
15const log = getLogger("notify");
16
17
18export async function audit(
19 prisma: PrismaClient,
20 data: {
21 workspaceId: string;
22 resourceType: string;
23 resourceId: string;
24 action: string;
25 metadata?: Record<string, unknown>;
26 pageId?: string;
27 actorId?: string;
28 }
29): Promise<void> {
30 try {
31 await prisma.auditLog.create({
32 data: {
33 actorType: "SYSTEM",
34 actorId: data.actorId ?? "worker",
35 workspaceId: data.workspaceId,
36 pageId: data.pageId ?? null,
37 resourceType: data.resourceType,
38 resourceId: data.resourceId,
39 action: data.action,
40 metadata: asJson(data.metadata ?? {})
41 }
42 });
43 } catch (err) {
44 log.warn({ err: (err as Error).message }, "audit write failed (non-fatal)");
45 }
46}
47
48
53export async function notifyWorkspaceAdmins(
54 prisma: PrismaClient,
55 connection: Redis | null,
56 smtpHost: string,
57 workspaceId: string,
58 title: string,
59 body: string,
60 link?: { type: string; id: string },
61 notificationType: "AUTOMATION" | "DATABASE_CHANGE" | "INTEGRATION" = "AUTOMATION"
62): Promise<void> {
63 try {
64 const workspace = await prisma.workspace.findUnique({
65 where: { id: workspaceId },
66 select: { ownerId: true, name: true }
67 });
68 if (!workspace) return;
69 const recipients = new Set<string>();
70 if (workspace.ownerId) recipients.add(workspace.ownerId);
71 const members = await prisma.workspaceMember.findMany({
72 where: { workspaceId, role: { in: ["ADMIN", "OWNER"] } },
73 select: { userId: true }
74 });
75 for (const m of members) recipients.add(m.userId);
76 if (recipients.size === 0) return;
77
78 for (const userId of recipients) {
79 await prisma.notification.create({
80 data: {
81 workspaceId,
82 userId,
83 type: notificationType,
84 channel: "IN_APP",
85 title,
86 body,
87 link: asJson(link ?? undefined)
88 }
89 });
90 }
91 if (smtpHost && connection) {
92 const users = await prisma.user.findMany({
93 where: { id: { in: [...recipients] } },
94 select: { email: true }
95 });
96 for (const u of users) {
97 await enqueue(connection, QUEUES.email, {
98 to: u.email,
99 subject: `[${workspace.name}] ${title}`,
100 html: `<p>${body}</p>`,
101 text: body
102 });
103 }
104 }
105 } catch (err) {
106 log.warn(
107 { err: (err as Error).message, workspaceId },
108 "admin notification failed (non-fatal)"
109 );
110 }
111}
112