Files
leistungsbilanz-ts/src/db/repositories/circuit-section-reorder-project-command.repository.ts
T

203 lines
6.0 KiB
TypeScript

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."
);
}
}
}
}