193 lines
5.6 KiB
TypeScript
193 lines
5.6 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import {
|
|
assertCircuitDeviceRowUpdateProjectCommand,
|
|
createCircuitDeviceRowUpdateProjectCommand,
|
|
type CircuitDeviceRowUpdateField,
|
|
type CircuitDeviceRowUpdatePatch,
|
|
type CircuitDeviceRowUpdateValues,
|
|
} from "../../domain/models/circuit-device-row-project-command.model.js";
|
|
import type {
|
|
CircuitDeviceRowProjectCommandStore,
|
|
ExecuteCircuitDeviceRowUpdateCommandInput,
|
|
} from "../../domain/ports/circuit-device-row-project-command.store.js";
|
|
import { deriveOverriddenFieldsForLocalEdit } from "../../domain/services/project-device-overrides.js";
|
|
import type { AppDatabase } from "../database-context.js";
|
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
|
import { circuitLists } from "../schema/circuit-lists.js";
|
|
import { circuits } from "../schema/circuits.js";
|
|
import { projectDevices } from "../schema/project-devices.js";
|
|
import { rooms } from "../schema/rooms.js";
|
|
import {
|
|
toCircuitDeviceRowPatchValues,
|
|
type CircuitDeviceRowPatchInput,
|
|
} from "./circuit-device-row.persistence.js";
|
|
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
|
|
|
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
|
|
|
export class CircuitDeviceRowProjectCommandRepository
|
|
implements CircuitDeviceRowProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
executeUpdate(input: ExecuteCircuitDeviceRowUpdateCommandInput) {
|
|
assertCircuitDeviceRowUpdateProjectCommand(input.command);
|
|
|
|
return executeProjectCommandTransactionWithAppliedForward(
|
|
this.database,
|
|
input,
|
|
(tx) => this.applyCommand(tx, input)
|
|
);
|
|
}
|
|
|
|
private applyCommand(
|
|
tx: AppDatabase,
|
|
input: ExecuteCircuitDeviceRowUpdateCommandInput
|
|
) {
|
|
const current = tx
|
|
.select()
|
|
.from(circuitDeviceRows)
|
|
.where(eq(circuitDeviceRows.id, input.command.payload.rowId))
|
|
.get();
|
|
if (!current) {
|
|
throw new Error("Invalid device row id.");
|
|
}
|
|
|
|
const circuit = tx
|
|
.select({ circuitListId: circuits.circuitListId })
|
|
.from(circuits)
|
|
.where(eq(circuits.id, current.circuitId))
|
|
.get();
|
|
const owningList = circuit
|
|
? tx
|
|
.select({ id: circuitLists.id })
|
|
.from(circuitLists)
|
|
.where(
|
|
and(
|
|
eq(circuitLists.id, circuit.circuitListId),
|
|
eq(circuitLists.projectId, input.projectId)
|
|
)
|
|
)
|
|
.get()
|
|
: null;
|
|
if (!owningList) {
|
|
throw new Error("Circuit device row does not belong to project.");
|
|
}
|
|
|
|
const patch = Object.fromEntries(
|
|
input.command.payload.changes.map((change) => [
|
|
change.field,
|
|
change.value,
|
|
])
|
|
) as CircuitDeviceRowPatchInput;
|
|
this.assertLinkedProjectDevice(tx, input.projectId, patch);
|
|
this.assertRoom(tx, input.projectId, patch);
|
|
|
|
const overriddenFields = deriveOverriddenFieldsForLocalEdit(
|
|
current,
|
|
patch
|
|
);
|
|
if (
|
|
overriddenFields !== undefined &&
|
|
patch.overriddenFields === undefined
|
|
) {
|
|
patch.overriddenFields = overriddenFields;
|
|
}
|
|
|
|
const appliedForward = createCircuitDeviceRowUpdateProjectCommand(
|
|
current.id,
|
|
patch as CircuitDeviceRowUpdatePatch
|
|
);
|
|
const inversePatch = Object.fromEntries(
|
|
appliedForward.payload.changes.map((change) => [
|
|
change.field,
|
|
getCircuitDeviceRowFieldValue(current, change.field),
|
|
])
|
|
) as CircuitDeviceRowUpdatePatch;
|
|
const inverse = createCircuitDeviceRowUpdateProjectCommand(
|
|
current.id,
|
|
inversePatch
|
|
);
|
|
|
|
const update = tx
|
|
.update(circuitDeviceRows)
|
|
.set(toCircuitDeviceRowPatchValues(patch))
|
|
.where(eq(circuitDeviceRows.id, current.id))
|
|
.run();
|
|
if (update.changes !== 1) {
|
|
throw new Error("Circuit device row changed before command execution.");
|
|
}
|
|
if (patch.phaseType !== undefined) {
|
|
updateDerivedCircuitVoltage(
|
|
tx,
|
|
input.projectId,
|
|
current.circuitId
|
|
);
|
|
}
|
|
|
|
return {
|
|
forward: appliedForward,
|
|
inverse,
|
|
};
|
|
}
|
|
|
|
private assertLinkedProjectDevice(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
patch: CircuitDeviceRowPatchInput
|
|
) {
|
|
if (
|
|
!Object.prototype.hasOwnProperty.call(patch, "linkedProjectDeviceId") ||
|
|
patch.linkedProjectDeviceId === null ||
|
|
patch.linkedProjectDeviceId === undefined
|
|
) {
|
|
return;
|
|
}
|
|
const device = database
|
|
.select({ id: projectDevices.id })
|
|
.from(projectDevices)
|
|
.where(
|
|
and(
|
|
eq(projectDevices.id, patch.linkedProjectDeviceId),
|
|
eq(projectDevices.projectId, projectId)
|
|
)
|
|
)
|
|
.get();
|
|
if (!device) {
|
|
throw new Error("Invalid linked project device id.");
|
|
}
|
|
}
|
|
|
|
private assertRoom(
|
|
database: AppDatabase,
|
|
projectId: string,
|
|
patch: CircuitDeviceRowPatchInput
|
|
) {
|
|
if (
|
|
!Object.prototype.hasOwnProperty.call(patch, "roomId") ||
|
|
patch.roomId === null ||
|
|
patch.roomId === undefined
|
|
) {
|
|
return;
|
|
}
|
|
const room = database
|
|
.select({ id: rooms.id })
|
|
.from(rooms)
|
|
.where(and(eq(rooms.id, patch.roomId), eq(rooms.projectId, projectId)))
|
|
.get();
|
|
if (!room) {
|
|
throw new Error("Invalid room id.");
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCircuitDeviceRowFieldValue<
|
|
TField extends CircuitDeviceRowUpdateField,
|
|
>(
|
|
row: CircuitDeviceRow,
|
|
field: TField
|
|
): CircuitDeviceRowUpdateValues[TField] {
|
|
return row[field] as unknown as CircuitDeviceRowUpdateValues[TField];
|
|
}
|