import type { UniqueID } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import type { Sequelize, Transaction } from "sequelize"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; import type { ListGlobalApplicationHistoryRequestDTO } from "../../../common"; import { GlobalResourceAdminSchema, ListGlobalApplicationHistoryResponseSchema, ResourceOwnershipSchema, } from "../../../common"; import type { IGlobalResourceRepository } from "../../application/global-resource.repository"; import { captureGlobalResourceSpecification } from "../../application/global-resource-specification"; import { readOperatingSystemIdentity, validateOperatingSystemIdentity, } from "../../application/operating-system-identity-policy"; import { completeOperatingSystemIdentity, sameOperatingSystemIdentity, } from "../../domain/operating-system-identity"; import type { Resource } from "../../domain/resource.aggregate"; import type { ResourceCatalog } from "../../domain/resource-catalog.aggregate"; import { ResourceCatalogError } from "../../domain/resource-definition"; import type { ResourceInput } from "../../domain/resource-values"; import { ResourceBindingModel, ResourceCatalogModel } from "./resource-catalog.model"; import { ResourceCatalogPersistenceMapper } from "./resource-catalog-persistence.mapper"; import { readDefinitions } from "./resource-catalog-relational"; import { readInventoryByIds, writeGlobalInventory } from "./resource-inventory-relational"; import { ResourcePersistenceMapper } from "./resource-persistence.mapper"; import { readResourceReferenceMetadata } from "./resource-reference-metadata"; import { ResourceScopeSchemaReader } from "./resource-scope-schema"; const HistoryCursorSchema = z.strictObject({ version: z.literal(1), resourceId: z.uuid(), position: z.number().int().nonnegative(), }); const HistoryIdentitySchema = z.object({ id: z.uuid(), position: z.number().int().nonnegative() }); export class SequelizeGlobalResourceRepository implements IGlobalResourceRepository { private readonly schema: ResourceScopeSchemaReader; private readonly mapper = new ResourcePersistenceMapper(); constructor( private readonly database: Sequelize, private readonly kind: "application" | "os" = "application" ) { this.schema = new ResourceScopeSchemaReader(database); } private async catalog(transaction: Transaction, write: boolean) { const root = await ResourceCatalogModel.findOne({ where: { catalogKey: "global" }, transaction, ...(write ? { lock: transaction.LOCK.UPDATE } : {}), }); if (!root) { throw new Error("Catálogo de recursos no inicializado."); } const definitions = await readDefinitions(this.database, root.id, transaction); const bindings = await ResourceBindingModel.findAll({ where: { templateId: definitions.map((item) => item.id) }, transaction, }); return new ResourceCatalogPersistenceMapper().toDomain(root, definitions, bindings, []); } template(options = { existing: true, kind: this.kind }) { return this.database.transaction(async (transaction) => { const catalog = await this.catalog(transaction, false); const template = catalog.definitions.find( (item) => item.stockKey.getOrUndefined() === `template:${this.kind}` ); return template ? this.specificationResponse( captureGlobalResourceSpecification(catalog, template.id.toPrimitive(), { ...options, kind: this.kind, }) ) : Result.fail(new ResourceCatalogError("TEMPLATE_UNAVAILABLE")); }); } private specificationResponse(result: ReturnType) { return result.isSuccess ? Result.ok(result.data.toPrimitive()) : result; } private async isGlobal(id: UniqueID, transaction: Transaction) { const rows = await this.database.query<{ companyId: unknown; scope?: unknown; templateId: string; }>("SELECT * FROM resources WHERE id=:id", { replacements: { id: id.toPrimitive() }, transaction, type: QueryTypes.SELECT, }); const root = rows[0]; if (!root) { return false; } const mode = await this.schema.read(); const ownership = ResourceOwnershipSchema.parse({ scope: mode === "legacy" ? "company" : root.scope, companyId: root.companyId, }); if (ownership.scope !== "global") { return false; } const metadata = await readResourceReferenceMetadata(this.database, transaction, mode, { globalOnly: true, ids: [id.toPrimitive()], }); return metadata[0]?.reference?.kind === this.kind; } admin(id: UniqueID) { return this.database.transaction(async (transaction) => { if (!(await this.isGlobal(id, transaction))) { return Result.fail(new ResourceCatalogError("RESOURCE_NOT_FOUND")); } const catalog = await this.catalog(transaction, false); const latest = await this.database.query<{ id: string }>( "SELECT id FROM resource_revisions WHERE resourceId=:id ORDER BY position DESC LIMIT 1", { replacements: { id: id.toPrimitive() }, transaction, type: QueryTypes.SELECT } ); const loaded = await readInventoryByIds( this.database, [id.toPrimitive()], transaction, latest.map((item) => item.id) ); const row = loaded.rows[0]; if (!(row && latest[0])) { throw new Error("Raíz global sin revisión actual."); } const eligible = captureGlobalResourceSpecification(catalog, row.templateId, { existing: true, kind: this.kind, }); if (!eligible.isSuccess) { throw new Error("Plantilla global persistida incompatible."); } return Result.ok( GlobalResourceAdminSchema.parse({ id: row.id, scope: "global", companyId: null, revisionId: latest[0].id, values: row.values, specification: row.specification, }) ); }); } private async writeContext(id: UniqueID, transaction: Transaction) { const mode = await this.schema.read(); const catalog = await this.catalog(transaction, true); const roots = await this.database.query<{ companyId: unknown; scope?: unknown }>( "SELECT * FROM resources WHERE id=:id FOR UPDATE", { replacements: { id: id.toPrimitive() }, transaction, type: QueryTypes.SELECT } ); const root = roots[0]; const ownership = root ? ResourceOwnershipSchema.parse({ scope: mode === "legacy" ? "company" : root.scope, companyId: root.companyId, }) : undefined; const loaded = ownership?.scope === "global" ? await readInventoryByIds(this.database, [id.toPrimitive()], transaction) : undefined; const row = loaded?.rows[0]; const expectedTemplate = catalog.definitions.find( (item) => item.stockKey.getOrUndefined() === `template:${this.kind}` ); const previous = row && loaded && row.templateId === expectedTemplate?.id.toPrimitive() ? this.mapper.toScopedDomain(row, loaded.revisions) : undefined; if (previous) { await readResourceReferenceMetadata(this.database, transaction, mode, { globalOnly: true, ids: [id.toPrimitive()], }); } if ( previous && !captureGlobalResourceSpecification(catalog, previous.values.toPrimitive().templateId, { existing: true, kind: this.kind, }).isSuccess ) { throw new Error("Plantilla global persistida incompatible."); } return { mode, catalog, root, previous, persistedRevisionIds: new Set(previous?.history.map((item) => item.id.toPrimitive()) ?? []), }; } async write(id: UniqueID, action: Parameters[1]) { try { return await this.database.transaction(async (transaction) => { const { mode, catalog, root, previous, persistedRevisionIds } = await this.writeContext( id, transaction ); const changed = action({ catalog, resource: previous ? Maybe.some(previous) : Maybe.none(), occupied: Boolean(root), }); if (!changed.isSuccess) { return changed; } const validation = await this.validateWrite(changed.data, id, catalog, mode, transaction); if (!validation.isSuccess) { return validation; } if (changed.data !== previous) { await writeGlobalInventory( this.database, this.mapper.toGlobalPersistence(changed.data), persistedRevisionIds, transaction ); } const revision = changed.data.history.at(-1); if (!revision) { throw new Error("Recurso global sin revisión."); } return Result.ok({ created: !root, resource: GlobalResourceAdminSchema.parse({ id: changed.data.id.toPrimitive(), scope: "global", companyId: null, revisionId: revision.id.toPrimitive(), values: changed.data.values.toPrimitive(), specification: changed.data.specification.toPrimitive(), }), }); }); } catch (error) { if (error instanceof ResourceCatalogError) { return Result.fail(error); } throw error; } } private async validateWrite( resource: Resource, id: UniqueID, catalog: ResourceCatalog, mode: "legacy" | "scoped", transaction: Transaction ) { if (mode !== "scoped") { return Result.fail(new ResourceCatalogError("RESOURCE_GLOBAL_FEATURE_DISABLED")); } if (!resource.id.equals(id) || resource.scope !== "global") { throw new ResourceCatalogError("RESOURCE_OWNERSHIP_FIXED"); } const eligible = captureGlobalResourceSpecification( catalog, resource.values.toPrimitive().templateId, { existing: true, kind: this.kind } ); if (!eligible.isSuccess) { return eligible; } const identityError = validateOperatingSystemIdentity(resource, catalog); if (identityError) { return Result.fail(new ResourceCatalogError(identityError)); } if (this.kind === "os" && (await this.duplicate(resource, catalog, transaction))) { return Result.fail(new ResourceCatalogError("OS_DUPLICATE")); } return Result.ok(undefined); } /** El lock exclusivo del catálogo serializa esta comprobación con altas y propagaciones. */ private async duplicate(resource: Resource, catalog: ResourceCatalog, transaction: Transaction) { const identity = readOperatingSystemIdentity(catalog, resource.values.toPrimitive()); if (!completeOperatingSystemIdentity(identity)) { return false; } const rows = await readResourceReferenceMetadata(this.database, transaction, "scoped", { globalOnly: true, globalKind: "os", currentRead: true, }); return rows.some( ({ reference }) => reference?.scope === "global" && reference.kind === "os" && reference.id !== resource.id.toPrimitive() && sameOperatingSystemIdentity(identity, reference) ); } review(values: ResourceInput) { return this.database.transaction(async (transaction) => { const catalog = await this.catalog(transaction, false); const identity = readOperatingSystemIdentity(catalog, values); const rows = await readResourceReferenceMetadata( this.database, transaction, await this.schema.read(), { globalOnly: true } ); return { identity, duplicates: rows.flatMap(({ reference }) => reference?.scope === "global" && reference.kind === "os" && sameOperatingSystemIdentity(identity, reference) ? [reference] : [] ), }; }); } history(id: UniqueID, query: ListGlobalApplicationHistoryRequestDTO) { return this.database.transaction(async (transaction) => { if (!(await this.isGlobal(id, transaction))) { return Result.fail(new ResourceCatalogError("RESOURCE_NOT_FOUND")); } let position: number | null = null; if (query.cursor) { try { const cursor = HistoryCursorSchema.parse( JSON.parse(Buffer.from(query.cursor, "base64url").toString("utf8")) ); if (cursor.resourceId !== id.toPrimitive()) { return Result.fail(new ResourceCatalogError("INVALID_REQUEST")); } position = cursor.position; } catch { return Result.fail(new ResourceCatalogError("INVALID_REQUEST")); } } const identities = z.array(HistoryIdentitySchema).parse( await this.database.query( "SELECT id,position FROM resource_revisions WHERE resourceId=:id AND (:position IS NULL OR position < :position) ORDER BY position DESC LIMIT :limit", { replacements: { id: id.toPrimitive(), position, limit: query.pageSize + 1 }, transaction, type: QueryTypes.SELECT, } ) ); const page = identities.slice(0, query.pageSize); const loaded = await readInventoryByIds( this.database, [id.toPrimitive()], transaction, page.map((item) => item.id) ); const actors = page.length ? await this.database.query<{ id: string; name: string }>( "SELECT u.id,u.name FROM users u JOIN resource_revisions r ON r.actorId=u.id WHERE r.id IN (:ids)", { replacements: { ids: page.map((item) => item.id) }, transaction, type: QueryTypes.SELECT, } ) : []; const last = page.at(-1); return Result.ok( ListGlobalApplicationHistoryResponseSchema.parse({ items: page.map((item) => { const revision = loaded.revisions.find((entry) => entry.id === item.id); if (!revision) { throw new Error("Revisión global ausente."); } return { id: revision.id, values: revision.values, specification: revision.specification, actorId: revision.actorId, actorName: actors.find((actor) => actor.id === revision.actorId)?.name ?? revision.actorId, recordedAt: revision.recordedAt, }; }), nextCursor: last && identities.length > page.length ? Buffer.from( JSON.stringify({ version: 1, resourceId: id.toPrimitive(), position: last.position, }) ).toString("base64url") : null, }) ); }); } }