import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import type { Transaction } from "../generated/kysely-tailordb"; import { NotDeliveredError, NotificationNotFoundError } from "../lib/errors.generated"; export interface ArchiveNotificationInput { notificationId: string; } /** * Adds ARCHIVED to a caller-owned, delivered Notification and stamps archivedAt, writing an ARCHIVED audit row. Idempotent; does not imply READ. */ export async function run(db: Transaction, input: ArchiveNotificationInput, ctx: CommandContext) { const { notificationId } = input; const notification = await db .selectFrom("Notification") .selectAll() .where("id", "=", notificationId) .forUpdate() .executeTakeFirst(); // A notification owned by another user is reported exactly like a missing // one so callers cannot enumerate foreign notification ids. if (!notification || notification.recipientUserId !== ctx.actorId) { return err(new NotificationNotFoundError(notificationId)); } if (notification.deliveryStatus !== "SENT" && notification.deliveryStatus !== "DELIVERED") { return err(new NotDeliveredError(notificationId)); } // Idempotent: row already ARCHIVED -> return unchanged, do not re-stamp archivedAt if (notification.engagementStatuses.includes("ARCHIVED")) { return ok({ notification }); } const now = new Date(); const updated = await db .updateTable("Notification") .set({ engagementStatuses: [...notification.engagementStatuses, "ARCHIVED"], archivedAt: now, updatedAt: now, }) .where("id", "=", notificationId) .returningAll() .executeTakeFirstOrThrow(); await db .insertInto("NotificationDeliveryAudit") .values({ id: crypto.randomUUID(), notificationId, eventType: "ARCHIVED", occurredAt: now, occurredBy: notification.recipientUserId, errorClass: null, errorDetail: null, createdAt: now, updatedAt: now, }) .execute(); return ok({ notification: updated }); }