Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/archive reader adapter #824

Merged
merged 13 commits into from
Feb 15, 2022
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { DiscoveryModule } from '@golevelup/nestjs-discovery';
import { FileRepositoryModule } from '@marxan/files-repository';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Scenario } from '../../../scenarios/scenario.api.entity';
import { ArchiveCreator } from '../application/archive-creator.port';
import { ExportRepository } from '../application/export-repository.port';
import { ResourcePieces } from '../application/resource-pieces.port';
import { ExportResourcePieces } from '../application/export-resource-pieces.port';
import { ExportComponentLocationEntity } from './entities/export-component-locations.api.entity';
import { ExportComponentEntity } from './entities/export-components.api.entity';
import { ExportEntity } from './entities/exports.api.entity';
import { ExportResourcePiecesAdapter } from './export-resource-pieces.adapter';
import { NodeArchiveCreator } from './node-archive-creator';
import { ResourcePiecesAdapter } from './resource-pieces.adapter';
import { ProjectResourcePiecesAdapter } from './resource-pieces/project-resource-pieces.adapter';
import { ScenarioResourcePiecesAdapter } from './resource-pieces/scenario-resource-pieces.adapter';
import { TypeormExportRepository } from './typeorm-export.repository';

@Module({
imports: [
FileRepositoryModule,
DiscoveryModule,
TypeOrmModule.forFeature([
Scenario,
ExportEntity,
Expand All @@ -32,16 +28,14 @@ import { TypeormExportRepository } from './typeorm-export.repository';
useClass: TypeormExportRepository,
},
{
provide: ResourcePieces,
useClass: ResourcePiecesAdapter,
provide: ExportResourcePieces,
useClass: ExportResourcePiecesAdapter,
},
{
provide: ArchiveCreator,
useClass: NodeArchiveCreator,
},
ProjectResourcePiecesAdapter,
ScenarioResourcePiecesAdapter,
],
exports: [ExportRepository, ResourcePieces, ArchiveCreator],
exports: [ExportRepository, ExportResourcePieces, ArchiveCreator],
})
export class ExportAdaptersModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ClonePiece, ResourceId, ResourceKind } from '@marxan/cloning/domain';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Scenario } from '../../../scenarios/scenario.api.entity';
import { ExportResourcePieces } from '../application/export-resource-pieces.port';
import { ExportComponent } from '../domain';

