134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import { and, eq } from "drizzle-orm";
|
|
import {
|
|
assertProjectDeviceUpdateProjectCommand,
|
|
createProjectDeviceUpdateProjectCommand,
|
|
type ProjectDeviceUpdateField,
|
|
type ProjectDeviceUpdatePatch,
|
|
type ProjectDeviceUpdateValues,
|
|
} from "../../domain/models/project-device-project-command.model.js";
|
|
import type {
|
|
ExecuteProjectDeviceUpdateCommandInput,
|
|
ProjectDeviceProjectCommandStore,
|
|
} from "../../domain/ports/project-device-project-command.store.js";
|
|
import type { AppDatabase } from "../database-context.js";
|
|
import { projectDevices } from "../schema/project-devices.js";
|
|
import { executeProjectCommandTransactionWithAppliedForward } from "./project-command-transaction.persistence.js";
|
|
import { resolveProjectDeviceVoltage } from "./project-voltage.persistence.js";
|
|
|
|
type ProjectDeviceRow = typeof projectDevices.$inferSelect;
|
|
|
|
export class ProjectDeviceProjectCommandRepository
|
|
implements ProjectDeviceProjectCommandStore
|
|
{
|
|
constructor(private readonly database: AppDatabase) {}
|
|
|
|
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
|
|
assertProjectDeviceUpdateProjectCommand(input.command);
|
|
|
|
return executeProjectCommandTransactionWithAppliedForward(
|
|
this.database,
|
|
input,
|
|
(tx) => this.applyCommand(tx, input)
|
|
);
|
|
}
|
|
|
|
private applyCommand(
|
|
tx: AppDatabase,
|
|
input: ExecuteProjectDeviceUpdateCommandInput
|
|
) {
|
|
const current = tx
|
|
.select()
|
|
.from(projectDevices)
|
|
.where(
|
|
and(
|
|
eq(
|
|
projectDevices.id,
|
|
input.command.payload.projectDeviceId
|
|
),
|
|
eq(projectDevices.projectId, input.projectId)
|
|
)
|
|
)
|
|
.get();
|
|
if (!current) {
|
|
throw new Error(
|
|
"Project device does not belong to project."
|
|
);
|
|
}
|
|
|
|
const patch = Object.fromEntries(
|
|
input.command.payload.changes.map((change) => [
|
|
change.field,
|
|
change.value,
|
|
])
|
|
) as ProjectDeviceUpdatePatch;
|
|
if (input.source === "user") {
|
|
if (
|
|
input.command.payload.changes.every(
|
|
(change) => change.field === "voltageV"
|
|
)
|
|
) {
|
|
throw new Error(
|
|
"Project-device voltage is derived and cannot be edited."
|
|
);
|
|
}
|
|
const targetPhaseType = patch.phaseType ?? current.phaseType;
|
|
if (
|
|
targetPhaseType !== "single_phase" &&
|
|
targetPhaseType !== "three_phase"
|
|
) {
|
|
throw new Error("Project-device phase type is invalid.");
|
|
}
|
|
const derivedVoltage = resolveProjectDeviceVoltage(
|
|
tx,
|
|
input.projectId,
|
|
targetPhaseType
|
|
);
|
|
if (derivedVoltage === current.voltageV) {
|
|
delete patch.voltageV;
|
|
} else {
|
|
patch.voltageV = derivedVoltage;
|
|
}
|
|
}
|
|
const appliedForward = createProjectDeviceUpdateProjectCommand(
|
|
current.id,
|
|
patch
|
|
);
|
|
const inversePatch = Object.fromEntries(
|
|
appliedForward.payload.changes.map((change) => [
|
|
change.field,
|
|
getProjectDeviceFieldValue(current, change.field),
|
|
])
|
|
) as ProjectDeviceUpdatePatch;
|
|
const inverse = createProjectDeviceUpdateProjectCommand(
|
|
current.id,
|
|
inversePatch
|
|
);
|
|
|
|
const updated = tx
|
|
.update(projectDevices)
|
|
.set(patch)
|
|
.where(
|
|
and(
|
|
eq(projectDevices.id, current.id),
|
|
eq(projectDevices.projectId, input.projectId)
|
|
)
|
|
)
|
|
.run();
|
|
if (updated.changes !== 1) {
|
|
throw new Error(
|
|
"Project device changed before command execution."
|
|
);
|
|
}
|
|
|
|
return { forward: appliedForward, inverse };
|
|
}
|
|
}
|
|
|
|
function getProjectDeviceFieldValue<
|
|
TField extends ProjectDeviceUpdateField,
|
|
>(
|
|
projectDevice: ProjectDeviceRow,
|
|
field: TField
|
|
): ProjectDeviceUpdateValues[TField] {
|
|
return projectDevice[field] as ProjectDeviceUpdateValues[TField];
|
|
}
|