import { and, eq } from "drizzle-orm"; import { assertDistributionBoardComponentDeleteProjectCommand, assertDistributionBoardComponentInsertProjectCommand, assertDistributionBoardComponentUpdateProjectCommand, createDistributionBoardComponentDeleteProjectCommand, createDistributionBoardComponentInsertProjectCommand, createDistributionBoardComponentUpdateProjectCommand, distributionBoardComponentDeleteCommandType, distributionBoardComponentInsertCommandType, distributionBoardComponentUpdateCommandType, type DistributionBoardComponentSnapshot, type DistributionBoardComponentStructureProjectCommand, } from "../../domain/models/distribution-board-component-structure-project-command.model.js"; import type { DistributionBoardComponentStructureProjectCommandStore, ExecuteDistributionBoardComponentStructureCommandInput, } from "../../domain/ports/distribution-board-component-structure-project-command.store.js"; import type { AppDatabase } from "../database-context.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuitSections } from "../schema/circuit-sections.js"; import { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js"; import { distributionBoardComponents } from "../schema/distribution-board-components.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class DistributionBoardComponentStructureProjectCommandRepository implements DistributionBoardComponentStructureProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute( input: ExecuteDistributionBoardComponentStructureCommandInput ) { return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand( tx, input.projectId, input.command ) ); } private applyCommand( database: AppDatabase, projectId: string, command: DistributionBoardComponentStructureProjectCommand ): DistributionBoardComponentStructureProjectCommand { if (command.type === distributionBoardComponentInsertCommandType) { assertDistributionBoardComponentInsertProjectCommand(command); this.insert(database, projectId, command.payload.snapshot); return createDistributionBoardComponentDeleteProjectCommand( command.payload.snapshot ); } if (command.type === distributionBoardComponentDeleteCommandType) { assertDistributionBoardComponentDeleteProjectCommand(command); this.delete(database, projectId, command.payload.snapshot); return createDistributionBoardComponentInsertProjectCommand( command.payload.snapshot ); } if (command.type === distributionBoardComponentUpdateCommandType) { assertDistributionBoardComponentUpdateProjectCommand(command); this.update( database, projectId, command.payload.expected, command.payload.target ); return createDistributionBoardComponentUpdateProjectCommand( command.payload.target, command.payload.expected ); } throw new Error( "Unsupported distribution-board component structure command." ); } private insert( database: AppDatabase, projectId: string, snapshot: DistributionBoardComponentSnapshot ) { this.assertOwnership(database, projectId, snapshot); const existing = database .select({ id: distributionBoardComponents.id }) .from(distributionBoardComponents) .where(eq(distributionBoardComponents.id, snapshot.component.id)) .get(); if (existing) { throw new Error("Distribution-board component id already exists."); } database .insert(distributionBoardComponents) .values(snapshot.component) .run(); if (snapshot.protectionDevice !== null) { database .insert(distributionBoardComponentProtectionDevices) .values(snapshot.protectionDevice) .run(); } } private delete( database: AppDatabase, projectId: string, snapshot: DistributionBoardComponentSnapshot ) { this.assertOwnership(database, projectId, snapshot); const component = database .select() .from(distributionBoardComponents) .where(eq(distributionBoardComponents.id, snapshot.component.id)) .get(); const protection = database .select() .from(distributionBoardComponentProtectionDevices) .where( eq( distributionBoardComponentProtectionDevices.componentId, snapshot.component.id ) ) .get(); if ( !component || !sameRecord(snapshot.component, component) || !sameNullableRecord(snapshot.protectionDevice, protection) ) { throw new Error( "Distribution-board component changed before deletion." ); } const deleted = database .delete(distributionBoardComponents) .where( and( eq(distributionBoardComponents.id, component.id), eq( distributionBoardComponents.circuitListId, snapshot.component.circuitListId ) ) ) .run(); if (deleted.changes !== 1) { throw new Error( "Distribution-board component could not be deleted." ); } } private update( database: AppDatabase, projectId: string, expected: DistributionBoardComponentSnapshot, target: DistributionBoardComponentSnapshot ) { this.assertOwnership(database, projectId, expected); this.assertOwnership(database, projectId, target); const component = database .select() .from(distributionBoardComponents) .where(eq(distributionBoardComponents.id, expected.component.id)) .get(); const protection = database .select() .from(distributionBoardComponentProtectionDevices) .where( eq( distributionBoardComponentProtectionDevices.componentId, expected.component.id ) ) .get(); if ( !component || !sameRecord(expected.component, component) || !sameNullableRecord(expected.protectionDevice, protection) ) { throw new Error( "Distribution-board component changed before update." ); } database .update(distributionBoardComponents) .set(target.component) .where(eq(distributionBoardComponents.id, expected.component.id)) .run(); if ( expected.protectionDevice !== null && target.protectionDevice !== null ) { database .update(distributionBoardComponentProtectionDevices) .set(target.protectionDevice) .where( eq( distributionBoardComponentProtectionDevices.componentId, expected.component.id ) ) .run(); } } private assertOwnership( database: AppDatabase, projectId: string, snapshot: DistributionBoardComponentSnapshot ) { const list = database .select({ projectId: circuitLists.projectId }) .from(circuitLists) .where(eq(circuitLists.id, snapshot.component.circuitListId)) .get(); if (!list || list.projectId !== projectId) { throw new Error( "Distribution-board component circuit list does not belong to project." ); } if (snapshot.component.sectionId !== null) { const section = database .select({ circuitListId: circuitSections.circuitListId }) .from(circuitSections) .where(eq(circuitSections.id, snapshot.component.sectionId)) .get(); if ( !section || section.circuitListId !== snapshot.component.circuitListId ) { throw new Error( "Distribution-board component section does not belong to circuit list." ); } } } } function sameNullableRecord( expected: object | null, actual: object | undefined ) { return expected === null ? actual === undefined : actual !== undefined && sameRecord(expected, actual); } function sameRecord(expected: object, actual: object) { const expectedEntries = Object.entries(expected); const actualRecord = actual as Record; return ( expectedEntries.length === Object.keys(actual).length && expectedEntries.every( ([key, value]) => actualRecord[key] === value ) ); }