1
12import type { NotificationType } from "@prisma/client";
13import { asJson } from "../../../lib/prisma.js";
14import { FatalError } from "./classify.js";
15import { interpolateConfig, resolveUserId } from "./helpers.js";
16import type { ActionExecutor, ActionResult } from "./types.js";
17
18
24function toLink(path: string): { type: string; id: string; pageId?: string } | { type: "path"; path: string } {
25 const m = /^\/w\/[^/]+\/(page|databases|records|record)\/([\w-]+)/.exec(path);
26 const kind = m?.[1];
27 const id = m?.[2];
28 if (kind && id) {
29 const type = kind === "record" || kind === "records" ? "record" : kind;
30 return { type, id };
31 }
32 return { type: "path", path };
33}
34
35const executor: ActionExecutor = {
36 type: "CREATE_NOTIFICATION",
37 async execute(cfg, ctx, deps): Promise<ActionResult> {
38 const warnings: string[] = [];
39 const toRaw = (cfg.to as string) ?? "automation_creator";
40 const userId = resolveUserId(toRaw, ctx);
41
42
43
44 const user = await deps.prisma.user.findUnique({ where: { id: userId }, select: { id: true } });
45 if (!user) throw new FatalError("USER_NOT_FOUND", `notification target user ${userId} not found`);
46 const membership = await deps.prisma.workspaceMember.findUnique({
47 where: { workspaceId_userId: { workspaceId: ctx.workspaceId, userId } },
48 select: { role: true }
49 });
50 if (!membership) {
51 throw new FatalError("PERMISSION_DENIED", "notification target is not a member of the workspace");
52 }
53
54 const body = interpolateConfig(cfg.body as string, ctx.vars, 500, "body", warnings);
55 const link = typeof cfg.link === "string" ? cfg.link : undefined;
56
57 const row = await deps.prisma.notification.create({
58 data: {
59 workspaceId: ctx.workspaceId,
60 userId,
61 type: cfg.type as NotificationType,
62 channel: "IN_APP",
63 title: ctx.automationName,
64 body,
65
66
67 ...(link ? { link: asJson(toLink(link)) } : {}),
68 actorId: ctx.actorUserId
69 },
70 select: { id: true }
71 });
72
73 return {
74 output: { notificationId: row.id, userId, title: ctx.automationName },
75 emittedEvents: [],
76 audit: { action: "automation.notification_created", resourceType: "notification", resourceId: row.id, metadata: { userId } }
77 };
78 }
79};
80
81export default executor;
82