metis-desk/modules/assets/src/api/infrastructure/persistence/sequelize-infrastructure.repository.ts

142 lines
4.9 KiB
TypeScript
Raw Normal View History

2026-10-17 15:01:16 +00:00
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { Sequelize, Transaction } from "sequelize";
import { QueryTypes } from "sequelize";
import { z } from "zod/v4";
import type { IInfrastructureRepository } from "../../application/infrastructure.repository";
import { AssetCatalogError } from "../../domain/asset-definition";
import { writeAssetRow } from "./asset-relational-sql";
import {
AddressRowSchema,
InfrastructurePersistenceMapper,
InfrastructureRevisionRowSchema,
InfrastructureRoleRowSchema,
InfrastructureRootRowSchema,
InterfaceRowSchema,
} from "./infrastructure-persistence.mapper";
import { writeInfrastructureRevision } from "./write-infrastructure-revision";
export class SequelizeInfrastructureRepository implements IInfrastructureRepository {
constructor(private readonly database: Sequelize) {}
private async load(company: UniqueID, transaction: Transaction, lock = false) {
const companies = await this.database.query<{ id: string }>(
`SELECT id FROM companies WHERE id=:id${lock ? " FOR UPDATE" : ""}`,
{ replacements: { id: company.toPrimitive() }, type: QueryTypes.SELECT, transaction }
);
if (!companies.length) {
return Result.fail(new AssetCatalogError("COMPANY_NOT_FOUND"));
}
const select = async (table: string) =>
this.database.query(
`SELECT t.* FROM ${table} t JOIN asset_infrastructure_revisions r ON r.id=t.revisionId JOIN asset_infrastructure i ON i.id=r.infrastructureId WHERE i.companyId=:companyId`,
{ replacements: { companyId: company.toPrimitive() }, type: QueryTypes.SELECT, transaction }
);
const roots = z.array(InfrastructureRootRowSchema).parse(
await this.database.query("SELECT * FROM asset_infrastructure WHERE companyId=:companyId", {
replacements: { companyId: company.toPrimitive() },
type: QueryTypes.SELECT,
transaction,
})
);
const revisions = z.array(InfrastructureRevisionRowSchema).parse(
await this.database.query(
"SELECT r.* FROM asset_infrastructure_revisions r JOIN asset_infrastructure i ON i.id=r.infrastructureId WHERE i.companyId=:companyId",
{
replacements: { companyId: company.toPrimitive() },
type: QueryTypes.SELECT,
transaction,
}
)
);
const roles = z
.array(InfrastructureRoleRowSchema)
.parse(await select("asset_infrastructure_roles"));
const interfaces = z.array(InterfaceRowSchema).parse(await select("asset_interface_snapshots"));
const addresses = z
.array(AddressRowSchema)
.parse(await select("asset_interface_address_snapshots"));
const actors = z.array(z.object({ id: z.uuid(), name: z.string() })).parse(
await this.database.query("SELECT id,name FROM users", {
type: QueryTypes.SELECT,
transaction,
})
);
const mapper = new InfrastructurePersistenceMapper();
return Result.ok({
items: roots.map((root) => mapper.toDomain(root, revisions, roles, interfaces, addresses)),
actorNames: new Map(actors.map((actor) => [actor.id, actor.name])),
});
}
get(company: UniqueID) {
return this.database.transaction((transaction) => this.load(company, transaction));
}
save(
company: UniqueID,
asset: UniqueID,
change: Parameters<IInfrastructureRepository["save"]>[2]
) {
return this.database.transaction(async (transaction) => {
const loaded = await this.load(company, transaction, true);
if (!loaded.isSuccess) {
return Result.fail(loaded.error);
}
const assets = await this.database.query<{ id: string }>(
"SELECT id FROM assets WHERE companyId=:companyId AND id=:assetId FOR UPDATE",
{
replacements: { companyId: company.toPrimitive(), assetId: asset.toPrimitive() },
type: QueryTypes.SELECT,
transaction,
}
);
if (!assets.length) {
return Result.fail(new AssetCatalogError("ASSET_NOT_FOUND"));
}
const previous = loaded.data.items.find((item) => item.assetId.equals(asset));
const changed = change(previous);
if (!changed.isSuccess) {
return Result.fail(changed.error);
}
const item = changed.data;
if (!previous) {
await writeAssetRow(
this.database,
"asset_infrastructure",
{
id: item.id.toPrimitive(),
companyId: company.toPrimitive(),
assetId: asset.toPrimitive(),
},
transaction
);
}
for (const [position, revision] of item.history.entries()) {
if (previous?.history.some((old) => old.id.equals(revision.id))) {
continue;
}
await writeInfrastructureRevision(
this.database,
item.id.toPrimitive(),
revision,
position,
transaction
);
}
return Result.ok({ item, actorNames: loaded.data.actorNames });
});
}
}