metis-desk/modules/resources/src/api/infrastructure/persistence/resource-compatibility.ts

192 lines
5.5 KiB
TypeScript
Raw Normal View History

2026-10-05 15:01:32 +00:00
import { UniqueID } from "@repo/rdx-ddd";
import type { Sequelize, Transaction } from "sequelize";
import { QueryTypes } from "sequelize";
2026-10-10 14:40:51 +00:00
import { z } from "zod/v4";
2026-10-05 15:01:32 +00:00
import type { Resource } from "../../domain/resource.aggregate";
import type { ResourceInput } from "../../domain/resource-values";
2026-10-10 14:40:51 +00:00
import { writeResourceRow } from "./resource-relational-sql";
2026-10-05 15:01:32 +00:00
/** 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;
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
if (!integration) {
return;
}
const companyId = resource.companyId.toPrimitive();
const id = resource.id.toPrimitive();
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
if (integration.kind === "mailbox") {
const values = mailboxProjection(current);
2026-10-10 10:01:58 +00:00
2026-10-10 14:40:51 +00:00
const columns = scalarProjection(values);
await writeResourceRow(
database,
"mailboxes",
{ id, companyId, addressKey: `${companyId}:${values.address}`, ...columns },
transaction
2026-10-05 15:01:32 +00:00
);
for (const [position, revision] of resource.history.entries()) {
2026-10-10 14:40:51 +00:00
const revisionId = revision.id.toPrimitive();
if (!(await projectionExists(database, "mailbox_revisions", revisionId, transaction))) {
await writeResourceRow(
database,
"mailbox_revisions",
{
id: revisionId,
2026-10-05 15:01:32 +00:00
itemId: id,
position,
actorId: revision.actorId.toPrimitive(),
recordedAt: revision.recordedAt,
2026-10-10 14:40:51 +00:00
...scalarProjection(mailboxProjection(revision.values.toPrimitive())),
2026-10-05 15:01:32 +00:00
},
2026-10-10 14:40:51 +00:00
transaction
);
}
2026-10-05 15:01:32 +00:00
}
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
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;
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
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);
2026-10-10 10:01:58 +00:00
2026-10-10 14:40:51 +00:00
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
2026-10-05 15:01:32 +00:00
);
for (const [sequence, revision] of resource.history.entries()) {
2026-10-10 14:40:51 +00:00
const revisionId = revision.id.toPrimitive();
if (!(await projectionExists(database, "access_revisions", revisionId, transaction))) {
await writeResourceRow(
database,
"access_revisions",
{
id: revisionId,
2026-10-05 15:01:32 +00:00
itemId: id,
sequence,
actorId: revision.actorId.toPrimitive(),
recordedAt: revision.recordedAt,
},
2026-10-10 14:40:51 +00:00
transaction
);
const { kind: _revisionKind, ...frozen } = scalarProjection(
accessProjection(revision.values.toPrimitive())
);
await writeResourceRow(
database,
"access_resource_revision_values",
{ ownerId: revisionId, ...frozen },
transaction
);
}
2026-10-05 15:01:32 +00:00
}
// 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 }
);
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
if (!existing.length) {
await database.query(
"INSERT INTO access_references (itemId,targetId) VALUES (:id,:targetId)",
{ replacements: { id, targetId }, transaction }
);
}
}
}
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
function mailboxProjection(input: ResourceInput) {
const integration = input.integration;
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
if (integration?.kind !== "mailbox") {
throw new Error("Recurso sin contexto de buzón.");
}
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
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,
};
}
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
function accessProjection(input: ResourceInput) {
const integration = input.integration;
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
if (integration?.kind !== "access") {
throw new Error("Recurso sin contexto de acceso.");
}
2026-10-10 10:01:58 +00:00
2026-10-05 15:01:32 +00:00
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,
};
}
2026-10-10 14:40:51 +00:00
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;
}