@Injectable()
export class ExportResourcePiecesAdapter implements ExportResourcePieces {
private resolverMapping: Record<
ResourceKind,
(id: ResourceId, kind: ResourceKind) => Promise<ExportComponent[]>
> = {
project: this.resolveForProject.bind(this),
scenario: this.resolveForScenario.bind(this),
};

constructor(
@InjectRepository(Scenario)
private readonly scenarioRepository: Repository<Scenario>,
) {}

resolveFor(id: ResourceId, kind: ResourceKind): Promise<ExportComponent[]> {
return this.resolverMapping[kind](id, kind);
}

private async resolveForProject(
id: ResourceId,
kind: ResourceKind,
): Promise<ExportComponent[]> {
const scenarios = await this.scenarioRepository.find({
where: { projectId: id.value },
});

const scenarioPieces = await Promise.all(
scenarios.map((scenario) =>
this.resolveForScenario(new ResourceId(scenario.id), kind),
),
);

return [
ExportComponent.newOne(id, ClonePiece.ProjectMetadata),
ExportComponent.newOne(id, ClonePiece.ExportConfig),
ExportComponent.newOne(id, ClonePiece.PlanningAreaCustom),
ExportComponent.newOne(id, ClonePiece.PlanningAreaGAdm),
ExportComponent.newOne(id, ClonePiece.PlanningAreaGridCustom),
...scenarioPieces.flat(),
];
}

private async resolveForScenario(
id: ResourceId,
kind: ResourceKind,
): Promise<ExportComponent[]> {
const pieces: ExportComponent[] = [
ExportComponent.newOne(id, ClonePiece.ScenarioMetadata),
];

if (kind === ResourceKind.Scenario) {
pieces.push(ExportComponent.newOne(id, ClonePiece.ExportConfig));
}

return pieces;
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '../domain';

import { ExportProjectHandler } from './export-project.handler';
import { ResourcePieces } from './resource-pieces.port';
import { ExportResourcePieces } from './export-resource-pieces.port';
import { ExportRepository } from './export-repository.port';
import { ExportProject } from './export-project.command';
import { InMemoryExportRepo } from '../adapters/in-memory-export.repository';
Expand All @@ -38,7 +38,7 @@ const getFixtures = async () => {
imports: [CqrsModule],
providers: [
{
provide: ResourcePieces,
provide: ExportResourcePieces,
useClass: FakePiecesProvider,
},
{
Expand All @@ -54,7 +54,7 @@ const getFixtures = async () => {

const sut = sandbox.get(ExportProjectHandler);
const repo: InMemoryExportRepo = sandbox.get(ExportRepository);
const piecesResolver: FakePiecesProvider = sandbox.get(ResourcePieces);
const piecesResolver: FakePiecesProvider = sandbox.get(ExportResourcePieces);
sandbox.get(EventBus).subscribe((event) => {
events.push(event);
});
Expand Down Expand Up @@ -136,8 +136,10 @@ const getFixtures = async () => {
};

@Injectable()
class FakePiecesProvider implements ResourcePieces {
resolveMock: jest.MockedFunction<ResourcePieces['resolveFor']> = jest.fn();
class FakePiecesProvider implements ExportResourcePieces {
resolveMock: jest.MockedFunction<
ExportResourcePieces['resolveFor']
> = jest.fn();

async resolveFor(
id: ResourceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { ResourceKind } from '@marxan/cloning/domain';
import { Export, ExportId } from '../domain';

import { ExportProject } from './export-project.command';
import { ResourcePieces } from './resource-pieces.port';
import { ExportResourcePieces } from './export-resource-pieces.port';
import { ExportRepository } from './export-repository.port';

@CommandHandler(ExportProject)
export class ExportProjectHandler
implements IInferredCommandHandler<ExportProject> {
constructor(
private readonly resourcePieces: ResourcePieces,
private readonly resourcePieces: ExportResourcePieces,
private readonly exportRepository: ExportRepository,
private readonly eventPublisher: EventPublisher,
) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ResourceId, ResourceKind } from '@marxan/cloning/domain';
import { ExportComponent } from '../domain';

export abstract class ResourcePieces {
export abstract class ExportResourcePieces {
abstract resolveFor(
id: ResourceId,
kind: ResourceKind,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { ResourceKind } from '@marxan/cloning/domain';
import { Export, ExportId } from '../domain';

import { ExportScenario } from './export-scenario.command';
import { ResourcePieces } from './resource-pieces.port';
import { ExportResourcePieces } from './export-resource-pieces.port';
import { ExportRepository } from './export-repository.port';

@CommandHandler(ExportScenario)
export class ExportScenarioHandler
implements IInferredCommandHandler<ExportScenario> {
constructor(
private readonly resourcePieces: ResourcePieces,
private readonly resourcePieces: ExportResourcePieces,
private readonly exportRepository: ExportRepository,
private readonly eventPublisher: EventPublisher,
) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
ArchiveLocation,
ClonePiece,
ResourceId,
ResourceKind,
} from '@marxan/cloning/domain';
import { ClonePieceRelativePaths } from '@marxan/cloning/infrastructure/clone-piece-data';
import { FileRepository } from '@marxan/files-repository';
import { fileNotFound } from '@marxan/files-repository/file.repository';
import { extractFile } from '@marxan/utils';
import { Injectable } from '@nestjs/common';
import { Either, isLeft, left, right } from 'fp-ts/lib/Either';
import {
archiveCorrupted,
ArchiveReader,
Failure,
invalidFiles,
} from '../application/archive-reader.port';
import { ImportResourcePieces } from '../application/import-resource-pieces.port';
import { Import } from '../domain';

@Injectable()
export class ArchiveReaderAdapter implements ArchiveReader {
constructor(
private readonly fileRepository: FileRepository,
private readonly importResourcePieces: ImportResourcePieces,
) {}

async get(location: ArchiveLocation): Promise<Either<Failure, Import>> {
const readableOrError = await this.fileRepository.get(location.value);
if (isLeft(readableOrError)) return left(fileNotFound);

const exportConfigOrError = await extractFile(
readableOrError.right,
new RegExp(ClonePieceRelativePaths[ClonePiece.ExportConfig].config),
);
if (isLeft(exportConfigOrError)) return left(archiveCorrupted);
const exportConfig = JSON.parse(exportConfigOrError.right);

const resourceId = new ResourceId(exportConfig.resourceId);
const resourceKind = exportConfig.resourceKind;

const validResourceKind = Object.values(ResourceKind).includes(
resourceKind,
);
if (!validResourceKind) return left(invalidFiles);

const pieces = await this.importResourcePieces.resolveFor(
resourceId,
resourceKind,
location,
);

return right(Import.newOne(resourceId, resourceKind, location, pieces));
}
}
Loading