264 lines
8.3 KiB
TypeScript
264 lines
8.3 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import {
|
|
assertDistributionBoardComponentDeleteProjectCommand,
|
|
assertDistributionBoardComponentInsertProjectCommand,
|
|
assertDistributionBoardComponentUpdateProjectCommand,
|
|
createDistributionBoardComponentDeleteProjectCommand,
|
|
createDistributionBoardComponentInsertProjectCommand,
|
|
createDistributionBoardComponentUpdateProjectCommand,
|
|
distributionBoardComponentDeleteCommandType,
|
|
distributionBoardComponentInsertCommandType,
|
|
distributionBoardComponentUpdateCommandType,
|
|
type DistributionBoardComponentSnapshot,
|
|
type DistributionBoardComponentStructureProjectCommand,
|
|
} from "../../domain/models/distribution-board-component-structure-project-command.model.js";
|
|
import type {
|
|
DistributionBoardComponentStructureProjectCommandStore,
|
|
ExecuteDistributionBoardComponentStructureCommandInput,
|
|
} from "../../domain/ports/distribution-board-component-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 { distributionBoardComponentProtectionDevices } from "../schema/distribution-board-component-protection-devices.js";
|
|
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
|
|
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
|
|
|
export class DistributionBoardComponentStructureProjectCommandRepository
|
|
implements DistributionBoardComponentStructureProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
execute(
|
|
input: ExecuteDistributionBoardComponentStructureCommandInput
|
|
) {
|
|
return executeProjectCommandTransaction(
|
|
this.database,
|
|
input,
|
|
(tx) =>
|
|
this.applyCommand(
|
|
tx,
|
|
input.projectId,
|
|
input.command
|
|
)
|
|
);
|
|
}
|
|
|
|
private applyCommand(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
command: DistributionBoardComponentStructureProjectCommand
|
|
): DistributionBoardComponentStructureProjectCommand {
|
|
if (command.type === distributionBoardComponentInsertCommandType) {
|
|
assertDistributionBoardComponentInsertProjectCommand(command);
|
|
this.insert(database, projectId, command.payload.snapshot);
|
|
return createDistributionBoardComponentDeleteProjectCommand(
|
|
command.payload.snapshot
|
|
);
|
|
}
|
|
if (command.type === distributionBoardComponentDeleteCommandType) {
|
|
assertDistributionBoardComponentDeleteProjectCommand(command);
|
|
this.delete(database, projectId, command.payload.snapshot);
|
|
return createDistributionBoardComponentInsertProjectCommand(
|
|
command.payload.snapshot
|
|
);
|
|
}
|
|
if (command.type === distributionBoardComponentUpdateCommandType) {
|
|
assertDistributionBoardComponentUpdateProjectCommand(command);
|
|
this.update(
|
|
database,
|
|
projectId,
|
|
command.payload.expected,
|
|
command.payload.target
|
|
);
|
|
return createDistributionBoardComponentUpdateProjectCommand(
|
|
command.payload.target,
|
|
command.payload.expected
|
|
);
|
|
}
|
|
throw new Error(
|
|
"Unsupported distribution-board component structure command."
|
|
);
|
|
}
|
|
|
|
private insert(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
snapshot: DistributionBoardComponentSnapshot
|
|
) {
|
|
this.assertOwnership(database, projectId, snapshot);
|
|
const existing = database
|
|
.select({ id: distributionBoardComponents.id })
|
|
.from(distributionBoardComponents)
|
|
.where(eq(distributionBoardComponents.id, snapshot.component.id))
|
|
.get();
|
|
if (existing) {
|
|
throw new Error("Distribution-board component id already exists.");
|
|
}
|
|
database
|
|
.insert(distributionBoardComponents)
|
|
.values(snapshot.component)
|
|
.run();
|
|
if (snapshot.protectionDevice !== null) {
|
|
database
|
|
.insert(distributionBoardComponentProtectionDevices)
|
|
.values(snapshot.protectionDevice)
|
|
.run();
|
|
}
|
|
}
|
|
|
|
private delete(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
snapshot: DistributionBoardComponentSnapshot
|
|
) {
|
|
this.assertOwnership(database, projectId, snapshot);
|
|
const component = database
|
|
.select()
|
|
.from(distributionBoardComponents)
|
|
.where(eq(distributionBoardComponents.id, snapshot.component.id))
|
|
.get();
|
|
const protection = database
|
|
.select()
|
|
.from(distributionBoardComponentProtectionDevices)
|
|
.where(
|
|
eq(
|
|
distributionBoardComponentProtectionDevices.componentId,
|
|
snapshot.component.id
|
|
)
|
|
)
|
|
.get();
|
|
if (
|
|
!component ||
|
|
!sameRecord(snapshot.component, component) ||
|
|
!sameNullableRecord(snapshot.protectionDevice, protection)
|
|
) {
|
|
throw new Error(
|
|
"Distribution-board component changed before deletion."
|
|
);
|
|
}
|
|
const deleted = database
|
|
.delete(distributionBoardComponents)
|
|
.where(
|
|
and(
|
|
eq(distributionBoardComponents.id, component.id),
|
|
eq(
|
|
distributionBoardComponents.circuitListId,
|
|
snapshot.component.circuitListId
|
|
)
|
|
)
|
|
)
|
|
.run();
|
|
if (deleted.changes !== 1) {
|
|
throw new Error(
|
|
"Distribution-board component could not be deleted."
|
|
);
|
|
}
|
|
}
|
|
|
|
private update(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
expected: DistributionBoardComponentSnapshot,
|
|
target: DistributionBoardComponentSnapshot
|
|
) {
|
|
this.assertOwnership(database, projectId, expected);
|
|
this.assertOwnership(database, projectId, target);
|
|
const component = database
|
|
.select()
|
|
.from(distributionBoardComponents)
|
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
|
.get();
|
|
const protection = database
|
|
.select()
|
|
.from(distributionBoardComponentProtectionDevices)
|
|
.where(
|
|
eq(
|
|
distributionBoardComponentProtectionDevices.componentId,
|
|
expected.component.id
|
|
)
|
|
)
|
|
.get();
|
|
if (
|
|
!component ||
|
|
!sameRecord(expected.component, component) ||
|
|
!sameNullableRecord(expected.protectionDevice, protection)
|
|
) {
|
|
throw new Error(
|
|
"Distribution-board component changed before update."
|
|
);
|
|
}
|
|
database
|
|
.update(distributionBoardComponents)
|
|
.set(target.component)
|
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
|
.run();
|
|
if (
|
|
expected.protectionDevice !== null &&
|
|
target.protectionDevice !== null
|
|
) {
|
|
database
|
|
.update(distributionBoardComponentProtectionDevices)
|
|
.set(target.protectionDevice)
|
|
.where(
|
|
eq(
|
|
distributionBoardComponentProtectionDevices.componentId,
|
|
expected.component.id
|
|
)
|
|
)
|
|
.run();
|
|
}
|
|
}
|
|
|
|
private assertOwnership(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
snapshot: DistributionBoardComponentSnapshot
|
|
) {
|
|
const list = database
|
|
.select({ projectId: circuitLists.projectId })
|
|
.from(circuitLists)
|
|
.where(eq(circuitLists.id, snapshot.component.circuitListId))
|
|
.get();
|
|
if (!list || list.projectId !== projectId) {
|
|
throw new Error(
|
|
"Distribution-board component circuit list does not belong to project."
|
|
);
|
|
}
|
|
if (snapshot.component.sectionId !== null) {
|
|
const section = database
|
|
.select({ circuitListId: circuitSections.circuitListId })
|
|
.from(circuitSections)
|
|
.where(eq(circuitSections.id, snapshot.component.sectionId))
|
|
.get();
|
|
if (
|
|
!section ||
|
|
section.circuitListId !== snapshot.component.circuitListId
|
|
) {
|
|
throw new Error(
|
|
"Distribution-board component section does not belong to circuit list."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function sameNullableRecord(
|
|
expected: object | null,
|
|
actual: object | undefined
|
|
) {
|
|
return expected === null
|
|
? actual === undefined
|
|
: actual !== undefined && sameRecord(expected, actual);
|
|
}
|
|
|
|
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
|
|
)
|
|
);
|
|
}
|