import { and, eq } from "drizzle-orm"; import { assertCircuitSectionReorderProjectCommand, circuitSectionReorderCommandType, createCircuitSectionReorderProjectCommand, type CircuitSectionReorderAssignment, type CircuitSectionReorderProjectCommand, } from "../../domain/models/circuit-section-reorder-project-command.model.js"; import { assertCircuitSectionsReorderProjectCommand, circuitSectionsReorderCommandType, createCircuitSectionsReorderProjectCommand, type CircuitSectionsReorderProjectCommand, type CircuitSectionsReorderSection, } from "../../domain/models/circuit-sections-reorder-project-command.model.js"; import type { CircuitSectionReorderProjectCommandStore, ExecuteCircuitSectionReorderCommandInput, } from "../../domain/ports/circuit-section-reorder-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 { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js"; type ReorderHistoryCommand = | CircuitSectionReorderProjectCommand | CircuitSectionsReorderProjectCommand; export class CircuitSectionReorderProjectCommandRepository implements CircuitSectionReorderProjectCommandStore { constructor(private readonly database: AppDatabase) {} execute(input: ExecuteCircuitSectionReorderCommandInput) { this.assertSupportedCommand(input.command); return executeProjectCommandTransaction( this.database, input, (tx) => this.applyCommand(tx, input) ); } private applyCommand( tx: AppDatabase, input: ExecuteCircuitSectionReorderCommandInput ) { const commandSections = this.getCommandSections(input.command); const inverseSections = commandSections.map((section) => { this.assertCompleteCurrentSection( tx, input.projectId, section ); return { sectionId: section.sectionId, assignments: section.assignments.map((assignment) => ({ circuitId: assignment.circuitId, expectedSortOrder: assignment.targetSortOrder, targetSortOrder: assignment.expectedSortOrder, })), }; }); for (const section of commandSections) { this.applyAssignments(tx, section); } const inverse = input.command.type === circuitSectionReorderCommandType ? createCircuitSectionReorderProjectCommand( inverseSections[0].sectionId, inverseSections[0].assignments ) : createCircuitSectionsReorderProjectCommand( inverseSections ); return inverse; } private assertSupportedCommand( command: ReorderHistoryCommand ) { if (command.type === circuitSectionReorderCommandType) { assertCircuitSectionReorderProjectCommand(command); return; } if (command.type === circuitSectionsReorderCommandType) { assertCircuitSectionsReorderProjectCommand(command); return; } throw new Error("Unsupported circuit section reorder command."); } private getCommandSections( command: ReorderHistoryCommand ): CircuitSectionsReorderSection[] { if (command.type === circuitSectionReorderCommandType) { return [ { sectionId: command.payload.sectionId, assignments: command.payload.assignments, }, ]; } return command.payload.sections; } private assertCompleteCurrentSection( database: AppDatabase, projectId: string, sectionCommand: CircuitSectionsReorderSection ) { const section = database .select({ id: circuitSections.id, circuitListId: circuitSections.circuitListId, projectId: circuitLists.projectId, }) .from(circuitSections) .innerJoin( circuitLists, eq(circuitLists.id, circuitSections.circuitListId) ) .where(eq(circuitSections.id, sectionCommand.sectionId)) .get(); if (!section || section.projectId !== projectId) { throw new Error( "Circuit section does not belong to project." ); } const persistedCircuits = database .select({ id: circuits.id, circuitListId: circuits.circuitListId, sortOrder: circuits.sortOrder, }) .from(circuits) .where(eq(circuits.sectionId, section.id)) .all(); const assignments = sectionCommand.assignments; if ( persistedCircuits.length !== assignments.length || persistedCircuits.some( (circuit) => circuit.circuitListId !== section.circuitListId ) ) { throw new Error( "Circuit reorder must include every circuit in the section." ); } const circuitsById = new Map( persistedCircuits.map((circuit) => [circuit.id, circuit]) ); for (const assignment of assignments) { const circuit = circuitsById.get(assignment.circuitId); if ( !circuit || circuit.sortOrder !== assignment.expectedSortOrder ) { throw new Error( "Circuit changed before section reorder execution." ); } } } private applyAssignments( database: AppDatabase, sectionCommand: { sectionId: string; assignments: CircuitSectionReorderAssignment[]; } ) { for (const assignment of sectionCommand.assignments) { if ( assignment.expectedSortOrder === assignment.targetSortOrder ) { continue; } const updated = database .update(circuits) .set({ sortOrder: assignment.targetSortOrder }) .where( and( eq(circuits.id, assignment.circuitId), eq(circuits.sectionId, sectionCommand.sectionId), eq(circuits.sortOrder, assignment.expectedSortOrder) ) ) .run(); if (updated.changes !== 1) { throw new Error( "Circuit changed during section reorder execution." ); } } } }