import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { MissingRequiredFieldError, TemplateNotFoundError, UndeclaredVariableReferenceError, } from "../lib/errors.generated"; import { extractPlaceholders, parseVariableSchema } from "./createNotificationTemplate"; export interface UpdateNotificationTemplateInput { templateId?: string; eventType?: string; channelId?: string; locale?: string; subject?: string; body?: string; htmlBody?: string | null; /** JSON-encoded VariableSchemaShape string */ variableSchema?: string; } /** * Updates a NotificationTemplate's mutable fields, re-validating placeholder references against the variable schema. Does not rewrite already-sent Notifications. */ export async function run( db: Transaction, input: UpdateNotificationTemplateInput, _ctx: CommandContext, ) { const { templateId, eventType, channelId, locale, subject, body, htmlBody, variableSchema } = input; // Identify the row let lookup = db.selectFrom("NotificationTemplate").selectAll(); if (templateId) { lookup = lookup.where("id", "=", templateId); } else if (eventType && channelId && locale) { lookup = lookup .where("eventType", "=", eventType) .where("channelId", "=", channelId) .where("locale", "=", locale); } else { return err(new TemplateNotFoundError("(no identifier supplied)")); } const existing = await lookup.forUpdate().executeTakeFirst(); if (!existing) { return err(new TemplateNotFoundError(templateId ?? `${eventType}:${channelId}:${locale}`)); } const hasMutation = subject !== undefined || body !== undefined || htmlBody !== undefined || variableSchema !== undefined; if (!hasMutation) { return err(new MissingRequiredFieldError("at least one mutable field")); } if (subject !== undefined && subject.trim() === "") { return err(new MissingRequiredFieldError("subject")); } if (body !== undefined && body.trim() === "") { return err(new MissingRequiredFieldError("body")); } const nextSubject = subject !== undefined ? subject : existing.subject; const nextBody = body !== undefined ? body : existing.body; const nextHtmlBody = htmlBody !== undefined ? htmlBody : existing.htmlBody; const nextVariableSchemaRaw = variableSchema !== undefined ? variableSchema : existing.variableSchema; const schema = parseVariableSchema(nextVariableSchemaRaw); if (!schema) { return err(new MissingRequiredFieldError("variableSchema")); } const declared = new Set(schema.variables.map((v) => v.name)); const referenced = extractPlaceholders(nextSubject, nextBody, nextHtmlBody); for (const ref of referenced) { if (!declared.has(ref)) { return err(new UndeclaredVariableReferenceError(ref)); } } const now = new Date(); let update = db .updateTable("NotificationTemplate") .set("updatedAt", now) .where("id", "=", existing.id); if (subject !== undefined) update = update.set("subject", subject); if (body !== undefined) update = update.set("body", body); if (htmlBody !== undefined) update = update.set("htmlBody", htmlBody); if (variableSchema !== undefined) update = update.set("variableSchema", variableSchema); await update.execute(); return ok({ template: { ...existing, subject: nextSubject, body: nextBody, htmlBody: nextHtmlBody, variableSchema: nextVariableSchemaRaw, updatedAt: now, }, }); }