import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { ChannelNotFoundError, MissingRequiredFieldError } from "../lib/errors.generated"; import type { ChannelCapabilities, ChannelId } from "./createNotificationChannel"; export interface UpdateNotificationChannelInput { channelId: ChannelId; displayName?: string; capabilities?: ChannelCapabilities; } function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object"; } function hasAllCapabilityKeys(value: unknown): value is ChannelCapabilities { if (!isRecord(value)) return false; return ( typeof value.supportsHtmlBody === "boolean" && typeof value.supportsAttachments === "boolean" && typeof value.supportsRichActions === "boolean" ); } /** * Updates a registered NotificationChannel's displayName and/or capabilities. Never changes channelId, kind, or enabled; errors when the channel is not registered. */ export async function run( db: Transaction, input: UpdateNotificationChannelInput, _ctx: CommandContext, ) { const { channelId, displayName, capabilities } = input; if (!channelId) { return err(new MissingRequiredFieldError("channelId")); } const existing = await db .selectFrom("NotificationChannel") .selectAll() .where("channelId", "=", channelId) .forUpdate() .executeTakeFirst(); if (!existing) { return err(new ChannelNotFoundError(channelId)); } const updates: { displayName?: string; capabilities?: ChannelCapabilities; updatedAt: Date; } = { updatedAt: new Date() }; if (displayName !== undefined) { const trimmed = displayName.trim(); if (trimmed === "") { return err(new MissingRequiredFieldError("displayName")); } updates.displayName = trimmed; } if (capabilities !== undefined) { if (!hasAllCapabilityKeys(capabilities)) { return err(new MissingRequiredFieldError("capabilities")); } updates.capabilities = capabilities; } const channel = await db .updateTable("NotificationChannel") .set(updates) .where("channelId", "=", channelId) .returningAll() .executeTakeFirstOrThrow(); return ok({ channel }); }