Skip to content

Commit

Permalink
feat(clone): export domain/app (#560)
Browse files Browse the repository at this point in the history
* feat(clone): domain

* feat(clone): domain snapshots and intentions return values

* feat(clone): rename clone-piece to export-component

* refactor(clone): replace hardcoded project export
  • Loading branch information
kgajowy authored Oct 25, 2021
1 parent 6f01e92 commit 80c2e39
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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<void>;

abstract find(projectId: ResourceId): Promise<Export | undefined>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { EventPublisher } from '@nestjs/cqrs';

import { ResourceId } from '../domain/export/resource.id';
import { ResourceKind } from '../domain/export/resource.kind';
import { ExportComponent } from '../domain/export/export-component/export-component';
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,
private readonly eventPublisher: EventPublisher,
) {}

async export(id: ResourceId, kind: ResourceKind): Promise<ExportId> {
const pieces: ExportComponent[] = await this.resourcePieces.resolveFor(
id,
kind,
);
const exportInstance = this.eventPublisher.mergeObjectContext(
Export.newOne(id, kind, pieces),
);
await this.exportRepository.save(exportInstance);

exportInstance.commit();

return exportInstance.id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ResourceId } from '../domain/export/resource.id';
import { ResourceKind } from '../domain/export/resource.kind';
import { ExportComponent } from '../domain/export/export-component/export-component';

export abstract class ResourcePieces {
abstract resolveFor(
id: ResourceId,
kind: ResourceKind,
): Promise<ExportComponent[]>;
}
Original file line number Diff line number Diff line change
@@ -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,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IEvent } from '@nestjs/cqrs';
import { ExportId } from '../export/export.id';

export class ExportComponentFinished implements IEvent {
constructor(public readonly exportId: ExportId) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IEvent } from '@nestjs/cqrs';

import { ExportId } from '../export/export.id';
import { ComponentId } from '../export/export-component/component.id';
import { ResourceId } from '../export/resource.id';
import { ClonePiece } from '../../../shared-kernel/clone-piece';

export class ExportComponentRequested implements IEvent {
constructor(
public readonly exportId: ExportId,
public readonly componentId: ComponentId,
public readonly resourceId: ResourceId,
public readonly piece: ClonePiece,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TinyTypeOf } from 'tiny-types';

/**
* a URI pointing at exported project/scenario archive
*/
export class ArchiveLocation extends TinyTypeOf<string>() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ClonePiece } from '../../../shared-kernel/clone-piece';

export interface ExportComponentSnapshot {
readonly id: string;
readonly piece: ClonePiece;
readonly resourceId: string;
readonly finished: boolean;
readonly uri?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TinyTypeOf } from 'tiny-types';

/**
* a URI pointing at the given Piece, whatever format it is
*/
export class ComponentLocation extends TinyTypeOf<string>() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TinyTypeOf } from 'tiny-types';

export class ComponentId extends TinyTypeOf<string>() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { v4 } from 'uuid';
import { ClonePiece } from '../../../../shared-kernel/clone-piece';

import { ResourceId } from '../resource.id';
import { ExportComponentSnapshot } from '../export-component.snapshot';

import { ComponentId } from './component.id';
import { ComponentLocation } from './component-location';

