metis-desk/modules/companies/src/api/infrastructure/persistence/sequelize-intervention-template.repository.ts

123 lines
4.8 KiB
TypeScript

import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { QueryTypes, type Sequelize, type Transaction } from "sequelize";
import type { IInterventionTemplateRepository } from "../../application/intervention-template.repository";
import type { InterventionTemplate } from "../../domain/intervention-template.aggregate";
import { InterventionTemplateError } from "../../domain/intervention-template-values";
import { InterventionTemplatePersistenceMapper } from "./intervention-template-persistence.mapper";
export class SequelizeInterventionTemplateRepository implements IInterventionTemplateRepository {
private readonly mapper = new InterventionTemplatePersistenceMapper();
constructor(private readonly database: Sequelize) {}
private async exists(companyId: UniqueID | undefined, transaction?: Transaction) {
if (!companyId) {
return true;
}
const rows = await this.database.query("SELECT id FROM companies WHERE id=:id", {
replacements: { id: companyId.toPrimitive() },
type: QueryTypes.SELECT,
...(transaction ? { transaction } : {}),
});
return rows.length > 0;
}
private async read(transaction?: Transaction) {
const rows = await this.database.query("SELECT * FROM intervention_templates", {
type: QueryTypes.SELECT,
...(transaction ? { transaction } : {}),
});
const revisions = await this.database.query<{ templateId: string }>(
"SELECT * FROM intervention_template_revisions ORDER BY position",
{ type: QueryTypes.SELECT, ...(transaction ? { transaction } : {}) }
);
const users = await this.database.query<{ id: string; name: string }>(
"SELECT id,name FROM users",
{ type: QueryTypes.SELECT, ...(transaction ? { transaction } : {}) }
);
return {
templates: rows.map((row) => {
const parsed = this.rowIdentifier(row);
return this.mapper.toDomain(
row,
revisions.filter((item) => item.templateId === parsed)
);
}),
actorNames: new Map(users.map((user) => [user.id, user.name])),
};
}
private rowIdentifier(row: object) {
if (!("id" in row) || typeof row.id !== "string") {
throw new Error("Invalid intervention template row");
}
return row.id;
}
async list(companyId: UniqueID | undefined) {
return this.database.transaction(async (transaction) => {
if (!(await this.exists(companyId, transaction))) {
return Result.fail(new InterventionTemplateError("COMPANY_NOT_FOUND"));
}
const context = await this.read(transaction);
return Result.ok({
...context,
templates: context.templates.filter(
(item) =>
item.companyId.getOrUndefined() === undefined ||
(companyId !== undefined && item.companyId.getOrUndefined()?.equals(companyId) === true)
),
});
});
}
async save(
companyId: UniqueID | undefined,
action: (
templates: InterventionTemplate[]
) => Result<InterventionTemplate, InterventionTemplateError>
) {
return this.database.transaction(async (transaction) => {
await this.database.query("SELECT id FROM intervention_template_lock WHERE id=1 FOR UPDATE", {
type: QueryTypes.SELECT,
transaction,
});
if (!(await this.exists(companyId, transaction))) {
return Result.fail(new InterventionTemplateError("COMPANY_NOT_FOUND"));
}
const context = await this.read(transaction);
const result = action(context.templates);
if (!result.isSuccess) {
return Result.fail(result.error);
}
const template = result.data;
const previous = context.templates.find((item) => item.id.equals(template.id));
await this.write(template, previous, transaction);
return Result.ok({ ...context, template });
});
}
private async write(
template: InterventionTemplate,
previous: InterventionTemplate | undefined,
transaction: Transaction
) {
if (previous === template) {
return;
}
const persisted = this.mapper.toPersistence(template);
if (previous) {
await this.database.query("UPDATE intervention_templates SET `values`=:values WHERE id=:id", {
replacements: persisted.root,
transaction,
});
} else {
await this.database.query(
"INSERT INTO intervention_templates (id,companyId,requestKey,`values`) VALUES (:id,:companyId,:requestKey,:values)",
{ replacements: persisted.root, transaction }
);
}
for (const revision of persisted.revisions.slice(previous?.history.length ?? 0)) {
await this.database.query(
"INSERT INTO intervention_template_revisions (id,templateId,`values`,actorId,recordedAt,position) VALUES (:id,:templateId,:values,:actorId,:recordedAt,:position)",
{ replacements: revision, transaction }
);
}
}
}