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

98 lines
3.1 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import {
assertCircuitProtectionUpdateProjectCommand,
createCircuitProtectionUpdateProjectCommand,
type CircuitProtectionSnapshot,
} from "../../domain/models/circuit-protection-project-command.model.js";
import type {
CircuitProtectionProjectCommandStore,
ExecuteCircuitProtectionCommandInput,
} from "../../domain/ports/circuit-protection-project-command.store.js";
import type { AppDatabase } from "../database-context.js";
import { circuitLists } from "../schema/circuit-lists.js";
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
import { circuits } from "../schema/circuits.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
export class CircuitProtectionProjectCommandRepository
implements CircuitProtectionProjectCommandStore
{
constructor(private readonly database: AppDatabase) {}
execute(input: ExecuteCircuitProtectionCommandInput) {
return executeProjectCommandTransaction(this.database, input, (tx) => {
assertCircuitProtectionUpdateProjectCommand(input.command);
const { circuitId, expected, target } = input.command.payload;
if (input.source === "user" && target === null) {
throw new Error("User commands cannot remove circuit protection.");
}
this.assertOwnership(tx, input.projectId, circuitId);
const actual = tx
.select()
.from(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.get();
if (!sameNullableSnapshot(expected, actual)) {
throw new Error("Circuit protection changed before update.");
}
if (target === null) {
tx.delete(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.run();
} else if (actual) {
tx.update(circuitProtectionDevices)
.set(target)
.where(eq(circuitProtectionDevices.circuitId, circuitId))
.run();
} else {
tx.insert(circuitProtectionDevices).values(target).run();
}
return createCircuitProtectionUpdateProjectCommand(
circuitId,
target,
expected
);
});
}
private assertOwnership(
database: AppDatabase,
projectId: string,
circuitId: string
) {
const circuit = database
.select({ projectId: circuitLists.projectId })
.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 protection does not belong to project.");
}
}
}
function sameNullableSnapshot(
expected: CircuitProtectionSnapshot | null,
actual: typeof circuitProtectionDevices.$inferSelect | undefined
) {
if (expected === null) {
return actual === undefined;
}
if (!actual) {
return false;
}
return Object.entries(expected).every(
([key, value]) =>
(actual as Record<string, unknown>)[key] === value
);
}