import type { Sequelize, Transaction } from "sequelize"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; import type { IResourceAccessWriter } from "../../application/resource-access-writer"; import type { Resource } from "../../domain/resource.aggregate"; import { ResourceCatalogError } from "../../domain/resource-definition"; import type { ResourceInput } from "../../domain/resource-values"; import { writeResourceRow } from "./resource-relational-sql"; /** Proyecciones para los contratos de accesos y perfiles: la ficha editable pertenece a resources. */ export async function projectResource( database: Sequelize, resource: Resource, transaction: Transaction, accessWriter?: IResourceAccessWriter ) { const current = resource.values.toPrimitive(); const integration = current.integration; if (!integration) { return; } const companyId = resource.companyId.toPrimitive(); const id = resource.id.toPrimitive(); if (integration.kind === "mailbox") { const values = mailboxProjection(current); const columns = scalarProjection(values); await writeResourceRow( database, "mailboxes", { id, companyId, addressKey: `${companyId}:${values.address}`, ...columns }, transaction ); for (const [position, revision] of resource.history.entries()) { const revisionId = revision.id.toPrimitive(); if (!(await projectionExists(database, "mailbox_revisions", revisionId, transaction))) { await writeResourceRow( database, "mailbox_revisions", { id: revisionId, itemId: id, position, actorId: revision.actorId.toPrimitive(), recordedAt: revision.recordedAt, ...scalarProjection(mailboxProjection(revision.values.toPrimitive())), }, transaction ); } } return; } if (!accessWriter) { throw new ResourceCatalogError("ACCESS_PROJECTION_UNAVAILABLE"); } await accessWriter.project({ resourceId: id, companyId, values: current, history: resource.history.map((revision) => ({ id: revision.id.toPrimitive(), values: revision.values.toPrimitive(), actorId: revision.actorId.toPrimitive(), recordedAt: revision.recordedAt, })), }); } function mailboxProjection(input: ResourceInput) { const integration = input.integration; if (integration?.kind !== "mailbox") { throw new Error("Recurso sin contexto de buzón."); } return { address: input.characteristics[integration.addressFieldId], kind: integration.mailboxKind, context: input.characteristics[integration.contextFieldId] || null, accountId: integration.accountId, active: input.active, notes: input.notes || null, }; } function scalarProjection(values: unknown) { return z .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])) .parse(values); } async function projectionExists( database: Sequelize, table: "mailbox_revisions" | "access_revisions", id: string, transaction: Transaction ) { const rows = await database.query(`SELECT id FROM ${table} WHERE id=:id`, { replacements: { id }, transaction, type: QueryTypes.SELECT, }); return rows.length > 0; }