165 lines
4.6 KiB
TypeScript
165 lines
4.6 KiB
TypeScript
import { and, eq, inArray } from "drizzle-orm";
|
|
import {
|
|
assertProjectDeviceRowSyncProjectCommand,
|
|
createProjectDeviceRowSyncProjectCommand,
|
|
invertProjectDeviceRowSyncOperation,
|
|
projectDeviceSyncRowSnapshotFields,
|
|
type ProjectDeviceSyncRowSnapshot,
|
|
} from "../../domain/models/project-device-row-sync-project-command.model.js";
|
|
import type {
|
|
ExecuteProjectDeviceRowSyncCommandInput,
|
|
ProjectDeviceRowSyncProjectCommandStore,
|
|
} from "../../domain/ports/project-device-row-sync-project-command.store.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 { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
|
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
|
|
|
|
type CircuitDeviceRow = typeof circuitDeviceRows.$inferSelect;
|
|
|
|
export class ProjectDeviceRowSyncProjectCommandRepository
|
|
implements ProjectDeviceRowSyncProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
execute(input: ExecuteProjectDeviceRowSyncCommandInput) {
|
|
assertProjectDeviceRowSyncProjectCommand(input.command);
|
|
|
|
return executeProjectCommandTransaction(
|
|
this.database,
|
|
input,
|
|
(tx) => this.applyCommand(tx, input)
|
|
);
|
|
}
|
|
|
|
private applyCommand(
|
|
tx: AppDatabase,
|
|
input: ExecuteProjectDeviceRowSyncCommandInput
|
|
) {
|
|
const projectDevice = tx
|
|
.select({ id: projectDevices.id })
|
|
.from(projectDevices)
|
|
.where(
|
|
and(
|
|
eq(
|
|
projectDevices.id,
|
|
input.command.payload.projectDeviceId
|
|
),
|
|
eq(projectDevices.projectId, input.projectId)
|
|
)
|
|
)
|
|
.get();
|
|
if (!projectDevice) {
|
|
throw new Error(
|
|
"Project device does not belong to project."
|
|
);
|
|
}
|
|
|
|
const rowIds = input.command.payload.rows.map(
|
|
(row) => row.rowId
|
|
);
|
|
const persistedRows = tx
|
|
.select({
|
|
row: circuitDeviceRows,
|
|
projectId: circuitLists.projectId,
|
|
})
|
|
.from(circuitDeviceRows)
|
|
.innerJoin(
|
|
circuits,
|
|
eq(circuits.id, circuitDeviceRows.circuitId)
|
|
)
|
|
.innerJoin(
|
|
circuitLists,
|
|
eq(circuitLists.id, circuits.circuitListId)
|
|
)
|
|
.where(inArray(circuitDeviceRows.id, rowIds))
|
|
.all();
|
|
if (
|
|
persistedRows.length !== rowIds.length ||
|
|
persistedRows.some(
|
|
(persisted) => persisted.projectId !== input.projectId
|
|
)
|
|
) {
|
|
throw new Error(
|
|
"One or more sync rows do not belong to project."
|
|
);
|
|
}
|
|
|
|
const persistedById = new Map(
|
|
persistedRows.map((persisted) => [
|
|
persisted.row.id,
|
|
persisted.row,
|
|
])
|
|
);
|
|
for (const assignment of input.command.payload.rows) {
|
|
const persisted = persistedById.get(assignment.rowId);
|
|
if (
|
|
!persisted ||
|
|
!snapshotMatchesRow(assignment.expected, persisted)
|
|
) {
|
|
throw new Error(
|
|
"Project-device row changed before sync execution."
|
|
);
|
|
}
|
|
}
|
|
|
|
const inverse = createProjectDeviceRowSyncProjectCommand(
|
|
projectDevice.id,
|
|
invertProjectDeviceRowSyncOperation(
|
|
input.command.payload.operation
|
|
),
|
|
input.command.payload.rows.map((row) => ({
|
|
rowId: row.rowId,
|
|
expected: row.target,
|
|
target: row.expected,
|
|
}))
|
|
);
|
|
|
|
for (const assignment of input.command.payload.rows) {
|
|
const updated = tx
|
|
.update(circuitDeviceRows)
|
|
.set(assignment.target)
|
|
.where(eq(circuitDeviceRows.id, assignment.rowId))
|
|
.run();
|
|
if (updated.changes !== 1) {
|
|
throw new Error(
|
|
"Project-device row changed during sync execution."
|
|
);
|
|
}
|
|
}
|
|
const affectedCircuitIds = new Set(
|
|
input.command.payload.rows
|
|
.filter(
|
|
(assignment) =>
|
|
assignment.expected.phaseType !==
|
|
assignment.target.phaseType
|
|
)
|
|
.map(
|
|
(assignment) =>
|
|
persistedById.get(assignment.rowId)!.circuitId
|
|
)
|
|
);
|
|
for (const circuitId of affectedCircuitIds) {
|
|
updateDerivedCircuitVoltage(
|
|
tx,
|
|
input.projectId,
|
|
circuitId
|
|
);
|
|
}
|
|
|
|
return inverse;
|
|
}
|
|
}
|
|
|
|
function snapshotMatchesRow(
|
|
snapshot: ProjectDeviceSyncRowSnapshot,
|
|
row: CircuitDeviceRow
|
|
) {
|
|
return projectDeviceSyncRowSnapshotFields.every(
|
|
(field) => snapshot[field] === row[field]
|
|
);
|
|
}
|