Persist Revit CSV configuration
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
assertExternalCsvConfigurationUpdateProjectCommand,
|
||||
createExternalCsvConfigurationUpdateProjectCommand,
|
||||
type ExternalCsvConfigurationSnapshot,
|
||||
} from "../../domain/models/external-csv-configuration-project-command.model.js";
|
||||
import type {
|
||||
ExecuteExternalCsvConfigurationCommandInput,
|
||||
ExternalCsvConfigurationProjectCommandStore,
|
||||
} from "../../domain/ports/external-csv-configuration-project-command.store.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
|
||||
export class ExternalCsvConfigurationProjectCommandRepository
|
||||
implements ExternalCsvConfigurationProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: ExecuteExternalCsvConfigurationCommandInput) {
|
||||
assertExternalCsvConfigurationUpdateProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
const project = tx
|
||||
.select({ id: projects.id })
|
||||
.from(projects)
|
||||
.where(eq(projects.id, input.projectId))
|
||||
.get();
|
||||
if (!project) {
|
||||
throw new Error("Project not found.");
|
||||
}
|
||||
const current = tx
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.get() ?? null;
|
||||
assertSameSnapshot(current, input.command.payload.expected);
|
||||
const target = input.command.payload.target;
|
||||
if (target !== null && target.projectId !== input.projectId) {
|
||||
throw new Error("External CSV configuration belongs to a different project.");
|
||||
}
|
||||
if (sameSnapshot(current, target)) {
|
||||
throw new Error("External CSV configuration did not change.");
|
||||
}
|
||||
if (
|
||||
input.source === "user" &&
|
||||
target !== null &&
|
||||
target.configurationVersion !== (current?.configurationVersion ?? 0) + 1
|
||||
) {
|
||||
throw new Error("External CSV configuration version must advance by one.");
|
||||
}
|
||||
|
||||
if (target === null) {
|
||||
tx.delete(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.run();
|
||||
} else if (current === null) {
|
||||
tx.insert(externalCsvConfigurations).values(target).run();
|
||||
} else {
|
||||
const updated = tx
|
||||
.update(externalCsvConfigurations)
|
||||
.set({
|
||||
configurationVersion: target.configurationVersion,
|
||||
configuration: target.configuration,
|
||||
})
|
||||
.where(eq(externalCsvConfigurations.id, current.id))
|
||||
.run();
|
||||
if (updated.changes !== 1) {
|
||||
throw new Error("External CSV configuration changed before update.");
|
||||
}
|
||||
}
|
||||
return createExternalCsvConfigurationUpdateProjectCommand(target, current);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function assertSameSnapshot(
|
||||
current: ExternalCsvConfigurationSnapshot | null,
|
||||
expected: ExternalCsvConfigurationSnapshot | null
|
||||
) {
|
||||
if (!sameSnapshot(current, expected)) {
|
||||
throw new Error("External CSV configuration changed before update.");
|
||||
}
|
||||
}
|
||||
|
||||
function sameSnapshot(
|
||||
left: ExternalCsvConfigurationSnapshot | null,
|
||||
right: ExternalCsvConfigurationSnapshot | null
|
||||
) {
|
||||
return canonicalJson(left) === canonicalJson(right);
|
||||
}
|
||||
|
||||
function canonicalJson(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map(canonicalJson).join(",")}]`;
|
||||
}
|
||||
if (value !== null && typeof value === "object") {
|
||||
const record = value as Record<string, unknown>;
|
||||
return `{${Object.keys(record)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import { projectDevices } from "../schema/project-devices.js";
|
||||
import { projectChangeSets } from "../schema/project-change-sets.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import {
|
||||
readProjectStateSnapshot,
|
||||
@@ -161,6 +162,7 @@ function canonicalProjectState(state: ProjectStateSnapshot) {
|
||||
projectDevices: byId(state.projectDevices),
|
||||
floors: byId(state.floors),
|
||||
rooms: byId(state.rooms),
|
||||
externalCsvConfiguration: state.externalCsvConfiguration,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,6 +185,10 @@ function replaceProjectState(
|
||||
database: AppDatabase,
|
||||
state: ProjectStateSnapshot
|
||||
) {
|
||||
database
|
||||
.delete(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, state.project.id))
|
||||
.run();
|
||||
database
|
||||
.delete(distributionBoards)
|
||||
.where(eq(distributionBoards.projectId, state.project.id))
|
||||
@@ -218,6 +224,12 @@ function replaceProjectState(
|
||||
.run();
|
||||
|
||||
insertMany(database, floors, state.floors);
|
||||
if (state.externalCsvConfiguration !== null) {
|
||||
database
|
||||
.insert(externalCsvConfigurations)
|
||||
.values(state.externalCsvConfiguration)
|
||||
.run();
|
||||
}
|
||||
insertMany(database, rooms, state.rooms);
|
||||
insertMany(database, projectDevices, state.projectDevices);
|
||||
insertMany(database, distributionBoards, state.distributionBoards);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { floors } from "../schema/floors.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
|
||||
export interface PersistedProjectStateSnapshot {
|
||||
currentRevision: number;
|
||||
@@ -59,6 +60,11 @@ export function readProjectStateSnapshot(
|
||||
.where(eq(distributionBoards.projectId, projectId))
|
||||
.orderBy(asc(distributionBoards.name), asc(distributionBoards.id))
|
||||
.all();
|
||||
const externalCsvConfiguration = database
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, projectId))
|
||||
.get() ?? null;
|
||||
const listRows = database
|
||||
.select()
|
||||
.from(circuitLists)
|
||||
@@ -241,6 +247,7 @@ export function readProjectStateSnapshot(
|
||||
project.enabledDistributionBoardSupplyTypes,
|
||||
},
|
||||
distributionBoards: boardRows,
|
||||
externalCsvConfiguration,
|
||||
circuitLists: listRows,
|
||||
circuitSections: sectionRows,
|
||||
distributionBoardComponents: componentRows,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { floors } from "../schema/floors.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { projects } from "../schema/projects.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import {
|
||||
hashProjectStatePayload,
|
||||
readProjectStateSnapshot,
|
||||
@@ -116,6 +117,12 @@ function insertProjectState(
|
||||
.insert(projects)
|
||||
.values({ ...state.project, currentRevision: 0 })
|
||||
.run();
|
||||
if (state.externalCsvConfiguration !== null) {
|
||||
database
|
||||
.insert(externalCsvConfigurations)
|
||||
.values(state.externalCsvConfiguration)
|
||||
.run();
|
||||
}
|
||||
insertMany(database, floors, state.floors);
|
||||
insertMany(database, rooms, state.rooms);
|
||||
insertMany(database, projectDevices, state.projectDevices);
|
||||
|
||||
Reference in New Issue
Block a user