import { and, asc, eq, inArray } from "drizzle-orm"; import { assertDistributionBoardDeleteProjectCommand, assertDistributionBoardInsertProjectCommand, createDistributionBoardDeleteProjectCommand, createDistributionBoardInsertProjectCommand, distributionBoardDeleteCommandType, distributionBoardInsertCommandType, type DistributionBoardStructureProjectCommand, type DistributionBoardStructureSnapshot, } from "../../domain/models/distribution-board-structure-project-command.model.js"; import type { DistributionBoardStructureProjectCommandStore, ExecuteDistributionBoardStructureCommandInput, } from "../../domain/ports/distribution-board-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 { circuits } from "../schema/circuits.js"; import { distributionBoards } from "../schema/distribution-boards.js"; import { projects } from "../schema/projects.js"; import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; export class DistributionBoardStructureProjectCommandRepository implements DistributionBoardStructureProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteDistributionBoardStructureCommandInput) { return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand(tx, input.projectId, input.command) ); } private applyCommand( database: AppDatabase, projectId: string, command: DistributionBoardStructureProjectCommand ): DistributionBoardStructureProjectCommand { if (command.type === distributionBoardInsertCommandType) { assertDistributionBoardInsertProjectCommand(command); return this.insert( database, projectId, command.payload.structure ); } if (command.type === distributionBoardDeleteCommandType) { assertDistributionBoardDeleteProjectCommand(command); return this.delete( database, projectId, command.payload.structure ); } throw new Error("Unsupported distribution-board structure command."); } private insert( database: AppDatabase, projectId: string, structure: DistributionBoardStructureSnapshot ) { if ( structure.distributionBoard.projectId !== projectId || structure.circuitList.projectId !== projectId ) { throw new Error( "Distribution-board structure does not belong to project." ); } const project = database .select({ id: projects.id }) .from(projects) .where(eq(projects.id, projectId)) .get(); if (!project) { throw new Error("Project not found."); } const existingBoard = database .select({ id: distributionBoards.id }) .from(distributionBoards) .where(eq(distributionBoards.id, structure.distributionBoard.id)) .get(); const existingList = database .select({ id: circuitLists.id }) .from(circuitLists) .where(eq(circuitLists.id, structure.circuitList.id)) .get(); const existingSection = database .select({ id: circuitSections.id }) .from(circuitSections) .where( inArray( circuitSections.id, structure.sections.map((section) => section.id) ) ) .limit(1) .get(); if (existingBoard || existingList || existingSection) { throw new Error("Distribution-board structure id already exists."); } database .insert(distributionBoards) .values(structure.distributionBoard) .run(); database.insert(circuitLists).values(structure.circuitList).run(); database.insert(circuitSections).values(structure.sections).run(); return createDistributionBoardDeleteProjectCommand(structure); } private delete( database: AppDatabase, projectId: string, structure: DistributionBoardStructureSnapshot ) { const board = database .select() .from(distributionBoards) .where(eq(distributionBoards.id, structure.distributionBoard.id)) .get(); const list = database .select() .from(circuitLists) .where(eq(circuitLists.id, structure.circuitList.id)) .get(); const sections = database .select() .from(circuitSections) .where( eq(circuitSections.circuitListId, structure.circuitList.id) ) .orderBy( asc(circuitSections.sortOrder), asc(circuitSections.id) ) .all(); if ( !board || !list || board.projectId !== projectId || !structureMatches(structure, { board, list, sections }) ) { throw new Error( "Distribution-board structure changed before deletion." ); } const populatedCircuit = database .select({ id: circuits.id }) .from(circuits) .where(eq(circuits.circuitListId, structure.circuitList.id)) .limit(1) .get(); if (populatedCircuit) { throw new Error( "A populated distribution board cannot be removed by history." ); } const deleted = database .delete(distributionBoards) .where( and( eq( distributionBoards.id, structure.distributionBoard.id ), eq(distributionBoards.projectId, projectId) ) ) .run(); if (deleted.changes !== 1) { throw new Error("Distribution board could not be deleted."); } return createDistributionBoardInsertProjectCommand(structure); } } function structureMatches( expected: DistributionBoardStructureSnapshot, actual: { board: typeof distributionBoards.$inferSelect; list: typeof circuitLists.$inferSelect; sections: Array; } ) { if ( !sameRecord(expected.distributionBoard, actual.board) || !sameRecord(expected.circuitList, actual.list) || expected.sections.length !== actual.sections.length ) { return false; } const expectedSections = [...expected.sections].sort( (left, right) => left.sortOrder - right.sortOrder || left.id.localeCompare(right.id) ); return expectedSections.every((section, index) => sameRecord(section, actual.sections[index]) ); } function sameRecord( expected: Record, actual: Record ) { const expectedEntries = Object.entries(expected); return ( expectedEntries.length === Object.keys(actual).length && expectedEntries.every( ([key, value]) => actual[key] === value ) ); }