Add circuit group commands

This commit is contained in:
2026-07-30 19:46:55 +02:00
parent 69b020339e
commit 1250f9a6af
16 changed files with 864 additions and 5 deletions
@@ -0,0 +1,189 @@
import { and, eq } from "drizzle-orm";
import {
assertCircuitGroupDeleteProjectCommand,
assertCircuitGroupInsertProjectCommand,
assertCircuitGroupUpdateProjectCommand,
circuitGroupDeleteCommandType,
circuitGroupInsertCommandType,
circuitGroupUpdateCommandType,
createCircuitGroupDeleteProjectCommand,
createCircuitGroupInsertProjectCommand,
createCircuitGroupUpdateProjectCommand,
type CircuitGroupSnapshot,
type CircuitGroupStructureProjectCommand,
} from "../../domain/models/circuit-group-structure-project-command.model.js";
import type {
CircuitGroupStructureProjectCommandStore,
ExecuteCircuitGroupStructureCommandInput,
} from "../../domain/ports/circuit-group-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 { distributionBoardComponents } from "../schema/distribution-board-components.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
export class CircuitGroupStructureProjectCommandRepository
implements CircuitGroupStructureProjectCommandStore
{
constructor(private readonly database: AppDatabase) {}
execute(input: ExecuteCircuitGroupStructureCommandInput) {
return executeProjectCommandTransaction(
this.database,
input,
(tx) => this.applyCommand(tx, input.projectId, input.command)
);
}
private applyCommand(
database: AppDatabase,
projectId: string,
command: CircuitGroupStructureProjectCommand
): CircuitGroupStructureProjectCommand {
if (command.type === circuitGroupInsertCommandType) {
assertCircuitGroupInsertProjectCommand(command);
this.insert(database, projectId, command.payload.snapshot);
return createCircuitGroupDeleteProjectCommand(
command.payload.snapshot
);
}
if (command.type === circuitGroupDeleteCommandType) {
assertCircuitGroupDeleteProjectCommand(command);
this.delete(database, projectId, command.payload.snapshot);
return createCircuitGroupInsertProjectCommand(
command.payload.snapshot
);
}
if (command.type === circuitGroupUpdateCommandType) {
assertCircuitGroupUpdateProjectCommand(command);
this.update(
database,
projectId,
command.payload.expected,
command.payload.target
);
return createCircuitGroupUpdateProjectCommand(
command.payload.target,
command.payload.expected
);
}
throw new Error("Unsupported circuit-group structure command.");
}
private insert(
database: AppDatabase,
projectId: string,
snapshot: CircuitGroupSnapshot
) {
this.assertListOwnership(database, projectId, snapshot.circuitListId);
const existing = database
.select({ id: circuitSections.id })
.from(circuitSections)
.where(eq(circuitSections.id, snapshot.id))
.get();
if (existing) {
throw new Error("Circuit-group id already exists.");
}
database.insert(circuitSections).values(snapshot).run();
}
private delete(
database: AppDatabase,
projectId: string,
snapshot: CircuitGroupSnapshot
) {
this.assertListOwnership(database, projectId, snapshot.circuitListId);
const current = this.getCurrent(database, snapshot.id);
if (!current || !sameRecord(snapshot, current)) {
throw new Error("Circuit group changed before deletion.");
}
const circuit = database
.select({ id: circuits.id })
.from(circuits)
.where(eq(circuits.sectionId, snapshot.id))
.get();
const component = database
.select({ id: distributionBoardComponents.id })
.from(distributionBoardComponents)
.where(eq(distributionBoardComponents.sectionId, snapshot.id))
.get();
if (circuit || component) {
throw new Error(
"Only empty circuit groups can be deleted by this command."
);
}
const deleted = database
.delete(circuitSections)
.where(
and(
eq(circuitSections.id, snapshot.id),
eq(circuitSections.circuitListId, snapshot.circuitListId)
)
)
.run();
if (deleted.changes !== 1) {
throw new Error("Circuit group could not be deleted.");
}
}
private update(
database: AppDatabase,
projectId: string,
expected: CircuitGroupSnapshot,
target: CircuitGroupSnapshot
) {
this.assertListOwnership(database, projectId, expected.circuitListId);
const current = this.getCurrent(database, expected.id);
if (!current || !sameRecord(expected, current)) {
throw new Error("Circuit group changed before update.");
}
const updated = database
.update(circuitSections)
.set({ displayName: target.displayName })
.where(
and(
eq(circuitSections.id, expected.id),
eq(circuitSections.circuitListId, expected.circuitListId)
)
)
.run();
if (updated.changes !== 1) {
throw new Error("Circuit group could not be updated.");
}
}
private assertListOwnership(
database: AppDatabase,
projectId: string,
circuitListId: string
) {
const list = database
.select({ projectId: circuitLists.projectId })
.from(circuitLists)
.where(eq(circuitLists.id, circuitListId))
.get();
if (!list || list.projectId !== projectId) {
throw new Error("Circuit group does not belong to project.");
}
}
private getCurrent(database: AppDatabase, id: string) {
return database
.select()
.from(circuitSections)
.where(eq(circuitSections.id, id))
.get();
}
}
function sameRecord(expected: object, actual: object) {
const expectedEntries = Object.entries(expected);
const actualRecord = actual as Record<string, unknown>;
return (
expectedEntries.length === Object.keys(actual).length &&
expectedEntries.every(
([key, value]) => actualRecord[key] === value
)
);
}