Add atomic Revit initial import
This commit is contained in:
parent
e87a3289fa
commit
c634570018
16 changed files with 3150 additions and 8 deletions
1
src/db/migrations/0004_illegal_the_stranger.sql
Normal file
1
src/db/migrations/0004_illegal_the_stranger.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `external_import_batches` ADD `configuration_version` integer NOT NULL;
|
||||
2396
src/db/migrations/meta/0004_snapshot.json
Normal file
2396
src/db/migrations/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,13 @@
|
|||
"when": 1785683710155,
|
||||
"tag": "0003_outstanding_maddog",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "6",
|
||||
"when": 1785684457208,
|
||||
"tag": "0004_illegal_the_stranger",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
import crypto from "node:crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
assertExternalInitialImportProjectCommand,
|
||||
createExternalInitialImportProjectCommand,
|
||||
isEmptyExternalModelState,
|
||||
} from "../../domain/models/external-initial-import-project-command.model.js";
|
||||
import type { ExternalInitialImportProjectCommandStore } from "../../domain/ports/external-initial-import-project-command.store.js";
|
||||
import { createExternalCsvPreview } from "../../external-model/application/external-csv-preview.js";
|
||||
import { parseExternalCsv } from "../../external-model/csv/external-csv-transport.js";
|
||||
import type { ExternalModelStateSnapshot } from "../../external-model/domain/external-model-contracts.js";
|
||||
import {
|
||||
createExternalRoomKey,
|
||||
indexExternalObjectsByIfcGuid,
|
||||
projectInitialExternalObject,
|
||||
} from "../../external-model/domain/external-model-matching.js";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { distributionBoards } from "../schema/distribution-boards.js";
|
||||
import { externalCsvConfigurations } from "../schema/external-csv-configurations.js";
|
||||
import { externalImportBatches } from "../schema/external-import-batches.js";
|
||||
import { externalModelObjects } from "../schema/external-model-objects.js";
|
||||
import { externalModelSources } from "../schema/external-model-sources.js";
|
||||
import { externalRoomMappings } from "../schema/external-room-mappings.js";
|
||||
import { projectDevices } from "../schema/project-devices.js";
|
||||
import { rooms } from "../schema/rooms.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
import { ExternalModelStateRepository } from "./external-model-state.repository.js";
|
||||
|
||||
export class ExternalInitialImportProjectCommandRepository
|
||||
implements ExternalInitialImportProjectCommandStore
|
||||
{
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
execute(input: Parameters<ExternalInitialImportProjectCommandStore["execute"]>[0]) {
|
||||
assertExternalInitialImportProjectCommand(input.command);
|
||||
return executeProjectCommandTransaction(this.database, input, (tx) => {
|
||||
const currentResult = new ExternalModelStateRepository(tx)
|
||||
.getByProject(input.projectId);
|
||||
if (!currentResult.projectExists) throw new Error("Project not found.");
|
||||
const expected = input.command.payload.expected;
|
||||
if (!sameState(currentResult.state, expected)) {
|
||||
throw new Error("External model state changed before initial import.");
|
||||
}
|
||||
const target = input.command.payload.target;
|
||||
if (!isEmptyExternalModelState(target)) {
|
||||
validatePopulatedTarget(tx, input, target);
|
||||
}
|
||||
|
||||
replaceExternalState(tx, input.projectId, target);
|
||||
const persisted = new ExternalModelStateRepository(tx)
|
||||
.getByProject(input.projectId).state;
|
||||
if (!sameState(persisted, target)) {
|
||||
throw new Error("Persisted external initial import differs from its target.");
|
||||
}
|
||||
return createExternalInitialImportProjectCommand(target, currentResult.state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function validatePopulatedTarget(
|
||||
database: AppDatabase,
|
||||
input: Parameters<ExternalInitialImportProjectCommandStore["execute"]>[0],
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
const source = target.source!;
|
||||
const batch = target.importBatches[0];
|
||||
if (source.projectId !== input.projectId) {
|
||||
throw new Error("External initial import belongs to a different project.");
|
||||
}
|
||||
if (
|
||||
input.source === "user" &&
|
||||
batch.appliedProjectRevision !== input.expectedRevision + 1
|
||||
) {
|
||||
throw new Error("External initial import revision is invalid.");
|
||||
}
|
||||
const configuration = database
|
||||
.select()
|
||||
.from(externalCsvConfigurations)
|
||||
.where(eq(externalCsvConfigurations.projectId, input.projectId))
|
||||
.get();
|
||||
if (
|
||||
!configuration ||
|
||||
configuration.configurationVersion !== batch.configurationVersion ||
|
||||
canonicalJson(configuration.configuration) !==
|
||||
canonicalJson(batch.configurationSnapshot)
|
||||
) {
|
||||
throw new Error("External initial import configuration changed before apply.");
|
||||
}
|
||||
|
||||
const bytes = decodeCanonicalBase64(batch.originalContentBase64);
|
||||
const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
if (sha256 !== batch.sha256) {
|
||||
throw new Error("External initial import checksum is invalid.");
|
||||
}
|
||||
const parsedDocument = parseExternalCsv(bytes, batch.configurationSnapshot);
|
||||
if (canonicalJson(parsedDocument) !== canonicalJson(batch.document)) {
|
||||
throw new Error("External initial import document differs from the original bytes.");
|
||||
}
|
||||
const preview = createExternalCsvPreview({
|
||||
fileName: batch.fileName,
|
||||
bytes,
|
||||
configuration: batch.configurationSnapshot,
|
||||
});
|
||||
const targetByIfcGuid = indexExternalObjectsByIfcGuid(target.objects);
|
||||
if (targetByIfcGuid.size !== preview.objects.length) {
|
||||
throw new Error("External initial import must contain every CSV object exactly once.");
|
||||
}
|
||||
const mappingsById = new Map(
|
||||
target.roomMappings.map((mapping) => [mapping.id, mapping])
|
||||
);
|
||||
const expectedRoomKeys = new Set<string>();
|
||||
for (const candidate of preview.objects) {
|
||||
const object = targetByIfcGuid.get(candidate.ifcGuid);
|
||||
if (!object) {
|
||||
throw new Error(`External initial import is missing IFCGUID ${candidate.ifcGuid}.`);
|
||||
}
|
||||
const projected = projectInitialExternalObject(
|
||||
candidate,
|
||||
batch.configurationSnapshot
|
||||
);
|
||||
if (canonicalJson(object.acceptedSourceValues) !== canonicalJson(projected.sourceValues)) {
|
||||
throw new Error("External initial import source values differ from the CSV.");
|
||||
}
|
||||
for (const field of Object.keys(projected.planningValues) as Array<keyof typeof projected.planningValues>) {
|
||||
if (
|
||||
!object.overriddenFields.includes(field) &&
|
||||
canonicalJson(object.planningValues[field]) !==
|
||||
canonicalJson(projected.planningValues[field])
|
||||
) {
|
||||
throw new Error(`External initial planning field ${field} changed without an override.`);
|
||||
}
|
||||
}
|
||||
const roomKey = createExternalRoomKey(candidate.roomNumber, candidate.roomName);
|
||||
if (roomKey === null) {
|
||||
if (object.externalRoomMappingId !== null) {
|
||||
throw new Error("External object without a source room must not use a room mapping.");
|
||||
}
|
||||
} else {
|
||||
expectedRoomKeys.add(roomKey);
|
||||
const mapping = object.externalRoomMappingId === null
|
||||
? null
|
||||
: mappingsById.get(object.externalRoomMappingId);
|
||||
if (!mapping || mapping.normalizedSourceRoomKey !== roomKey) {
|
||||
throw new Error("External object source room mapping is incomplete.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
target.roomMappings.length !== expectedRoomKeys.size ||
|
||||
target.roomMappings.some(
|
||||
(mapping) => !expectedRoomKeys.has(mapping.normalizedSourceRoomKey)
|
||||
)
|
||||
) {
|
||||
throw new Error("External initial import room mappings do not match CSV rooms.");
|
||||
}
|
||||
validateInternalLinks(database, input.projectId, target);
|
||||
}
|
||||
|
||||
function validateInternalLinks(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
const roomIds = new Set(
|
||||
database.select({ id: rooms.id }).from(rooms)
|
||||
.where(eq(rooms.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
const boardIds = new Set(
|
||||
database.select({ id: distributionBoards.id }).from(distributionBoards)
|
||||
.where(eq(distributionBoards.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
const deviceIds = new Set(
|
||||
database.select({ id: projectDevices.id }).from(projectDevices)
|
||||
.where(eq(projectDevices.projectId, projectId)).all().map((row) => row.id)
|
||||
);
|
||||
for (const mapping of target.roomMappings) {
|
||||
if (mapping.roomId !== null && !roomIds.has(mapping.roomId)) {
|
||||
throw new Error("External room mapping references another project.");
|
||||
}
|
||||
if (
|
||||
mapping.defaultDistributionBoardId !== null &&
|
||||
!boardIds.has(mapping.defaultDistributionBoardId)
|
||||
) {
|
||||
throw new Error("External room mapping distribution board belongs to another project.");
|
||||
}
|
||||
}
|
||||
for (const object of target.objects) {
|
||||
if (object.distributionBoardId !== null && !boardIds.has(object.distributionBoardId)) {
|
||||
throw new Error("External object distribution board belongs to another project.");
|
||||
}
|
||||
if (object.linkedProjectDeviceId !== null && !deviceIds.has(object.linkedProjectDeviceId)) {
|
||||
throw new Error("External object project device belongs to another project.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replaceExternalState(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
target: ExternalModelStateSnapshot
|
||||
) {
|
||||
database.delete(externalModelSources)
|
||||
.where(eq(externalModelSources.projectId, projectId)).run();
|
||||
if (target.source === null) return;
|
||||
database.insert(externalModelSources).values(target.source).run();
|
||||
database.insert(externalImportBatches).values(
|
||||
target.importBatches.map(({ originalContentBase64, ...batch }) => ({
|
||||
...batch,
|
||||
originalBytes: Buffer.from(originalContentBase64, "base64"),
|
||||
}))
|
||||
).run();
|
||||
if (target.roomMappings.length) {
|
||||
database.insert(externalRoomMappings).values(target.roomMappings).run();
|
||||
}
|
||||
database.insert(externalModelObjects).values(target.objects).run();
|
||||
}
|
||||
|
||||
function decodeCanonicalBase64(value: string) {
|
||||
const bytes = Buffer.from(value, "base64");
|
||||
if (bytes.toString("base64") !== value) {
|
||||
throw new Error("External initial import bytes are not canonical base64.");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function sameState(left: ExternalModelStateSnapshot, right: ExternalModelStateSnapshot) {
|
||||
return canonicalJson(sortState(left)) === canonicalJson(sortState(right));
|
||||
}
|
||||
|
||||
function sortState(state: ExternalModelStateSnapshot): ExternalModelStateSnapshot {
|
||||
const byId = <T extends { id: string }>(entries: readonly T[]) =>
|
||||
[...entries].sort((left, right) => left.id.localeCompare(right.id));
|
||||
return {
|
||||
source: state.source,
|
||||
importBatches: byId(state.importBatches),
|
||||
roomMappings: byId(state.roomMappings),
|
||||
objects: byId(state.objects),
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ export const externalImportBatches = sqliteTable(
|
|||
fileName: text("file_name").notNull(),
|
||||
sha256: text("sha256").notNull(),
|
||||
appliedProjectRevision: integer("applied_project_revision").notNull(),
|
||||
configurationVersion: integer("configuration_version").notNull(),
|
||||
configurationSnapshot: text("configuration_snapshot", { mode: "json" })
|
||||
.$type<ExternalCsvConfiguration>()
|
||||
.notNull(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
import type { ExternalModelStateSnapshot } from "../../external-model/domain/external-model-contracts.js";
|
||||
import { parseExternalModelStateSnapshot } from "./project-state-snapshot.model.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
|
||||
export const externalInitialImportCommandType =
|
||||
"external-import.apply-initial" as const;
|
||||
export const externalInitialImportCommandSchemaVersion = 1 as const;
|
||||
|
||||
export interface ExternalInitialImportPayload {
|
||||
expected: ExternalModelStateSnapshot;
|
||||
target: ExternalModelStateSnapshot;
|
||||
}
|
||||
|
||||
export interface ExternalInitialImportProjectCommand
|
||||
extends SerializedProjectCommand<ExternalInitialImportPayload> {
|
||||
schemaVersion: typeof externalInitialImportCommandSchemaVersion;
|
||||
type: typeof externalInitialImportCommandType;
|
||||
}
|
||||
|
||||
export function createExternalInitialImportProjectCommand(
|
||||
expected: ExternalModelStateSnapshot,
|
||||
target: ExternalModelStateSnapshot
|
||||
): ExternalInitialImportProjectCommand {
|
||||
const command: ExternalInitialImportProjectCommand = {
|
||||
schemaVersion: externalInitialImportCommandSchemaVersion,
|
||||
type: externalInitialImportCommandType,
|
||||
payload: { expected, target },
|
||||
};
|
||||
assertExternalInitialImportProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function assertExternalInitialImportProjectCommand(
|
||||
command: SerializedProjectCommand<unknown>
|
||||
): asserts command is ExternalInitialImportProjectCommand {
|
||||
if (
|
||||
command.schemaVersion !== externalInitialImportCommandSchemaVersion ||
|
||||
command.type !== externalInitialImportCommandType ||
|
||||
!isRecord(command.payload) ||
|
||||
Object.keys(command.payload).length !== 2
|
||||
) {
|
||||
throw new Error("Unsupported external initial import command.");
|
||||
}
|
||||
const expected = parseExternalModelStateSnapshot(command.payload.expected);
|
||||
const target = parseExternalModelStateSnapshot(command.payload.target);
|
||||
const expectedIsEmpty = isEmptyExternalModelState(expected);
|
||||
const targetIsEmpty = isEmptyExternalModelState(target);
|
||||
if (expectedIsEmpty === targetIsEmpty) {
|
||||
throw new Error("External initial import must transition between empty and populated state.");
|
||||
}
|
||||
assertPopulatedInitialState(expectedIsEmpty ? target : expected);
|
||||
}
|
||||
|
||||
export function createEmptyExternalModelState(): ExternalModelStateSnapshot {
|
||||
return { source: null, importBatches: [], roomMappings: [], objects: [] };
|
||||
}
|
||||
|
||||
export function isEmptyExternalModelState(state: ExternalModelStateSnapshot) {
|
||||
return (
|
||||
state.source === null &&
|
||||
state.importBatches.length === 0 &&
|
||||
state.roomMappings.length === 0 &&
|
||||
state.objects.length === 0
|
||||
);
|
||||
}
|
||||
|
||||
function assertPopulatedInitialState(state: ExternalModelStateSnapshot) {
|
||||
if (state.source === null || state.importBatches.length !== 1 || state.objects.length === 0) {
|
||||
throw new Error("External initial import requires one source, one batch and at least one object.");
|
||||
}
|
||||
const source = state.source;
|
||||
const batch = state.importBatches[0];
|
||||
if (
|
||||
batch.importKind !== "initial" ||
|
||||
batch.projectId !== source.projectId ||
|
||||
batch.sourceId !== source.id
|
||||
) {
|
||||
throw new Error("External initial import batch identity is invalid.");
|
||||
}
|
||||
const mappingIds = uniqueIds(state.roomMappings, "room mapping");
|
||||
const roomKeys = new Set<string>();
|
||||
for (const mapping of state.roomMappings) {
|
||||
if (mapping.projectId !== source.projectId || mapping.sourceId !== source.id) {
|
||||
throw new Error("External initial room mapping identity is invalid.");
|
||||
}
|
||||
if (roomKeys.has(mapping.normalizedSourceRoomKey)) {
|
||||
throw new Error("External initial import contains duplicate room keys.");
|
||||
}
|
||||
roomKeys.add(mapping.normalizedSourceRoomKey);
|
||||
}
|
||||
uniqueIds(state.objects, "object");
|
||||
const ifcGuids = new Set<string>();
|
||||
for (const object of state.objects) {
|
||||
if (
|
||||
object.projectId !== source.projectId ||
|
||||
object.sourceId !== source.id ||
|
||||
object.lastSeenImportBatchId !== batch.id ||
|
||||
object.lastAcceptedImportBatchId !== batch.id
|
||||
) {
|
||||
throw new Error("External initial object identity is invalid.");
|
||||
}
|
||||
if (ifcGuids.has(object.ifcGuid)) {
|
||||
throw new Error("External initial import contains duplicate IFCGUIDs.");
|
||||
}
|
||||
ifcGuids.add(object.ifcGuid);
|
||||
if (
|
||||
object.externalRoomMappingId !== null &&
|
||||
!mappingIds.has(object.externalRoomMappingId)
|
||||
) {
|
||||
throw new Error("External initial object room mapping is invalid.");
|
||||
}
|
||||
if (object.circuitDeviceRowId !== null) {
|
||||
throw new Error("External initial import must not assign circuit device rows.");
|
||||
}
|
||||
if (object.presenceStatus !== "present") {
|
||||
throw new Error("External initial import objects must be present.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueIds(entries: ReadonlyArray<{ id: string }>, label: string) {
|
||||
const ids = new Set<string>();
|
||||
for (const entry of entries) {
|
||||
if (ids.has(entry.id)) {
|
||||
throw new Error(`External initial import contains duplicate ${label} ids.`);
|
||||
}
|
||||
ids.add(entry.id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
externalImportKinds,
|
||||
externalModelSourceTypes,
|
||||
externalObjectPresenceStatuses,
|
||||
type ExternalModelStateSnapshot,
|
||||
} from "../../external-model/domain/external-model-contracts.js";
|
||||
|
||||
export const projectStateSnapshotSchemaVersion = 4 as const;
|
||||
|
|
@ -333,6 +334,7 @@ const externalImportBatchSchema = z.object({
|
|||
fileName: z.string().trim().min(1),
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
appliedProjectRevision: z.number().int().nonnegative(),
|
||||
configurationVersion: z.number().int().positive(),
|
||||
configurationSnapshot: z.custom<ExternalCsvConfiguration>(
|
||||
(value) => validateExternalCsvConfiguration(value).success
|
||||
),
|
||||
|
|
@ -414,6 +416,12 @@ const externalModelStateSchema = z.object({
|
|||
objects: z.array(externalModelObjectSchema),
|
||||
}).strict();
|
||||
|
||||
export function parseExternalModelStateSnapshot(
|
||||
value: unknown
|
||||
): ExternalModelStateSnapshot {
|
||||
return externalModelStateSchema.parse(value);
|
||||
}
|
||||
|
||||
const projectStateSnapshotContents = {
|
||||
circuitLists: z.array(circuitListSchema),
|
||||
circuitSections: z.array(circuitSectionSchema),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import type { ExternalInitialImportProjectCommand } from "../models/external-initial-import-project-command.model.js";
|
||||
import type { AppendedProjectRevision, ProjectRevisionSource } from "./project-revision.store.js";
|
||||
|
||||
export interface ExecuteExternalInitialImportCommandInput {
|
||||
projectId: string;
|
||||
expectedRevision: number;
|
||||
source: ProjectRevisionSource;
|
||||
description?: string;
|
||||
actorId?: string;
|
||||
historyTargetChangeSetId?: string;
|
||||
command: ExternalInitialImportProjectCommand;
|
||||
}
|
||||
|
||||
export interface ExternalInitialImportProjectCommandStore {
|
||||
execute(input: ExecuteExternalInitialImportCommandInput): {
|
||||
revision: AppendedProjectRevision;
|
||||
inverse: ExternalInitialImportProjectCommand;
|
||||
};
|
||||
}
|
||||
|
|
@ -166,6 +166,11 @@ import {
|
|||
externalCsvConfigurationUpdateCommandType,
|
||||
} from "../models/external-csv-configuration-project-command.model.js";
|
||||
import type { ExternalCsvConfigurationProjectCommandStore } from "../ports/external-csv-configuration-project-command.store.js";
|
||||
import {
|
||||
assertExternalInitialImportProjectCommand,
|
||||
externalInitialImportCommandType,
|
||||
} from "../models/external-initial-import-project-command.model.js";
|
||||
import type { ExternalInitialImportProjectCommandStore } from "../ports/external-initial-import-project-command.store.js";
|
||||
|
||||
interface DispatchProjectCommandInput {
|
||||
projectId: string;
|
||||
|
|
@ -201,7 +206,8 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||
private readonly circuitGroupSubtreeStore: CircuitGroupSubtreeProjectCommandStore,
|
||||
private readonly circuitProtectionStore: CircuitProtectionProjectCommandStore,
|
||||
private readonly historyStore: ProjectHistoryStore,
|
||||
private readonly externalCsvConfigurationStore?: ExternalCsvConfigurationProjectCommandStore
|
||||
private readonly externalCsvConfigurationStore?: ExternalCsvConfigurationProjectCommandStore,
|
||||
private readonly externalInitialImportStore?: ExternalInitialImportProjectCommandStore
|
||||
) {}
|
||||
|
||||
executeUser(
|
||||
|
|
@ -568,6 +574,16 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case externalInitialImportCommandType: {
|
||||
assertExternalInitialImportProjectCommand(input.command);
|
||||
if (!this.externalInitialImportStore) {
|
||||
throw new Error("External initial import store is not available.");
|
||||
}
|
||||
return this.externalInitialImportStore.execute({
|
||||
...input,
|
||||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case projectStateRestoreCommandType: {
|
||||
assertProjectStateRestoreCommand(input.command);
|
||||
return this.projectStateRestoreStore.execute({
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export interface ExternalImportBatchSnapshot {
|
|||
fileName: string;
|
||||
sha256: string;
|
||||
appliedProjectRevision: number;
|
||||
configurationVersion: number;
|
||||
configurationSnapshot: ExternalCsvConfiguration;
|
||||
originalContentBase64: string;
|
||||
document: ExternalCsvDocument;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { ProjectSettingsProjectCommandRepository } from "../../db/repositories/p
|
|||
import { ProjectStateRestoreCommandRepository } from "../../db/repositories/project-state-restore-command.repository.js";
|
||||
import { ProjectCommandService } from "../../domain/services/project-command.service.js";
|
||||
import { ExternalCsvConfigurationProjectCommandRepository } from "../../db/repositories/external-csv-configuration-project-command.repository.js";
|
||||
import { ExternalInitialImportProjectCommandRepository } from "../../db/repositories/external-initial-import-project-command.repository.js";
|
||||
|
||||
export const circuitProjectCommandStore = new CircuitProjectCommandRepository(db);
|
||||
export const circuitDeviceRowProjectCommandStore =
|
||||
|
|
@ -68,6 +69,8 @@ export const projectStateRestoreCommandStore =
|
|||
export const projectHistoryStore = new ProjectHistoryRepository(db);
|
||||
export const externalCsvConfigurationProjectCommandStore =
|
||||
new ExternalCsvConfigurationProjectCommandRepository(db);
|
||||
export const externalInitialImportProjectCommandStore =
|
||||
new ExternalInitialImportProjectCommandRepository(db);
|
||||
export const projectCommandService = new ProjectCommandService(
|
||||
circuitProjectCommandStore,
|
||||
circuitDeviceRowProjectCommandStore,
|
||||
|
|
@ -91,5 +94,6 @@ export const projectCommandService = new ProjectCommandService(
|
|||
circuitGroupSubtreeProjectCommandStore,
|
||||
circuitProtectionProjectCommandStore,
|
||||
projectHistoryStore,
|
||||
externalCsvConfigurationProjectCommandStore
|
||||
externalCsvConfigurationProjectCommandStore,
|
||||
externalInitialImportProjectCommandStore
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue