import { UniqueID } from "@repo/rdx-ddd"; import type { Sequelize, Transaction } from "sequelize"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; import type { Resource } from "../../domain/resource.aggregate"; 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 ) { 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; } const roots = await database.query<{ id: string }>( "SELECT id FROM access_workspaces WHERE companyId=:companyId", { replacements: { companyId }, transaction, type: QueryTypes.SELECT } ); let workspaceId = roots[0]?.id; if (!workspaceId) { workspaceId = UniqueID.generateNewID().toPrimitive(); await database.query( "INSERT INTO access_workspaces (id,companyId,scopeKey) VALUES (:id,:companyId,:companyId)", { replacements: { id: workspaceId, companyId }, transaction } ); } const values = accessProjection(current); await writeResourceRow( database, "access_items", { id, workspaceId, kind: "resource" }, transaction ); const { kind: _kind, ...columns } = scalarProjection(values); await writeResourceRow( database, "access_resource_values", { ownerId: id, ...columns }, transaction ); for (const [sequence, revision] of resource.history.entries()) { const revisionId = revision.id.toPrimitive(); if (!(await projectionExists(database, "access_revisions", revisionId, transaction))) { await writeResourceRow( database, "access_revisions", { id: revisionId, itemId: id, sequence, actorId: revision.actorId.toPrimitive(), recordedAt: revision.recordedAt, }, transaction ); const { kind: _revisionKind, ...frozen } = scalarProjection( accessProjection(revision.values.toPrimitive()) ); await writeResourceRow( database, "access_resource_revision_values", { ownerId: revisionId, ...frozen }, transaction ); } } // El contexto es fijo; estas referencias preservan las FK utilizadas por permisos y necesidades. for (const targetId of [ integration.resourceTypeId, integration.systemId, integration.environmentId, ].filter(Boolean)) { const existing = await database.query<{ itemId: string }>( "SELECT itemId FROM access_references WHERE itemId=:id AND targetId=:targetId", { replacements: { id, targetId }, transaction, type: QueryTypes.SELECT } ); if (!existing.length) { await database.query( "INSERT INTO access_references (itemId,targetId) VALUES (:id,:targetId)", { replacements: { id, targetId }, transaction } ); } } } 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 accessProjection(input: ResourceInput) { const integration = input.integration; if (integration?.kind !== "access") { throw new Error("Recurso sin contexto de acceso."); } return { kind: "resource", name: input.name, active: input.active, resourceTypeId: integration.resourceTypeId, systemId: integration.systemId, environmentId: integration.environmentId, reference: input.characteristics[integration.referenceFieldId] || null, 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; }