export class ExportComponent {
private constructor(
readonly id: ComponentId,
readonly piece: ClonePiece,
readonly resourceId: ResourceId,
private finished: boolean = false,
private uri?: ComponentLocation,
) {}

static newOne(resourceId: ResourceId, piece: ClonePiece): ExportComponent {
return new ExportComponent(new ComponentId(v4()), piece, resourceId);
}

finish(location: ComponentLocation) {
this.finished = true;
this.uri = location;
}

isReady() {
return this.finished;
}

toSnapshot(): ExportComponentSnapshot {
return {
id: this.id.value,
piece: this.piece,
resourceId: this.resourceId.value,
finished: this.finished,
uri: this.uri?.value,
};
}

static fromSnapshot(snapshot: ExportComponentSnapshot) {
return new ExportComponent(
new ComponentId(snapshot.id),
snapshot.piece,
new ResourceId(snapshot.resourceId),
snapshot.finished,
snapshot.uri ? new ComponentLocation(snapshot.uri) : undefined,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TinyTypeOf } from 'tiny-types';

export class ExportId extends TinyTypeOf<string>() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ResourceKind } from './resource.kind';
import { ExportComponentSnapshot } from './export-component.snapshot';

export interface ExportSnapshot {
id: string;
resourceId: string;
resourceKind: ResourceKind;
archiveLocation?: string;
exportPieces: ExportComponentSnapshot[];
}
106 changes: 106 additions & 0 deletions api/apps/api/src/modules/clone/export/domain/export/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { v4 } from 'uuid';
import { AggregateRoot } from '@nestjs/cqrs';
import { Either, left, right } from 'fp-ts/Either';

import { ResourceKind } from './resource.kind';
import { ExportId } from './export.id';
import { ResourceId } from './resource.id';
import { ArchiveLocation } from './archive-location';

import { ExportComponentRequested } from '../events/export-component-requested.event';
import { ExportComponentFinished } from '../events/export-component-finished.event';
import { ArchiveReady } from '../events/archive-ready.event';

import { ComponentLocation } from './export-component/component-location';
import { ExportComponent } from './export-component/export-component';
import { ComponentId } from './export-component/component.id';
import { ExportSnapshot } from './export.snapshot';

export const pieceNotFound = Symbol('export piece not found');
export const notReady = Symbol('some pieces of export are not yet ready');

export class Export extends AggregateRoot {
private constructor(
public readonly id: ExportId,
public readonly resourceId: ResourceId,
public readonly resourceKind: ResourceKind,
public readonly pieces: ExportComponent[],
private archiveLocation?: ArchiveLocation,
) {
super();
}

static newOne(
id: ResourceId,
kind: ResourceKind,
parts: ExportComponent[],
): Export {
const exportRequest = new Export(new ExportId(v4()), id, kind, parts);

exportRequest.apply(
parts
.filter((part) => !part.isReady())
.map(
(part) =>
new ExportComponentRequested(
exportRequest.id,
part.id,
part.resourceId,
part.piece,
),
),
);

return exportRequest;
}

completeComponent(
id: ComponentId,
pieceLocation: ComponentLocation,
): Either<typeof pieceNotFound, true> {
const piece = this.pieces.find((piece) => piece.id.equals(id));
if (!piece) {
return left(pieceNotFound);
}
piece.finish(pieceLocation);

if (this.#allPiecesReady()) {
this.apply(new ExportComponentFinished(this.id));
}

return right(true);
}

complete(archiveLocation: ArchiveLocation): Either<typeof notReady, true> {
if (!this.#allPiecesReady()) {
return left(notReady);
}
this.archiveLocation = archiveLocation;
this.apply(new ArchiveReady(this.id, this.archiveLocation));
return right(true);
}

toSnapshot(): ExportSnapshot {
return {
id: this.id.value,
resourceId: this.resourceId.value,
resourceKind: this.resourceKind,
exportPieces: this.pieces.map((piece) => piece.toSnapshot()),
archiveLocation: this.archiveLocation?.value,
};
}

static fromSnapshot(snapshot: ExportSnapshot): Export {
return new Export(
new ExportId(snapshot.id),
new ResourceId(snapshot.resourceId),
snapshot.resourceKind,
snapshot.exportPieces.map((piece) => ExportComponent.fromSnapshot(piece)),
snapshot.archiveLocation
? new ArchiveLocation(snapshot.archiveLocation)
: undefined,
);
}

#allPiecesReady = () => this.pieces.every((piece) => piece.isReady());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { TinyTypeOf } from 'tiny-types';

export class ResourceId extends TinyTypeOf<string>() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ResourceKind {
Project = 'project',
Scenario = 'scenario',
}
9 changes: 9 additions & 0 deletions api/apps/api/src/modules/clone/shared-kernel/clone-piece.ts
Original file line number Diff line number Diff line change
@@ -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',
}

0 comments on commit 80c2e39

Please sign in to comment.