metis-desk/modules/resources/src/api/infrastructure/persistence/resource-compatibility.ts
2026-10-25 01:19:17 +02:00

114 lines
3.2 KiB
TypeScript

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 companyId = resource.requireCompanyId().toPrimitive();
const current = resource.values.toPrimitive();
const integration = current.integration;
if (!integration) {
return;
}
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;
}