leistungsbilanz-ts/src/db/repositories/circuit-device-row-structure-project-command.repository.ts

195 lines
5.8 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import {
assertCircuitDeviceRowDeleteProjectCommand,
assertCircuitDeviceRowInsertProjectCommand,
circuitDeviceRowDeleteCommandType,
circuitDeviceRowInsertCommandType,
createCircuitDeviceRowDeleteProjectCommand,
createCircuitDeviceRowInsertProjectCommand,
type CircuitDeviceRowSnapshot,
type CircuitDeviceRowStructureProjectCommand,
} from "../../domain/models/circuit-device-row-structure-project-command.model.js";
import type {
CircuitDeviceRowStructureProjectCommandStore,
ExecuteCircuitDeviceRowStructureCommandInput,
} from "../../domain/ports/circuit-device-row-structure-project-command.store.js";
import type { AppDatabase } from "../database-context.js";
import { isElectricalPhaseType } from "../../domain/services/project-voltage.service.js";
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
import { circuitLists } from "../schema/circuit-lists.js";
import { circuits } from "../schema/circuits.js";
import {
assertCircuitDeviceRowReferencesInProject,
toCircuitDeviceRowInsertValues,
toCircuitDeviceRowSnapshot,
} from "./circuit-device-row-structure.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
export class CircuitDeviceRowStructureProjectCommandRepository
implements CircuitDeviceRowStructureProjectCommandStore
{
constructor(private readonly database: AppDatabase) {}
execute(input: ExecuteCircuitDeviceRowStructureCommandInput) {
return executeProjectCommandTransaction(
this.database,
input,
(tx) =>
this.applyCommand(
tx,
input.projectId,
input.source,
input.command
)
);
}
private applyCommand(
database: AppDatabase,
projectId: string,
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
command: CircuitDeviceRowStructureProjectCommand
): CircuitDeviceRowStructureProjectCommand {
if (command.type === circuitDeviceRowInsertCommandType) {
assertCircuitDeviceRowInsertProjectCommand(command);
return this.insert(
database,
projectId,
source,
command.payload.row
);
}
if (command.type === circuitDeviceRowDeleteCommandType) {
assertCircuitDeviceRowDeleteProjectCommand(command);
return this.delete(
database,
projectId,
command.payload.rowId,
command.payload.expectedCircuitId
);
}
throw new Error("Unsupported circuit device-row structure command.");
}
private insert(
database: AppDatabase,
projectId: string,
source: ExecuteCircuitDeviceRowStructureCommandInput["source"],
row: CircuitDeviceRowSnapshot
) {
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
throw new Error("Device-row phase type is invalid.");
}
this.assertCircuitInProject(database, projectId, row.circuitId);
assertCircuitDeviceRowReferencesInProject(database, projectId, row);
const existing = database
.select({ id: circuitDeviceRows.id })
.from(circuitDeviceRows)
.where(eq(circuitDeviceRows.id, row.id))
.get();
if (existing) {
throw new Error("Circuit device-row id already exists.");
}
database
.insert(circuitDeviceRows)
.values(toCircuitDeviceRowInsertValues(row))
.run();
const circuitUpdate = database
.update(circuits)
.set({ isReserve: 0 })
.where(eq(circuits.id, row.circuitId))
.run();
if (circuitUpdate.changes !== 1) {
throw new Error("Circuit changed before device-row insertion.");
}
updateDerivedCircuitVoltage(database, projectId, row.circuitId);
return createCircuitDeviceRowDeleteProjectCommand(
row.id,
row.circuitId
);
}
private delete(
database: AppDatabase,
projectId: string,
rowId: string,
expectedCircuitId: string
) {
const row = database
.select()
.from(circuitDeviceRows)
.where(eq(circuitDeviceRows.id, rowId))
.get();
if (!row || row.circuitId !== expectedCircuitId) {
throw new Error(
"Circuit device row changed before command execution."
);
}
this.assertCircuitInProject(database, projectId, row.circuitId);
const result = database
.delete(circuitDeviceRows)
.where(
and(
eq(circuitDeviceRows.id, rowId),
eq(circuitDeviceRows.circuitId, expectedCircuitId)
)
)
.run();
if (result.changes !== 1) {
throw new Error("Circuit device row could not be deleted.");
}
const remainingRow = database
.select({ id: circuitDeviceRows.id })
.from(circuitDeviceRows)
.where(eq(circuitDeviceRows.circuitId, expectedCircuitId))
.limit(1)
.get();
const circuitUpdate = database
.update(circuits)
.set({ isReserve: remainingRow ? 0 : 1 })
.where(eq(circuits.id, expectedCircuitId))
.run();
if (circuitUpdate.changes !== 1) {
throw new Error("Circuit changed before device-row deletion.");
}
updateDerivedCircuitVoltage(
database,
projectId,
expectedCircuitId
);
return createCircuitDeviceRowInsertProjectCommand(
toCircuitDeviceRowSnapshot(row)
);
}
private assertCircuitInProject(
database: AppDatabase,
projectId: string,
circuitId: string
) {
const circuit = database
.select({ id: circuits.id })
.from(circuits)
.innerJoin(
circuitLists,
eq(circuitLists.id, circuits.circuitListId)
)
.where(
and(
eq(circuits.id, circuitId),
eq(circuitLists.projectId, projectId)
)
)
.get();
if (!circuit) {
throw new Error("Circuit does not belong to project.");
}
}
}