142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
|
|
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 });
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|