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 ) { 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 } ); } } }