114 lines
3.2 KiB
TypeScript
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;
|
|
}
|