From c45dda4107e185136d07c2a26c91d2b370eff825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Wed, 11 May 2022 09:54:59 +0100 Subject: [PATCH 1/6] feat: legacy project import aggregate --- ...cy-project-import-pieces-imported.event.ts | 10 + ...egacy-project-import-batch-failed.event.ts | 9 + ...acy-project-import-piece-imported.event.ts | 10 + ...cy-project-import-piece-requested.event.ts | 10 + .../legacy-project-import-requested.event.ts | 10 + .../legacy-projec-import.snapshot.ts | 8 + .../legacy-project-import-component-status.ts | 37 ++++ .../legacy-project-import-component.id.ts | 14 ++ ...egacy-project-import-component.snapshot.ts | 11 ++ .../legacy-project-import-component.ts | 74 ++++++++ .../legacy-project-import.id.ts | 14 ++ .../legacy-project-import.ts | 171 ++++++++++++++++++ .../legacy-project-import/src/domain/index.ts | 6 - .../domain/legacy-project-import-piece.id.ts | 14 -- .../src/domain/legacy-project-import-piece.ts | 32 ++-- api/libs/legacy-project-import/src/index.ts | 4 +- 16 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/events/all-legacy-project-import-pieces-imported.event.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-batch-failed.event.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-imported.event.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-requested.event.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-requested.event.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.id.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.id.ts create mode 100644 api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts delete mode 100644 api/libs/legacy-project-import/src/domain/index.ts delete mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-piece.id.ts diff --git a/api/apps/api/src/modules/legacy-project-import/domain/events/all-legacy-project-import-pieces-imported.event.ts b/api/apps/api/src/modules/legacy-project-import/domain/events/all-legacy-project-import-pieces-imported.event.ts new file mode 100644 index 0000000000..862e9fad12 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/events/all-legacy-project-import-pieces-imported.event.ts @@ -0,0 +1,10 @@ +import { ResourceId } from '@marxan/cloning/domain'; +import { IEvent } from '@nestjs/cqrs'; +import { LegacyProjectImportId } from '../legacy-project-import/legacy-project-import.id'; + +export class AllLegacyProjectPiecesImported implements IEvent { + constructor( + public readonly legacyProjectImportId: LegacyProjectImportId, + public readonly projectId: ResourceId, + ) {} +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-batch-failed.event.ts b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-batch-failed.event.ts new file mode 100644 index 0000000000..c25badb4f7 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-batch-failed.event.ts @@ -0,0 +1,9 @@ +import { IEvent } from '@nestjs/cqrs'; +import { LegacyProjectImportId } from '../legacy-project-import/legacy-project-import.id'; + +export class LegacyProjectImportBatchFailed implements IEvent { + constructor( + public readonly legacyProjectImportId: LegacyProjectImportId, + public readonly batchNumber: number, + ) {} +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-imported.event.ts b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-imported.event.ts new file mode 100644 index 0000000000..98a81311b0 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-imported.event.ts @@ -0,0 +1,10 @@ +import { IEvent } from '@nestjs/cqrs'; +import { LegacyProjectImportComponentId } from '../legacy-project-import/legacy-project-import-component.id'; +import { LegacyProjectImportId } from '../legacy-project-import/legacy-project-import.id'; + +export class LegacyProjectImportPieceImported implements IEvent { + constructor( + public readonly legacyProjectImportId: LegacyProjectImportId, + public readonly componentId: LegacyProjectImportComponentId, + ) {} +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-requested.event.ts b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-requested.event.ts new file mode 100644 index 0000000000..e6f56105e2 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-piece-requested.event.ts @@ -0,0 +1,10 @@ +import { IEvent } from '@nestjs/cqrs'; +import { LegacyProjectImportComponentId } from '../legacy-project-import/legacy-project-import-component.id'; +import { LegacyProjectImportId } from '../legacy-project-import/legacy-project-import.id'; + +export class LegacyProjectImportPieceRequested implements IEvent { + constructor( + public readonly legacyProjectImportId: LegacyProjectImportId, + public readonly componentId: LegacyProjectImportComponentId, + ) {} +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-requested.event.ts b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-requested.event.ts new file mode 100644 index 0000000000..b01fe3da9d --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/events/legacy-project-import-requested.event.ts @@ -0,0 +1,10 @@ +import { ResourceId } from '@marxan/cloning/domain'; +import { IEvent } from '@nestjs/cqrs'; +import { LegacyProjectImportId } from '../legacy-project-import/legacy-project-import.id'; + +export class LegacyProjectImportRequested implements IEvent { + constructor( + public readonly legacyProjectImportId: LegacyProjectImportId, + public readonly projectId: ResourceId, + ) {} +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts new file mode 100644 index 0000000000..ea2367043b --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts @@ -0,0 +1,8 @@ +import { LegacyProjectImportComponentSnapshot } from './legacy-project-import-component.snapshot'; + +export interface LegacyProjectImportSnapshot { + readonly id: string; + readonly projectId: string; + readonly ownerId: string; + readonly pieces: LegacyProjectImportComponentSnapshot[]; +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts new file mode 100644 index 0000000000..41697a262e --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts @@ -0,0 +1,37 @@ +export enum LegacyProjectImportComponentStatuses { + Submitted = 'submitted', + Completed = 'completed', + Failed = 'failed', +} + +export class LegacyProjectImportComponentStatus { + static create(): LegacyProjectImportComponentStatus { + return new LegacyProjectImportComponentStatus( + LegacyProjectImportComponentStatuses.Submitted, + ); + } + + #status: LegacyProjectImportComponentStatuses; + + constructor(status: LegacyProjectImportComponentStatuses) { + this.#status = status; + } + + get value(): LegacyProjectImportComponentStatuses { + return this.#status; + } + + markAsCompleted(): void { + if (this.value === LegacyProjectImportComponentStatuses.Failed) + throw new Error('Import component has already failed'); + + this.#status = LegacyProjectImportComponentStatuses.Completed; + } + + markAsFailed(): void { + if (this.value === LegacyProjectImportComponentStatuses.Completed) + throw new Error('Import component has already been completed'); + + this.#status = LegacyProjectImportComponentStatuses.Failed; + } +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.id.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.id.ts new file mode 100644 index 0000000000..94fe8e04d8 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.id.ts @@ -0,0 +1,14 @@ +import { v4 } from 'uuid'; + +export class LegacyProjectImportComponentId { + private readonly _token = 'legacy-project-import-component-id'; + constructor(public readonly value: string) {} + + static create(): LegacyProjectImportComponentId { + return new LegacyProjectImportComponentId(v4()); + } + + equals(other: LegacyProjectImportComponentId): boolean { + return this.value === other.value; + } +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts new file mode 100644 index 0000000000..bbcd423d24 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts @@ -0,0 +1,11 @@ +import { LegacyProjectImportPiece } from '@marxan/legacy-project-import'; +import { LegacyProjectImportComponentStatuses } from './legacy-project-import-component-status'; + +export interface LegacyProjectImportComponentSnapshot { + readonly id: string; + readonly kind: LegacyProjectImportPiece; + readonly resourceId: string; + readonly order: number; + readonly archiveLocation?: string; + readonly status: LegacyProjectImportComponentStatuses; +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts new file mode 100644 index 0000000000..b05271a59b --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts @@ -0,0 +1,74 @@ +import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain'; +import { LegacyProjectImportPiece } from '@marxan/legacy-project-import'; +import { + LegacyProjectImportComponentStatus, + LegacyProjectImportComponentStatuses, +} from './legacy-project-import-component-status'; +import { LegacyProjectImportComponentId } from './legacy-project-import-component.id'; +import { LegacyProjectImportComponentSnapshot } from './legacy-project-import-component.snapshot'; + +export class LegacyProjectImportComponent { + private constructor( + readonly id: LegacyProjectImportComponentId, + readonly kind: LegacyProjectImportPiece, + readonly resourceId: ResourceId, + readonly order: number, + readonly archiveLocation?: ArchiveLocation, + readonly status: LegacyProjectImportComponentStatus = LegacyProjectImportComponentStatus.create(), + ) {} + + static fromSnapshot(snapshot: LegacyProjectImportComponentSnapshot) { + return new LegacyProjectImportComponent( + new LegacyProjectImportComponentId(snapshot.id), + snapshot.kind, + new ResourceId(snapshot.resourceId), + snapshot.order, + snapshot.archiveLocation + ? new ArchiveLocation(snapshot.archiveLocation) + : undefined, + new LegacyProjectImportComponentStatus(snapshot.status), + ); + } + + static newOne( + kind: LegacyProjectImportPiece, + resourceId: ResourceId, + order: number, + archiveLocation?: ArchiveLocation, + ): LegacyProjectImportComponent { + return new LegacyProjectImportComponent( + LegacyProjectImportComponentId.create(), + kind, + resourceId, + order, + archiveLocation, + ); + } + + isReady() { + return this.status.value === LegacyProjectImportComponentStatuses.Completed; + } + + hasFailed() { + return this.status.value === LegacyProjectImportComponentStatuses.Failed; + } + + complete() { + this.status.markAsCompleted(); + } + + markAsFailed() { + this.status.markAsFailed(); + } + + toSnapshot(): LegacyProjectImportComponentSnapshot { + return { + id: this.id.value, + order: this.order, + status: this.status.value, + kind: this.kind, + resourceId: this.resourceId.value, + archiveLocation: this.archiveLocation?.value, + }; + } +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.id.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.id.ts new file mode 100644 index 0000000000..efe224d7f7 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.id.ts @@ -0,0 +1,14 @@ +import { v4 } from 'uuid'; + +export class LegacyProjectImportId { + private readonly _token = 'legacy-project-import-id'; + constructor(public readonly value: string) {} + + static create(): LegacyProjectImportId { + return new LegacyProjectImportId(v4()); + } + + equals(other: LegacyProjectImportId): boolean { + return this.value === other.value; + } +} diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts new file mode 100644 index 0000000000..436e3c1d08 --- /dev/null +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts @@ -0,0 +1,171 @@ +import { ResourceId } from '@marxan/cloning/domain'; +import { UserId } from '@marxan/domain-ids'; +import { AggregateRoot } from '@nestjs/cqrs'; +import { Either, left, right } from 'fp-ts/Either'; +import { AllLegacyProjectPiecesImported } from '../events/all-legacy-project-import-pieces-imported.event'; +import { LegacyProjectImportBatchFailed } from '../events/legacy-project-import-batch-failed.event'; +import { LegacyProjectImportPieceImported } from '../events/legacy-project-import-piece-imported.event'; +import { LegacyProjectImportPieceRequested } from '../events/legacy-project-import-piece-requested.event'; +import { LegacyProjectImportRequested } from '../events/legacy-project-import-requested.event'; +import { LegacyProjectImportSnapshot } from './legacy-projec-import.snapshot'; +import { LegacyProjectImportComponent } from './legacy-project-import-component'; +import { LegacyProjectImportComponentId } from './legacy-project-import-component.id'; +import { LegacyProjectImportId } from './legacy-project-import.id'; + +export const legacyProjectImportComponentNotFound = Symbol( + `legacy project import component not found`, +); +export const legacyProjectImportComponentAlreadyCompleted = Symbol( + `legacy project import component already completed`, +); +export const legacyProjectImportComponentAlreadyFailed = Symbol( + `legacy project import component already failed`, +); + +export type CompleteLegacyProjectImportPieceSuccess = true; +export type CompleteLegacyProjectImportPieceErrors = + | typeof legacyProjectImportComponentNotFound + | typeof legacyProjectImportComponentAlreadyCompleted; +export type MarkLegacyProjectImportPieceAsFailedErrors = + | typeof legacyProjectImportComponentNotFound + | typeof legacyProjectImportComponentAlreadyFailed; + +export class LegacyProjectImport extends AggregateRoot { + private constructor( + readonly id: LegacyProjectImportId, + private readonly projectId: ResourceId, + private readonly ownerId: UserId, + private readonly pieces: LegacyProjectImportComponent[], + ) { + super(); + } + + static fromSnapshot( + snapshot: LegacyProjectImportSnapshot, + ): LegacyProjectImport { + return new LegacyProjectImport( + new LegacyProjectImportId(snapshot.id), + new ResourceId(snapshot.projectId), + new UserId(snapshot.ownerId), + snapshot.pieces.map(LegacyProjectImportComponent.fromSnapshot), + ); + } + + static newOne( + projectId: ResourceId, + ownerId: UserId, + pieces: LegacyProjectImportComponent[], + ): LegacyProjectImport { + return new LegacyProjectImport( + LegacyProjectImportId.create(), + projectId, + ownerId, + pieces, + ); + } + + private requestFirstBatch() { + const firstBatchOrder = Math.min( + ...this.pieces.map((piece) => piece.order), + ); + + for (const component of this.pieces.filter( + (pc) => pc.order === firstBatchOrder, + )) { + this.apply(new LegacyProjectImportPieceRequested(this.id, component.id)); + } + } + + private hasBatchFinished(order: number) { + return this.pieces + .filter((piece) => piece.order === order) + .every((piece) => piece.isReady() || piece.hasFailed()); + } + + private isLastBatch(order: number) { + return order === Math.max(...this.pieces.map((piece) => piece.order)); + } + + private hasBatchFailed(order: number) { + return this.pieces + .filter((piece) => piece.order === order) + .some((piece) => piece.hasFailed()); + } + + start(): void { + this.apply(new LegacyProjectImportRequested(this.id, this.projectId)); + this.requestFirstBatch(); + } + + markPieceAsFailed( + pieceId: LegacyProjectImportComponentId, + ): Either { + const piece = this.pieces.find((pc) => pc.id.value === pieceId.value); + if (!piece) return left(legacyProjectImportComponentNotFound); + if (piece.hasFailed()) + return left(legacyProjectImportComponentAlreadyFailed); + + piece.markAsFailed(); + + const hasThisBatchFinished = this.hasBatchFinished(piece.order); + const hasThisBatchFailed = this.hasBatchFailed(piece.order); + + if (hasThisBatchFinished && hasThisBatchFailed) { + this.apply(new LegacyProjectImportBatchFailed(this.id, piece.order)); + } + + return right(true); + } + + completePiece( + pieceId: LegacyProjectImportComponentId, + ): Either< + CompleteLegacyProjectImportPieceErrors, + CompleteLegacyProjectImportPieceSuccess + > { + const pieceToComplete = this.pieces.find( + (pc) => pc.id.value === pieceId.value, + ); + if (!pieceToComplete) return left(legacyProjectImportComponentNotFound); + if (pieceToComplete.isReady()) + return left(legacyProjectImportComponentAlreadyCompleted); + + this.apply(new LegacyProjectImportPieceImported(this.id, pieceId)); + + pieceToComplete.complete(); + + const isThisTheLastBatch = this.isLastBatch(pieceToComplete.order); + const hasThisBatchFinished = this.hasBatchFinished(pieceToComplete.order); + const hasThisBatchFailed = this.hasBatchFailed(pieceToComplete.order); + + if (hasThisBatchFinished && hasThisBatchFailed) { + this.apply( + new LegacyProjectImportBatchFailed(this.id, pieceToComplete.order), + ); + return right(true); + } + + if (isThisTheLastBatch && hasThisBatchFinished) + this.apply(new AllLegacyProjectPiecesImported(this.id, this.projectId)); + if (isThisTheLastBatch || !hasThisBatchFinished) return right(true); + + const nextBatch = this.pieces.filter( + (piece) => piece.order === pieceToComplete.order + 1, + ); + + for (const component of nextBatch) { + this.apply(new LegacyProjectImportPieceRequested(this.id, component.id)); + } + + return right(true); + } + + toSnapshot(): LegacyProjectImportSnapshot { + return { + id: this.id.value, + pieces: this.pieces.map((piece) => piece.toSnapshot()), + projectId: this.projectId.value, + ownerId: this.ownerId.value, + }; + } +} diff --git a/api/libs/legacy-project-import/src/domain/index.ts b/api/libs/legacy-project-import/src/domain/index.ts deleted file mode 100644 index e00c5c6247..0000000000 --- a/api/libs/legacy-project-import/src/domain/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { - LegacyProjectImportPiece, - LegacyProjectImportPieceRelativePathResolver, - LegacyProjectImportPieceOrderResolver, -} from './legacy-project-import-piece'; -export { LegacyProjectImportPieceId } from './legacy-project-import-piece.id'; diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.id.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.id.ts deleted file mode 100644 index 504e88c64f..0000000000 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.id.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { v4 } from 'uuid'; - -export class LegacyProjectImportPieceId { - private readonly _token = 'legacy-project-import-piece-id'; - constructor(public readonly value: string) {} - - static create(): LegacyProjectImportPieceId { - return new LegacyProjectImportPieceId(v4()); - } - - equals(other: LegacyProjectImportPieceId): boolean { - return this.value === other.value; - } -} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts index f164fd6787..3f3c1a998d 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts @@ -1,10 +1,25 @@ -export enum LegacyProjectImportPiece {} +export enum LegacyProjectImportPiece { + PlanningGrid = 'planning-grid', + ShapefileFeature = 'shapefile-feature', + NonShapefileFeatures = 'non-shapefile-features', + ScenarioPusData = 'scenario-pus-data', + FeatureSpecification = 'features-specification', + Solutions = 'solutions', +} export class LegacyProjectImportPieceOrderResolver { private static legacyProjectImportPieceOrder: Record< LegacyProjectImportPiece, number - > = {}; + > = { + // TODO Establish proper order for pieces + [LegacyProjectImportPiece.PlanningGrid]: 0, + [LegacyProjectImportPiece.ShapefileFeature]: 1, + [LegacyProjectImportPiece.NonShapefileFeatures]: 1, + [LegacyProjectImportPiece.ScenarioPusData]: 1, + [LegacyProjectImportPiece.FeatureSpecification]: 2, + [LegacyProjectImportPiece.Solutions]: 2, + }; static resolveFor( legacyProjectImportPiece: LegacyProjectImportPiece, @@ -12,16 +27,3 @@ export class LegacyProjectImportPieceOrderResolver { return this.legacyProjectImportPieceOrder[legacyProjectImportPiece]; } } - -export class LegacyProjectImportPieceRelativePathResolver { - private static legacyProjectImportPieceRelativePaths: Record< - LegacyProjectImportPiece, - string - > = {}; - - static resolveFor( - legacyProjectImportPiece: LegacyProjectImportPiece, - ): string { - return this.legacyProjectImportPieceRelativePaths[legacyProjectImportPiece]; - } -} diff --git a/api/libs/legacy-project-import/src/index.ts b/api/libs/legacy-project-import/src/index.ts index 478ad5e532..230898d960 100644 --- a/api/libs/legacy-project-import/src/index.ts +++ b/api/libs/legacy-project-import/src/index.ts @@ -4,7 +4,5 @@ export { LegacyProjectImportJobOutput } from './job-output'; export { LegacyProjectImportPiece, LegacyProjectImportPieceOrderResolver, - LegacyProjectImportPieceRelativePathResolver, - LegacyProjectImportPieceId, -} from './domain'; +} from './domain/legacy-project-import-piece'; export { LegacyProjectImportPieceProcessor } from './legacy-project-import-piece-processor.port'; From 435ac02a64e74ea1b0ad92a132dfde4a8f8f0230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Wed, 11 May 2022 10:07:38 +0100 Subject: [PATCH 2/6] fix: import issues --- api/libs/legacy-project-import/src/job-input.ts | 2 +- api/libs/legacy-project-import/src/job-output.ts | 2 +- .../src/legacy-project-import-piece-processor.port.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/libs/legacy-project-import/src/job-input.ts b/api/libs/legacy-project-import/src/job-input.ts index 15358cd0b0..5187d7afd7 100644 --- a/api/libs/legacy-project-import/src/job-input.ts +++ b/api/libs/legacy-project-import/src/job-input.ts @@ -1,4 +1,4 @@ -import { LegacyProjectImportPiece } from './domain'; +import { LegacyProjectImportPiece } from './domain/legacy-project-import-piece'; export interface LegacyProjectImportJobInput { readonly legacyProjectImportId: string; diff --git a/api/libs/legacy-project-import/src/job-output.ts b/api/libs/legacy-project-import/src/job-output.ts index c70f75c886..482ce6d57f 100644 --- a/api/libs/legacy-project-import/src/job-output.ts +++ b/api/libs/legacy-project-import/src/job-output.ts @@ -1,4 +1,4 @@ -import { LegacyProjectImportPiece } from './domain'; +import { LegacyProjectImportPiece } from './domain/legacy-project-import-piece'; export interface LegacyProjectImportJobOutput { readonly legacyProjectImportId: string; diff --git a/api/libs/legacy-project-import/src/legacy-project-import-piece-processor.port.ts b/api/libs/legacy-project-import/src/legacy-project-import-piece-processor.port.ts index 2304537df9..cad0869cc4 100644 --- a/api/libs/legacy-project-import/src/legacy-project-import-piece-processor.port.ts +++ b/api/libs/legacy-project-import/src/legacy-project-import-piece-processor.port.ts @@ -1,4 +1,4 @@ -import { LegacyProjectImportPiece } from './domain'; +import { LegacyProjectImportPiece } from './domain/legacy-project-import-piece'; export abstract class LegacyProjectImportPieceProcessor { abstract run(input: I): Promise; From d3cbc2a62b71b369cb1b1cf18e9ad15f3cfa658c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Wed, 11 May 2022 14:22:15 +0100 Subject: [PATCH 3/6] ref: legacy project import aggregate --- .../legacy-project-import-component-status.ts | 36 +++-- ...egacy-project-import-component.snapshot.ts | 1 - .../legacy-project-import-component.ts | 30 ++--- ...t.ts => legacy-project-import.snapshot.ts} | 4 + .../legacy-project-import.ts | 127 ++++++++++++++++-- .../legacy-project-import/src/domain/index.ts | 8 ++ .../domain/legacy-project-import-file-type.ts | 9 ++ .../domain/legacy-project-import-file.id.ts | 14 ++ .../legacy-project-import-file.snapshot.ts | 7 + .../src/domain/legacy-project-import-file.ts | 30 +++++ .../src/domain/legacy-project-import-piece.ts | 12 +- api/libs/legacy-project-import/src/index.ts | 6 +- .../legacy-project-import/src/job-input.ts | 4 +- .../legacy-project-import/src/job-output.ts | 6 +- 14 files changed, 239 insertions(+), 55 deletions(-) rename api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/{legacy-projec-import.snapshot.ts => legacy-project-import.snapshot.ts} (58%) create mode 100644 api/libs/legacy-project-import/src/domain/index.ts create mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts create mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts create mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts create mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts index 41697a262e..994b6aab1b 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component-status.ts @@ -11,27 +11,35 @@ export class LegacyProjectImportComponentStatus { ); } - #status: LegacyProjectImportComponentStatuses; + constructor(private readonly status: LegacyProjectImportComponentStatuses) {} - constructor(status: LegacyProjectImportComponentStatuses) { - this.#status = status; - } + markAsCompleted(): LegacyProjectImportComponentStatus { + if (this.status === LegacyProjectImportComponentStatuses.Failed) + throw new Error('Import component has already failed'); - get value(): LegacyProjectImportComponentStatuses { - return this.#status; + return new LegacyProjectImportComponentStatus( + LegacyProjectImportComponentStatuses.Completed, + ); } - markAsCompleted(): void { - if (this.value === LegacyProjectImportComponentStatuses.Failed) - throw new Error('Import component has already failed'); + markAsFailed(): LegacyProjectImportComponentStatus { + if (this.status === LegacyProjectImportComponentStatuses.Completed) + throw new Error('Import component has already been completed'); - this.#status = LegacyProjectImportComponentStatuses.Completed; + return new LegacyProjectImportComponentStatus( + LegacyProjectImportComponentStatuses.Failed, + ); } - markAsFailed(): void { - if (this.value === LegacyProjectImportComponentStatuses.Completed) - throw new Error('Import component has already been completed'); + isReady() { + return this.status === LegacyProjectImportComponentStatuses.Completed; + } + + hasFailed() { + return this.status === LegacyProjectImportComponentStatuses.Failed; + } - this.#status = LegacyProjectImportComponentStatuses.Failed; + toSnapshot(): LegacyProjectImportComponentStatuses { + return this.status; } } diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts index bbcd423d24..c7f0bc538d 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts @@ -4,7 +4,6 @@ import { LegacyProjectImportComponentStatuses } from './legacy-project-import-co export interface LegacyProjectImportComponentSnapshot { readonly id: string; readonly kind: LegacyProjectImportPiece; - readonly resourceId: string; readonly order: number; readonly archiveLocation?: string; readonly status: LegacyProjectImportComponentStatuses; diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts index b05271a59b..6bbd493b46 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts @@ -1,9 +1,9 @@ -import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain'; -import { LegacyProjectImportPiece } from '@marxan/legacy-project-import'; +import { ArchiveLocation } from '@marxan/cloning/domain'; import { - LegacyProjectImportComponentStatus, - LegacyProjectImportComponentStatuses, -} from './legacy-project-import-component-status'; + LegacyProjectImportPiece, + LegacyProjectImportPieceOrderResolver, +} from '@marxan/legacy-project-import'; +import { LegacyProjectImportComponentStatus } from './legacy-project-import-component-status'; import { LegacyProjectImportComponentId } from './legacy-project-import-component.id'; import { LegacyProjectImportComponentSnapshot } from './legacy-project-import-component.snapshot'; @@ -11,17 +11,15 @@ export class LegacyProjectImportComponent { private constructor( readonly id: LegacyProjectImportComponentId, readonly kind: LegacyProjectImportPiece, - readonly resourceId: ResourceId, readonly order: number, readonly archiveLocation?: ArchiveLocation, - readonly status: LegacyProjectImportComponentStatus = LegacyProjectImportComponentStatus.create(), + private status: LegacyProjectImportComponentStatus = LegacyProjectImportComponentStatus.create(), ) {} static fromSnapshot(snapshot: LegacyProjectImportComponentSnapshot) { return new LegacyProjectImportComponent( new LegacyProjectImportComponentId(snapshot.id), snapshot.kind, - new ResourceId(snapshot.resourceId), snapshot.order, snapshot.archiveLocation ? new ArchiveLocation(snapshot.archiveLocation) @@ -32,42 +30,38 @@ export class LegacyProjectImportComponent { static newOne( kind: LegacyProjectImportPiece, - resourceId: ResourceId, - order: number, archiveLocation?: ArchiveLocation, ): LegacyProjectImportComponent { return new LegacyProjectImportComponent( LegacyProjectImportComponentId.create(), kind, - resourceId, - order, + LegacyProjectImportPieceOrderResolver.resolveFor(kind), archiveLocation, ); } isReady() { - return this.status.value === LegacyProjectImportComponentStatuses.Completed; + return this.status.isReady(); } hasFailed() { - return this.status.value === LegacyProjectImportComponentStatuses.Failed; + return this.status.hasFailed(); } complete() { - this.status.markAsCompleted(); + this.status = this.status.markAsCompleted(); } markAsFailed() { - this.status.markAsFailed(); + this.status = this.status.markAsFailed(); } toSnapshot(): LegacyProjectImportComponentSnapshot { return { id: this.id.value, order: this.order, - status: this.status.value, + status: this.status.toSnapshot(), kind: this.kind, - resourceId: this.resourceId.value, archiveLocation: this.archiveLocation?.value, }; } diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.snapshot.ts similarity index 58% rename from api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts rename to api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.snapshot.ts index ea2367043b..88eb502480 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-projec-import.snapshot.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.snapshot.ts @@ -1,8 +1,12 @@ +import { LegacyProjectImportFileSnapshot } from '@marxan/legacy-project-import'; import { LegacyProjectImportComponentSnapshot } from './legacy-project-import-component.snapshot'; export interface LegacyProjectImportSnapshot { readonly id: string; readonly projectId: string; + readonly scenarioId: string; readonly ownerId: string; + readonly isAcceptingFiles: boolean; readonly pieces: LegacyProjectImportComponentSnapshot[]; + readonly files: LegacyProjectImportFileSnapshot[]; } diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts index 436e3c1d08..f1a33d612e 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts @@ -1,17 +1,21 @@ -import { ResourceId } from '@marxan/cloning/domain'; +import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain'; import { UserId } from '@marxan/domain-ids'; +import { + LegacyProjectImportFile, + LegacyProjectImportFileType, + LegacyProjectImportPiece, +} from '@marxan/legacy-project-import'; import { AggregateRoot } from '@nestjs/cqrs'; -import { Either, left, right } from 'fp-ts/Either'; +import { Either, isLeft, left, right } from 'fp-ts/Either'; import { AllLegacyProjectPiecesImported } from '../events/all-legacy-project-import-pieces-imported.event'; import { LegacyProjectImportBatchFailed } from '../events/legacy-project-import-batch-failed.event'; import { LegacyProjectImportPieceImported } from '../events/legacy-project-import-piece-imported.event'; import { LegacyProjectImportPieceRequested } from '../events/legacy-project-import-piece-requested.event'; import { LegacyProjectImportRequested } from '../events/legacy-project-import-requested.event'; -import { LegacyProjectImportSnapshot } from './legacy-projec-import.snapshot'; import { LegacyProjectImportComponent } from './legacy-project-import-component'; import { LegacyProjectImportComponentId } from './legacy-project-import-component.id'; import { LegacyProjectImportId } from './legacy-project-import.id'; - +import { LegacyProjectImportSnapshot } from './legacy-project-import.snapshot'; export const legacyProjectImportComponentNotFound = Symbol( `legacy project import component not found`, ); @@ -21,6 +25,12 @@ export const legacyProjectImportComponentAlreadyCompleted = Symbol( export const legacyProjectImportComponentAlreadyFailed = Symbol( `legacy project import component already failed`, ); +export const legacyProjectImportDuplicateFile = Symbol( + `legacy project import already has this file`, +); +export const legacyProjectImportMissingRequiredFile = Symbol( + `legacy project import missing required file`, +); export type CompleteLegacyProjectImportPieceSuccess = true; export type CompleteLegacyProjectImportPieceErrors = @@ -30,12 +40,19 @@ export type MarkLegacyProjectImportPieceAsFailedErrors = | typeof legacyProjectImportComponentNotFound | typeof legacyProjectImportComponentAlreadyFailed; +export type AddFileToLegacyProjectImportErrors = typeof legacyProjectImportDuplicateFile; + +export type GenerateLegacyProjectImportPiecesErrors = typeof legacyProjectImportMissingRequiredFile; + export class LegacyProjectImport extends AggregateRoot { private constructor( readonly id: LegacyProjectImportId, private readonly projectId: ResourceId, + private readonly scenarioId: ResourceId, private readonly ownerId: UserId, - private readonly pieces: LegacyProjectImportComponent[], + private isAcceptingFiles: boolean = true, + private pieces: LegacyProjectImportComponent[] = [], + private readonly files: LegacyProjectImportFile[] = [], ) { super(); } @@ -46,21 +63,24 @@ export class LegacyProjectImport extends AggregateRoot { return new LegacyProjectImport( new LegacyProjectImportId(snapshot.id), new ResourceId(snapshot.projectId), + new ResourceId(snapshot.scenarioId), new UserId(snapshot.ownerId), + snapshot.isAcceptingFiles, snapshot.pieces.map(LegacyProjectImportComponent.fromSnapshot), + snapshot.files.map(LegacyProjectImportFile.fromSnapshot), ); } static newOne( projectId: ResourceId, + scenarioId: ResourceId, ownerId: UserId, - pieces: LegacyProjectImportComponent[], ): LegacyProjectImport { return new LegacyProjectImport( LegacyProjectImportId.create(), projectId, + scenarioId, ownerId, - pieces, ); } @@ -92,9 +112,77 @@ export class LegacyProjectImport extends AggregateRoot { .some((piece) => piece.hasFailed()); } - start(): void { + private areRequiredFilesPresent(): boolean { + const requiredFilesTypes = [ + LegacyProjectImportFileType.PlanningGridShapefile, + LegacyProjectImportFileType.InputDat, + LegacyProjectImportFileType.PuDat, + LegacyProjectImportFileType.PuvsprDat, + LegacyProjectImportFileType.SpecDat, + ]; + const filesTypes = this.files.map((file) => file.type); + + return requiredFilesTypes.every((type) => filesTypes.includes(type)); + } + + private generatePieces(): Either< + GenerateLegacyProjectImportPiecesErrors, + LegacyProjectImportComponent[] + > { + const areRequiredFilesPresent = this.areRequiredFilesPresent(); + if (!areRequiredFilesPresent) + return left(legacyProjectImportMissingRequiredFile); + + const piecesData: { + kind: LegacyProjectImportPiece; + location?: ArchiveLocation; + }[] = [ + { kind: LegacyProjectImportPiece.PlanningGrid }, + { kind: LegacyProjectImportPiece.ScenarioPusData }, + { kind: LegacyProjectImportPiece.Features }, + { kind: LegacyProjectImportPiece.FeaturesSpecification }, + ]; + + const solutionsFile = this.files.find( + (file) => file.type === LegacyProjectImportFileType.Output, + ); + if (solutionsFile) { + piecesData.push({ + kind: LegacyProjectImportPiece.Solutions, + location: solutionsFile.location, + }); + } + + this.files + .filter( + (file) => file.type === LegacyProjectImportFileType.FeatureShapefile, + ) + .forEach((featureShapefile) => { + piecesData.push({ + kind: LegacyProjectImportPiece.FeatureShapefile, + location: featureShapefile.location, + }); + }); + + return right( + piecesData.map(({ kind, location }) => + LegacyProjectImportComponent.newOne(kind, location), + ), + ); + } + + start(): Either { + this.isAcceptingFiles = false; + const piecesOrError = this.generatePieces(); + + if (isLeft(piecesOrError)) return piecesOrError; + + this.pieces = piecesOrError.right; + this.apply(new LegacyProjectImportRequested(this.id, this.projectId)); this.requestFirstBatch(); + + return right(true); } markPieceAsFailed( @@ -163,9 +251,30 @@ export class LegacyProjectImport extends AggregateRoot { toSnapshot(): LegacyProjectImportSnapshot { return { id: this.id.value, - pieces: this.pieces.map((piece) => piece.toSnapshot()), projectId: this.projectId.value, + scenarioId: this.scenarioId.value, ownerId: this.ownerId.value, + isAcceptingFiles: this.isAcceptingFiles, + pieces: this.pieces.map((piece) => piece.toSnapshot()), + files: this.files.map((file) => file.toSnapshot()), }; } + + addFile( + file: LegacyProjectImportFile, + ): Either { + const isFeatureShapefileFile = + file.type === LegacyProjectImportFileType.FeatureShapefile; + const fileTypeAlreadyPresent = this.files.some( + (el) => el.type === file.type, + ); + + if (fileTypeAlreadyPresent && !isFeatureShapefileFile) { + return left(legacyProjectImportDuplicateFile); + } + + this.files.push(file); + + return right(true); + } } diff --git a/api/libs/legacy-project-import/src/domain/index.ts b/api/libs/legacy-project-import/src/domain/index.ts new file mode 100644 index 0000000000..340d30157e --- /dev/null +++ b/api/libs/legacy-project-import/src/domain/index.ts @@ -0,0 +1,8 @@ +export { LegacyProjectImportFile } from './legacy-project-import-file'; +export { LegacyProjectImportFileType } from './legacy-project-import-file-type'; +export { LegacyProjectImportFileId } from './legacy-project-import-file.id'; +export { LegacyProjectImportFileSnapshot } from './legacy-project-import-file.snapshot'; +export { + LegacyProjectImportPiece, + LegacyProjectImportPieceOrderResolver, +} from './legacy-project-import-piece'; diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts new file mode 100644 index 0000000000..e84d20d593 --- /dev/null +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts @@ -0,0 +1,9 @@ +export enum LegacyProjectImportFileType { + PlanningGridShapefile = 'planning-grid-shapefile', + InputDat = 'input-dat', + PuDat = 'pu-dat', + SpecDat = 'spec-dat', + PuvsprDat = 'puvspr-dat', + Output = 'output', + FeatureShapefile = 'feature-shapefile', +} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts new file mode 100644 index 0000000000..86580be601 --- /dev/null +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts @@ -0,0 +1,14 @@ +import { v4 } from 'uuid'; + +export class LegacyProjectImportFileId { + private readonly _token = 'legacy-project-import-file-id'; + constructor(public readonly value: string) {} + + static create(): LegacyProjectImportFileId { + return new LegacyProjectImportFileId(v4()); + } + + equals(other: LegacyProjectImportFileId): boolean { + return this.value === other.value; + } +} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts new file mode 100644 index 0000000000..ac4e0e5771 --- /dev/null +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts @@ -0,0 +1,7 @@ +import { LegacyProjectImportFileType } from './legacy-project-import-file-type'; + +export interface LegacyProjectImportFileSnapshot { + id: string; + location: string; + type: LegacyProjectImportFileType; +} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts new file mode 100644 index 0000000000..ffed2b25fb --- /dev/null +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts @@ -0,0 +1,30 @@ +import { ArchiveLocation } from '../../../cloning/src/domain'; +import { LegacyProjectImportFileType } from './legacy-project-import-file-type'; +import { LegacyProjectImportFileId } from './legacy-project-import-file.id'; +import { LegacyProjectImportFileSnapshot } from './legacy-project-import-file.snapshot'; + +export class LegacyProjectImportFile { + constructor( + readonly id: LegacyProjectImportFileId, + readonly type: LegacyProjectImportFileType, + readonly location: ArchiveLocation, + ) {} + + toSnapshot(): LegacyProjectImportFileSnapshot { + return { + id: this.id.value, + location: this.location.value, + type: this.type, + }; + } + + static fromSnapshot( + snapshot: LegacyProjectImportFileSnapshot, + ): LegacyProjectImportFile { + return new LegacyProjectImportFile( + new LegacyProjectImportFileId(snapshot.id), + snapshot.type, + new ArchiveLocation(snapshot.location), + ); + } +} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts index 3f3c1a998d..3cd994dedb 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts @@ -1,9 +1,9 @@ export enum LegacyProjectImportPiece { PlanningGrid = 'planning-grid', - ShapefileFeature = 'shapefile-feature', - NonShapefileFeatures = 'non-shapefile-features', + FeatureShapefile = 'feature-shapefile', + Features = 'features', ScenarioPusData = 'scenario-pus-data', - FeatureSpecification = 'features-specification', + FeaturesSpecification = 'features-specification', Solutions = 'solutions', } @@ -14,10 +14,10 @@ export class LegacyProjectImportPieceOrderResolver { > = { // TODO Establish proper order for pieces [LegacyProjectImportPiece.PlanningGrid]: 0, - [LegacyProjectImportPiece.ShapefileFeature]: 1, - [LegacyProjectImportPiece.NonShapefileFeatures]: 1, + [LegacyProjectImportPiece.Features]: 1, [LegacyProjectImportPiece.ScenarioPusData]: 1, - [LegacyProjectImportPiece.FeatureSpecification]: 2, + [LegacyProjectImportPiece.FeatureShapefile]: 2, + [LegacyProjectImportPiece.FeaturesSpecification]: 2, [LegacyProjectImportPiece.Solutions]: 2, }; diff --git a/api/libs/legacy-project-import/src/index.ts b/api/libs/legacy-project-import/src/index.ts index 230898d960..3aa9636e40 100644 --- a/api/libs/legacy-project-import/src/index.ts +++ b/api/libs/legacy-project-import/src/index.ts @@ -1,8 +1,6 @@ export { legacyProjectImportQueueName } from './legacy-project-import-queue-name'; export { LegacyProjectImportJobInput } from './job-input'; export { LegacyProjectImportJobOutput } from './job-output'; -export { - LegacyProjectImportPiece, - LegacyProjectImportPieceOrderResolver, -} from './domain/legacy-project-import-piece'; +export * from './domain'; +export { LegacyProjectImportFileType } from './domain/legacy-project-import-file-type'; export { LegacyProjectImportPieceProcessor } from './legacy-project-import-piece-processor.port'; diff --git a/api/libs/legacy-project-import/src/job-input.ts b/api/libs/legacy-project-import/src/job-input.ts index 5187d7afd7..7385be1638 100644 --- a/api/libs/legacy-project-import/src/job-input.ts +++ b/api/libs/legacy-project-import/src/job-input.ts @@ -1,9 +1,11 @@ +import { LegacyProjectImportFileSnapshot } from './domain'; import { LegacyProjectImportPiece } from './domain/legacy-project-import-piece'; export interface LegacyProjectImportJobInput { readonly legacyProjectImportId: string; readonly pieceId: string; - readonly resourceId: string; + readonly files: LegacyProjectImportFileSnapshot[]; readonly projectId: string; + readonly scenarioId: string; readonly piece: LegacyProjectImportPiece; } diff --git a/api/libs/legacy-project-import/src/job-output.ts b/api/libs/legacy-project-import/src/job-output.ts index 482ce6d57f..30c4882caf 100644 --- a/api/libs/legacy-project-import/src/job-output.ts +++ b/api/libs/legacy-project-import/src/job-output.ts @@ -1,9 +1,11 @@ +import { LegacyProjectImportFileSnapshot } from './domain/legacy-project-import-file.snapshot'; import { LegacyProjectImportPiece } from './domain/legacy-project-import-piece'; export interface LegacyProjectImportJobOutput { readonly legacyProjectImportId: string; - readonly projectId: string; - readonly resourceId: string; readonly pieceId: string; + readonly files: LegacyProjectImportFileSnapshot[]; + readonly projectId: string; + readonly scenarioId: string; readonly piece: LegacyProjectImportPiece; } From a2d145265ee61d7f223c6950ec01179777ec64a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Thu, 12 May 2022 08:58:06 +0100 Subject: [PATCH 4/6] ref: legacy project import file entity --- .../legacy-project-import.ts | 15 ++++++++++++++- .../legacy-project-import/src/domain/index.ts | 1 - .../src/domain/legacy-project-import-file.id.ts | 14 -------------- .../domain/legacy-project-import-file.snapshot.ts | 1 - .../src/domain/legacy-project-import-file.ts | 4 ---- 5 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts index f1a33d612e..5e412af390 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts @@ -28,6 +28,9 @@ export const legacyProjectImportComponentAlreadyFailed = Symbol( export const legacyProjectImportDuplicateFile = Symbol( `legacy project import already has this file`, ); +export const legacyProjectImportDuplicateFileType = Symbol( + `legacy project import already has this file type`, +); export const legacyProjectImportMissingRequiredFile = Symbol( `legacy project import missing required file`, ); @@ -40,7 +43,9 @@ export type MarkLegacyProjectImportPieceAsFailedErrors = | typeof legacyProjectImportComponentNotFound | typeof legacyProjectImportComponentAlreadyFailed; -export type AddFileToLegacyProjectImportErrors = typeof legacyProjectImportDuplicateFile; +export type AddFileToLegacyProjectImportErrors = + | typeof legacyProjectImportDuplicateFile + | typeof legacyProjectImportDuplicateFileType; export type GenerateLegacyProjectImportPiecesErrors = typeof legacyProjectImportMissingRequiredFile; @@ -270,6 +275,14 @@ export class LegacyProjectImport extends AggregateRoot { ); if (fileTypeAlreadyPresent && !isFeatureShapefileFile) { + return left(legacyProjectImportDuplicateFileType); + } + + const duplicateArchiveLocation = this.files.some( + (el) => el.location === file.location, + ); + + if (duplicateArchiveLocation) { return left(legacyProjectImportDuplicateFile); } diff --git a/api/libs/legacy-project-import/src/domain/index.ts b/api/libs/legacy-project-import/src/domain/index.ts index 340d30157e..8df881b1bd 100644 --- a/api/libs/legacy-project-import/src/domain/index.ts +++ b/api/libs/legacy-project-import/src/domain/index.ts @@ -1,6 +1,5 @@ export { LegacyProjectImportFile } from './legacy-project-import-file'; export { LegacyProjectImportFileType } from './legacy-project-import-file-type'; -export { LegacyProjectImportFileId } from './legacy-project-import-file.id'; export { LegacyProjectImportFileSnapshot } from './legacy-project-import-file.snapshot'; export { LegacyProjectImportPiece, diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts deleted file mode 100644 index 86580be601..0000000000 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.id.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { v4 } from 'uuid'; - -export class LegacyProjectImportFileId { - private readonly _token = 'legacy-project-import-file-id'; - constructor(public readonly value: string) {} - - static create(): LegacyProjectImportFileId { - return new LegacyProjectImportFileId(v4()); - } - - equals(other: LegacyProjectImportFileId): boolean { - return this.value === other.value; - } -} diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts index ac4e0e5771..669cd4241b 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.snapshot.ts @@ -1,7 +1,6 @@ import { LegacyProjectImportFileType } from './legacy-project-import-file-type'; export interface LegacyProjectImportFileSnapshot { - id: string; location: string; type: LegacyProjectImportFileType; } diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts index ffed2b25fb..6a040b8ab4 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file.ts @@ -1,18 +1,15 @@ import { ArchiveLocation } from '../../../cloning/src/domain'; import { LegacyProjectImportFileType } from './legacy-project-import-file-type'; -import { LegacyProjectImportFileId } from './legacy-project-import-file.id'; import { LegacyProjectImportFileSnapshot } from './legacy-project-import-file.snapshot'; export class LegacyProjectImportFile { constructor( - readonly id: LegacyProjectImportFileId, readonly type: LegacyProjectImportFileType, readonly location: ArchiveLocation, ) {} toSnapshot(): LegacyProjectImportFileSnapshot { return { - id: this.id.value, location: this.location.value, type: this.type, }; @@ -22,7 +19,6 @@ export class LegacyProjectImportFile { snapshot: LegacyProjectImportFileSnapshot, ): LegacyProjectImportFile { return new LegacyProjectImportFile( - new LegacyProjectImportFileId(snapshot.id), snapshot.type, new ArchiveLocation(snapshot.location), ); From 6d3cf744b6f9af362ac70761dc5ffe8fd84209c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Thu, 12 May 2022 09:03:18 +0100 Subject: [PATCH 5/6] ref: legacy project import component --- .../legacy-project-import-component.snapshot.ts | 2 ++ .../legacy-project-import-component.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts index c7f0bc538d..81b303e4b3 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts @@ -7,4 +7,6 @@ export interface LegacyProjectImportComponentSnapshot { readonly order: number; readonly archiveLocation?: string; readonly status: LegacyProjectImportComponentStatuses; + readonly errors: string[]; + readonly warnings: string[]; } diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts index 6bbd493b46..74ce477b57 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts @@ -14,6 +14,8 @@ export class LegacyProjectImportComponent { readonly order: number, readonly archiveLocation?: ArchiveLocation, private status: LegacyProjectImportComponentStatus = LegacyProjectImportComponentStatus.create(), + private errors: string[] = [], + private warnings: string[] = [], ) {} static fromSnapshot(snapshot: LegacyProjectImportComponentSnapshot) { @@ -48,12 +50,15 @@ export class LegacyProjectImportComponent { return this.status.hasFailed(); } - complete() { + complete(warnings: string[] = []) { this.status = this.status.markAsCompleted(); + this.warnings.push(...warnings); } - markAsFailed() { + markAsFailed(errors: string[] = [], warnings: string[] = []) { this.status = this.status.markAsFailed(); + this.errors.push(...errors); + this.warnings.push(...warnings); } toSnapshot(): LegacyProjectImportComponentSnapshot { @@ -63,6 +68,8 @@ export class LegacyProjectImportComponent { status: this.status.toSnapshot(), kind: this.kind, archiveLocation: this.archiveLocation?.value, + errors: this.errors, + warnings: this.warnings, }; } } From 7006e807689cd3a142298b95382bcb0d73e78f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daute=20Rodr=C3=ADguez=20Rodr=C3=ADguez?= Date: Thu, 12 May 2022 10:06:47 +0100 Subject: [PATCH 6/6] ref: legacy project import use case simplification --- ...egacy-project-import-component.snapshot.ts | 1 - .../legacy-project-import-component.ts | 11 +---- .../legacy-project-import.ts | 47 +++++++------------ .../domain/legacy-project-import-file-type.ts | 1 - .../src/domain/legacy-project-import-piece.ts | 2 - 5 files changed, 17 insertions(+), 45 deletions(-) diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts index 81b303e4b3..d5e1dad325 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.snapshot.ts @@ -5,7 +5,6 @@ export interface LegacyProjectImportComponentSnapshot { readonly id: string; readonly kind: LegacyProjectImportPiece; readonly order: number; - readonly archiveLocation?: string; readonly status: LegacyProjectImportComponentStatuses; readonly errors: string[]; readonly warnings: string[]; diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts index 74ce477b57..1543834cde 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import-component.ts @@ -12,7 +12,6 @@ export class LegacyProjectImportComponent { readonly id: LegacyProjectImportComponentId, readonly kind: LegacyProjectImportPiece, readonly order: number, - readonly archiveLocation?: ArchiveLocation, private status: LegacyProjectImportComponentStatus = LegacyProjectImportComponentStatus.create(), private errors: string[] = [], private warnings: string[] = [], @@ -23,22 +22,15 @@ export class LegacyProjectImportComponent { new LegacyProjectImportComponentId(snapshot.id), snapshot.kind, snapshot.order, - snapshot.archiveLocation - ? new ArchiveLocation(snapshot.archiveLocation) - : undefined, new LegacyProjectImportComponentStatus(snapshot.status), ); } - static newOne( - kind: LegacyProjectImportPiece, - archiveLocation?: ArchiveLocation, - ): LegacyProjectImportComponent { + static newOne(kind: LegacyProjectImportPiece): LegacyProjectImportComponent { return new LegacyProjectImportComponent( LegacyProjectImportComponentId.create(), kind, LegacyProjectImportPieceOrderResolver.resolveFor(kind), - archiveLocation, ); } @@ -67,7 +59,6 @@ export class LegacyProjectImportComponent { order: this.order, status: this.status.toSnapshot(), kind: this.kind, - archiveLocation: this.archiveLocation?.value, errors: this.errors, warnings: this.warnings, }; diff --git a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts index 5e412af390..5aac329e1e 100644 --- a/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts +++ b/api/apps/api/src/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.ts @@ -138,42 +138,29 @@ export class LegacyProjectImport extends AggregateRoot { if (!areRequiredFilesPresent) return left(legacyProjectImportMissingRequiredFile); - const piecesData: { - kind: LegacyProjectImportPiece; - location?: ArchiveLocation; - }[] = [ - { kind: LegacyProjectImportPiece.PlanningGrid }, - { kind: LegacyProjectImportPiece.ScenarioPusData }, - { kind: LegacyProjectImportPiece.Features }, - { kind: LegacyProjectImportPiece.FeaturesSpecification }, + const pieces: LegacyProjectImportComponent[] = [ + LegacyProjectImportComponent.newOne( + LegacyProjectImportPiece.PlanningGrid, + ), + LegacyProjectImportComponent.newOne( + LegacyProjectImportPiece.ScenarioPusData, + ), + LegacyProjectImportComponent.newOne(LegacyProjectImportPiece.Features), + LegacyProjectImportComponent.newOne( + LegacyProjectImportPiece.FeaturesSpecification, + ), ]; const solutionsFile = this.files.find( (file) => file.type === LegacyProjectImportFileType.Output, ); if (solutionsFile) { - piecesData.push({ - kind: LegacyProjectImportPiece.Solutions, - location: solutionsFile.location, - }); + pieces.push( + LegacyProjectImportComponent.newOne(LegacyProjectImportPiece.Solutions), + ); } - this.files - .filter( - (file) => file.type === LegacyProjectImportFileType.FeatureShapefile, - ) - .forEach((featureShapefile) => { - piecesData.push({ - kind: LegacyProjectImportPiece.FeatureShapefile, - location: featureShapefile.location, - }); - }); - - return right( - piecesData.map(({ kind, location }) => - LegacyProjectImportComponent.newOne(kind, location), - ), - ); + return right(pieces); } start(): Either { @@ -268,13 +255,11 @@ export class LegacyProjectImport extends AggregateRoot { addFile( file: LegacyProjectImportFile, ): Either { - const isFeatureShapefileFile = - file.type === LegacyProjectImportFileType.FeatureShapefile; const fileTypeAlreadyPresent = this.files.some( (el) => el.type === file.type, ); - if (fileTypeAlreadyPresent && !isFeatureShapefileFile) { + if (fileTypeAlreadyPresent) { return left(legacyProjectImportDuplicateFileType); } diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts index e84d20d593..c3117844dc 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-file-type.ts @@ -5,5 +5,4 @@ export enum LegacyProjectImportFileType { SpecDat = 'spec-dat', PuvsprDat = 'puvspr-dat', Output = 'output', - FeatureShapefile = 'feature-shapefile', } diff --git a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts index 3cd994dedb..9a8637f1f9 100644 --- a/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts +++ b/api/libs/legacy-project-import/src/domain/legacy-project-import-piece.ts @@ -1,6 +1,5 @@ export enum LegacyProjectImportPiece { PlanningGrid = 'planning-grid', - FeatureShapefile = 'feature-shapefile', Features = 'features', ScenarioPusData = 'scenario-pus-data', FeaturesSpecification = 'features-specification', @@ -16,7 +15,6 @@ export class LegacyProjectImportPieceOrderResolver { [LegacyProjectImportPiece.PlanningGrid]: 0, [LegacyProjectImportPiece.Features]: 1, [LegacyProjectImportPiece.ScenarioPusData]: 1, - [LegacyProjectImportPiece.FeatureShapefile]: 2, [LegacyProjectImportPiece.FeaturesSpecification]: 2, [LegacyProjectImportPiece.Solutions]: 2, };