/** * apps/worker — shared audit + notification helpers. * * Used by the files, webhooks and automations processors. Kept dependency- * light (Prisma + optional enqueue) so any processor can call it without * importing another processor. */ import type { PrismaClient } from "@prisma/client"; import { asJson } from "./prisma.js"; import type { Redis } from "ioredis"; import { getLogger } from "../logger.js"; import { QUEUES, enqueue } from "../queues.js"; const log = getLogger("notify"); /** Write an AuditLog row; never throws (audit must not fail the job). */ export async function audit( prisma: PrismaClient, data: { workspaceId: string; resourceType: string; resourceId: string; action: string; metadata?: Record; pageId?: string; actorId?: string; } ): Promise { try { await prisma.auditLog.create({ data: { actorType: "SYSTEM", actorId: data.actorId ?? "worker", workspaceId: data.workspaceId, pageId: data.pageId ?? null, resourceType: data.resourceType, resourceId: data.resourceId, action: data.action, metadata: asJson(data.metadata ?? {}) } }); } catch (err) { log.warn({ err: (err as Error).message }, "audit write failed (non-fatal)"); } } /** * Notify every workspace admin (owner + ADMIN/OWNER members): IN_APP * Notification row, plus an email enqueue when SMTP is configured. * Never throws. */ export async function notifyWorkspaceAdmins( prisma: PrismaClient, connection: Redis | null, smtpHost: string, workspaceId: string, title: string, body: string, link?: { type: string; id: string }, notificationType: "AUTOMATION" | "DATABASE_CHANGE" | "INTEGRATION" = "AUTOMATION" ): Promise { try { const workspace = await prisma.workspace.findUnique({ where: { id: workspaceId }, select: { ownerId: true, name: true } }); if (!workspace) return; const recipients = new Set(); if (workspace.ownerId) recipients.add(workspace.ownerId); const members = await prisma.workspaceMember.findMany({ where: { workspaceId, role: { in: ["ADMIN", "OWNER"] } }, select: { userId: true } }); for (const m of members) recipients.add(m.userId); if (recipients.size === 0) return; for (const userId of recipients) { await prisma.notification.create({ data: { workspaceId, userId, type: notificationType, channel: "IN_APP", title, body, link: asJson(link ?? undefined) } }); } if (smtpHost && connection) { const users = await prisma.user.findMany({ where: { id: { in: [...recipients] } }, select: { email: true } }); for (const u of users) { await enqueue(connection, QUEUES.email, { to: u.email, subject: `[${workspace.name}] ${title}`, html: `

${body}

`, text: body }); } } } catch (err) { log.warn( { err: (err as Error).message, workspaceId }, "admin notification failed (non-fatal)" ); } }