Add project device update history
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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 { applyProjectHistoryTransition } from "./project-history.persistence.js";
|
||||
import { appendProjectRevision } from "./project-revision.persistence.js";
|
||||
|
||||
type ProjectDeviceRow = typeof projectDevices.$inferSelect;
|
||||
|
||||
export class ProjectDeviceProjectCommandRepository
|
||||
implements ProjectDeviceProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
executeUpdate(input: ExecuteProjectDeviceUpdateCommandInput) {
|
||||
assertProjectDeviceUpdateProjectCommand(input.command);
|
||||
|
||||
return this.database.transaction((tx) => {
|
||||
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;
|
||||
const inversePatch = Object.fromEntries(
|
||||
input.command.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."
|
||||
);
|
||||
}
|
||||
|
||||
const revision = appendProjectRevision(tx, {
|
||||
projectId: input.projectId,
|
||||
expectedRevision: input.expectedRevision,
|
||||
source: input.source,
|
||||
description: input.description,
|
||||
actorId: input.actorId,
|
||||
forward: input.command,
|
||||
inverse,
|
||||
});
|
||||
applyProjectHistoryTransition(tx, {
|
||||
projectId: input.projectId,
|
||||
source: input.source,
|
||||
recordedChangeSetId: revision.changeSetId,
|
||||
targetChangeSetId: input.historyTargetChangeSetId,
|
||||
});
|
||||
return { revision, inverse };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectDeviceFieldValue<
|
||||
TField extends ProjectDeviceUpdateField,
|
||||
>(
|
||||
projectDevice: ProjectDeviceRow,
|
||||
field: TField
|
||||
): ProjectDeviceUpdateValues[TField] {
|
||||
return projectDevice[field] as ProjectDeviceUpdateValues[TField];
|
||||
}
|
||||
Reference in New Issue
Block a user