From 561042750c8aaa7c763b10c769bae6dad01b1373 Mon Sep 17 00:00:00 2001 From: kgajowy Date: Thu, 7 Oct 2021 15:19:23 +0200 Subject: [PATCH] feat(clone): domain --- .../application/export-repository.port.ts | 8 ++ .../export/application/request-export.ts | 22 ++++++ .../application/resource-pieces.port.ts | 7 ++ .../domain/events/archive-ready.event.ts | 11 +++ .../events/clone-part-requested.event.ts | 15 ++++ .../events/clone-parts-finished.event.ts | 6 ++ .../export/domain/export/archive-location.ts | 6 ++ .../domain/export/clone-part/clone-part.ts | 36 +++++++++ .../export/clone-part/piece-location.ts | 6 ++ .../domain/export/clone-part/piece.id.ts | 3 + .../clone/export/domain/export/export.id.ts | 3 + .../clone/export/domain/export/export.ts | 78 +++++++++++++++++++ .../clone/export/domain/export/resource.id.ts | 3 + .../export/domain/export/resource.kind.ts | 4 + .../clone/shared-kernel/clone-piece.ts | 9 +++ 15 files changed, 217 insertions(+) create mode 100644 api/apps/api/src/modules/clone/export/application/export-repository.port.ts create mode 100644 api/apps/api/src/modules/clone/export/application/request-export.ts create mode 100644 api/apps/api/src/modules/clone/export/application/resource-pieces.port.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/events/archive-ready.event.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/events/clone-part-requested.event.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/events/clone-parts-finished.event.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/archive-location.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/clone-part/clone-part.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/clone-part/piece-location.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/clone-part/piece.id.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/export.id.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/export.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/resource.id.ts create mode 100644 api/apps/api/src/modules/clone/export/domain/export/resource.kind.ts create mode 100644 api/apps/api/src/modules/clone/shared-kernel/clone-piece.ts diff --git a/api/apps/api/src/modules/clone/export/application/export-repository.port.ts b/api/apps/api/src/modules/clone/export/application/export-repository.port.ts new file mode 100644 index 0000000000..81cff24766 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/application/export-repository.port.ts @@ -0,0 +1,8 @@ +import { Export } from '../domain/export/export'; +import { ResourceId } from '../domain/export/resource.id'; + +export abstract class ExportRepository { + abstract save(exportInstance: Export): Promise; + + abstract find(projectId: ResourceId): Promise; +} diff --git a/api/apps/api/src/modules/clone/export/application/request-export.ts b/api/apps/api/src/modules/clone/export/application/request-export.ts new file mode 100644 index 0000000000..33ebae410b --- /dev/null +++ b/api/apps/api/src/modules/clone/export/application/request-export.ts @@ -0,0 +1,22 @@ +import { ResourceId } from '../domain/export/resource.id'; +import { ResourceKind } from '../domain/export/resource.kind'; +import { ClonePart } from '../domain/export/clone-part/clone-part'; +import { Export } from '../domain/export/export'; +import { ExportId } from '../domain/export/export.id'; + +import { ExportRepository } from './export-repository.port'; +import { ResourcePieces } from './resource-pieces.port'; + +export class RequestExport { + constructor( + private readonly resourcePieces: ResourcePieces, + private readonly exportRepository: ExportRepository, + ) {} + + async export(id: ResourceId, kind: ResourceKind): Promise { + const parts: ClonePart[] = await this.resourcePieces.resolveFor(id, kind); + const exportInstance = Export.project(id, parts); + await this.exportRepository.save(exportInstance); + return exportInstance.id; + } +} diff --git a/api/apps/api/src/modules/clone/export/application/resource-pieces.port.ts b/api/apps/api/src/modules/clone/export/application/resource-pieces.port.ts new file mode 100644 index 0000000000..3c7f719e7d --- /dev/null +++ b/api/apps/api/src/modules/clone/export/application/resource-pieces.port.ts @@ -0,0 +1,7 @@ +import { ResourceId } from '../domain/export/resource.id'; +import { ResourceKind } from '../domain/export/resource.kind'; +import { ClonePart } from '../domain/export/clone-part/clone-part'; + +export abstract class ResourcePieces { + abstract resolveFor(id: ResourceId, kind: ResourceKind): Promise; +} diff --git a/api/apps/api/src/modules/clone/export/domain/events/archive-ready.event.ts b/api/apps/api/src/modules/clone/export/domain/events/archive-ready.event.ts new file mode 100644 index 0000000000..30d152886f --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/events/archive-ready.event.ts @@ -0,0 +1,11 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { ExportId } from '../export/export.id'; +import { ArchiveLocation } from '../export/archive-location'; + +export class ArchiveReady implements IEvent { + constructor( + public readonly exportId: ExportId, + public readonly archiveLocation: ArchiveLocation, + ) {} +} diff --git a/api/apps/api/src/modules/clone/export/domain/events/clone-part-requested.event.ts b/api/apps/api/src/modules/clone/export/domain/events/clone-part-requested.event.ts new file mode 100644 index 0000000000..30ce8d18d4 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/events/clone-part-requested.event.ts @@ -0,0 +1,15 @@ +import { IEvent } from '@nestjs/cqrs'; + +import { ExportId } from '../export/export.id'; +import { PieceId } from '../export/clone-part/piece.id'; +import { ResourceId } from '../export/resource.id'; +import { ClonePiece } from '../../../shared-kernel/clone-piece'; + +export class ClonePartRequested implements IEvent { + constructor( + public readonly exportId: ExportId, + public readonly pieceId: PieceId, + public readonly resourceId: ResourceId, + public readonly piece: ClonePiece, + ) {} +} diff --git a/api/apps/api/src/modules/clone/export/domain/events/clone-parts-finished.event.ts b/api/apps/api/src/modules/clone/export/domain/events/clone-parts-finished.event.ts new file mode 100644 index 0000000000..5602703026 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/events/clone-parts-finished.event.ts @@ -0,0 +1,6 @@ +import { IEvent } from '@nestjs/cqrs'; +import { ExportId } from '../export/export.id'; + +export class ClonePartsFinished implements IEvent { + constructor(public readonly exportId: ExportId) {} +} diff --git a/api/apps/api/src/modules/clone/export/domain/export/archive-location.ts b/api/apps/api/src/modules/clone/export/domain/export/archive-location.ts new file mode 100644 index 0000000000..ce10293469 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/archive-location.ts @@ -0,0 +1,6 @@ +import { TinyTypeOf } from 'tiny-types'; + +/** + * a URI pointing at exported project/scenario archive + */ +export class ArchiveLocation extends TinyTypeOf() {} diff --git a/api/apps/api/src/modules/clone/export/domain/export/clone-part/clone-part.ts b/api/apps/api/src/modules/clone/export/domain/export/clone-part/clone-part.ts new file mode 100644 index 0000000000..17bd648c2c --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/clone-part/clone-part.ts @@ -0,0 +1,36 @@ +import { v4 } from 'uuid'; +import { ClonePiece } from '../../../../shared-kernel/clone-piece'; +import { ResourceId } from '../resource.id'; +import { PieceId } from './piece.id'; +import { PieceLocation } from './piece-location'; + +export class ClonePart { + private constructor( + readonly id: PieceId, + readonly piece: ClonePiece, + readonly resourceId: ResourceId, + private finished: boolean = false, + private uri?: PieceLocation, + ) {} + + static newOne(resourceId: ResourceId, piece: ClonePiece): ClonePart { + return new ClonePart(new PieceId(v4()), piece, resourceId); + } + + finish(location: PieceLocation) { + this.finished = true; + this.uri = location; + } + + isReady() { + return this.finished; + } + + // TODO + toSnapshot() { + return {}; + } + + // TODO + static fromSnapshot() {} +} diff --git a/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece-location.ts b/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece-location.ts new file mode 100644 index 0000000000..daef4ea59c --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece-location.ts @@ -0,0 +1,6 @@ +import { TinyTypeOf } from 'tiny-types'; + +/** + * a URI pointing at the given Piece, whatever format it is + */ +export class PieceLocation extends TinyTypeOf() {} diff --git a/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece.id.ts b/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece.id.ts new file mode 100644 index 0000000000..73bda7880b --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/clone-part/piece.id.ts @@ -0,0 +1,3 @@ +import { TinyTypeOf } from 'tiny-types'; + +export class PieceId extends TinyTypeOf() {} diff --git a/api/apps/api/src/modules/clone/export/domain/export/export.id.ts b/api/apps/api/src/modules/clone/export/domain/export/export.id.ts new file mode 100644 index 0000000000..d341e125f3 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/export.id.ts @@ -0,0 +1,3 @@ +import { TinyTypeOf } from 'tiny-types'; + +export class ExportId extends TinyTypeOf() {} diff --git a/api/apps/api/src/modules/clone/export/domain/export/export.ts b/api/apps/api/src/modules/clone/export/domain/export/export.ts new file mode 100644 index 0000000000..86f60f3120 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/export.ts @@ -0,0 +1,78 @@ +import { v4 } from 'uuid'; +import { AggregateRoot } from '@nestjs/cqrs'; + +import { ResourceKind } from './resource.kind'; +import { ExportId } from './export.id'; +import { ResourceId } from './resource.id'; +import { ArchiveLocation } from './archive-location'; + +import { ClonePartRequested } from '../events/clone-part-requested.event'; +import { ClonePartsFinished } from '../events/clone-parts-finished.event'; +import { ArchiveReady } from '../events/archive-ready.event'; + +import { PieceLocation } from './clone-part/piece-location'; +import { ClonePart } from './clone-part/clone-part'; +import { PieceId } from './clone-part/piece.id'; + +export class Export extends AggregateRoot { + private constructor( + public readonly id: ExportId, + public readonly resourceId: ResourceId, + public readonly resourceKind: ResourceKind, + public readonly pieces: ClonePart[], + private archiveLocation?: ArchiveLocation, + ) { + super(); + } + + static project(id: ResourceId, parts: ClonePart[]): Export { + const exportRequest = new Export( + new ExportId(v4()), + id, + ResourceKind.Project, + parts, + ); + + exportRequest.apply( + parts + .filter((part) => !part.isReady()) + .map( + (part) => + new ClonePartRequested( + exportRequest.id, + part.id, + part.resourceId, + part.piece, + ), + ), + ); + + return exportRequest; + } + + completePiece(id: PieceId, pieceLocation: PieceLocation) { + const piece = this.pieces.find((piece) => piece.id.equals(id)); + + // TODO throw + piece?.finish(pieceLocation); + + if (this.#allPiecesReady()) { + this.apply(new ClonePartsFinished(this.id)); + } + } + + complete(archiveLocation: ArchiveLocation) { + this.archiveLocation = archiveLocation; + this.apply(new ArchiveReady(this.id, this.archiveLocation)); + } + + toSnapshot() { + // + } + + static fromSnapshot() { + // + } + + #allPiecesReady = () => this.pieces.every((piece) => piece.isReady()); +} diff --git a/api/apps/api/src/modules/clone/export/domain/export/resource.id.ts b/api/apps/api/src/modules/clone/export/domain/export/resource.id.ts new file mode 100644 index 0000000000..8f0cc19ce3 --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/resource.id.ts @@ -0,0 +1,3 @@ +import { TinyTypeOf } from 'tiny-types'; + +export class ResourceId extends TinyTypeOf() {} diff --git a/api/apps/api/src/modules/clone/export/domain/export/resource.kind.ts b/api/apps/api/src/modules/clone/export/domain/export/resource.kind.ts new file mode 100644 index 0000000000..b2a836f71b --- /dev/null +++ b/api/apps/api/src/modules/clone/export/domain/export/resource.kind.ts @@ -0,0 +1,4 @@ +export enum ResourceKind { + Project = 'project', + Scenario = 'scenario', +} diff --git a/api/apps/api/src/modules/clone/shared-kernel/clone-piece.ts b/api/apps/api/src/modules/clone/shared-kernel/clone-piece.ts new file mode 100644 index 0000000000..87d209b0a5 --- /dev/null +++ b/api/apps/api/src/modules/clone/shared-kernel/clone-piece.ts @@ -0,0 +1,9 @@ +export enum ClonePiece { + ProjectMetadata = 'project-metadata', + PlanningAreaGAdm = 'planning-area-gadm', + PlanningAreaCustom = 'planning-area-shapefile', + PlanningAreaGridConfig = 'planning-area-grid-config', + PlanningAreaGridCustom = 'planning-area-grid-shapefile', + ScenarioMetadata = 'scenario-metadata', + MarxanSettings = 'marxan-settings', +}