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

ref: export aggregate #1040

Merged
merged 1 commit into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,35 @@ export class MemoryExportRepo implements ExportRepository {
): Promise<Export[]> {
return Object.values(this.#memory)
.filter((exportInstance) => {
const { resourceId, resourceKind } = exportInstance.toSnapshot();
return (
projectId === resourceId && resourceKind === ResourceKind.Project
);
const { resourceId } = exportInstance.toSnapshot();
return projectId === resourceId && exportInstance.isForProject();
})
.filter((exportInstance) => {
const { archiveLocation } = exportInstance.toSnapshot();
return options?.isFinished === undefined || options.isFinished
? archiveLocation !== undefined
: archiveLocation === undefined;
if (!options) return true;
const { isFinished } = options;
if (isFinished === undefined) return true;

const exportHasFinished = exportInstance.hasFinished();

return isFinished === exportHasFinished;
})
.filter((exportInstance) => {
const { importResourceId } = exportInstance.toSnapshot();
return options?.isStandalone === undefined || options.isStandalone
? importResourceId === undefined
: importResourceId !== undefined;
if (!options) return true;
const { isStandalone } = options;
if (isStandalone === undefined) return true;

const isStandaloneExport = !exportInstance.isCloning();

return isStandalone === isStandaloneExport;
})
.filter((exportInstance) => {
const { foreignExport } = exportInstance.toSnapshot();
return (
options?.isLocal === undefined || foreignExport === !options.isLocal
);
if (!options) return true;
const { isLocal } = options;
if (isLocal === undefined) return true;

const isLocalExport = !exportInstance.isForeignExport();

return isLocal === isLocalExport;
})
.sort(createdAtPropertySorter)
.slice(0, limit);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ const getFixtures = async () => {
opts: { cloning: boolean } = { cloning: false },
) => {
const exportInstance = await repo.find(exportId);
expect(exportInstance?.toSnapshot()).toBeDefined();
if (!opts.cloning)
expect(exportInstance?.importResourceId).toEqual(undefined);
else expect(exportInstance?.importResourceId).toBeDefined();
expect(exportInstance).toBeDefined();
expect(exportInstance!.isCloning()).toEqual(opts.cloning);
},
ThenUnfinishedExportPiecesAreRequestedToProcess: async (
projectId: ResourceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { Either, left } from 'fp-ts/Either';
import { Readable } from 'stream';
import { ExportRepository } from './export-repository.port';
import {
exportNotFound,
GetExportArchive,
GetFailure,
locationNotFound,
unfinishedExport,
} from './get-archive.query';

@QueryHandler(GetExportArchive)
Expand All @@ -20,11 +21,18 @@ export class GetArchiveHandler
async execute({
exportId,
}: GetExportArchive): Promise<Either<GetFailure, Readable>> {
const location = (await this.exportRepo.find(exportId))?.toSnapshot()
.archiveLocation;
const exportInstance = await this.exportRepo.find(exportId);

if (!location) return left(locationNotFound);
if (!exportInstance) {
return left(exportNotFound);
}

return this.fileRepo.get(location);
const { archiveLocation } = exportInstance.toSnapshot();

if (!exportInstance.hasFinished() || !archiveLocation) {
return left(unfinishedExport);
}

return this.fileRepo.get(archiveLocation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import { Either } from 'fp-ts/Either';
import { Readable } from 'stream';
import { ExportId } from '../domain';

export { ExportId };
export const locationNotFound = Symbol(`location not found`);
export type GetFailure = typeof locationNotFound | GetFileError;
export const exportNotFound = Symbol('export not found');
export const unfinishedExport = Symbol('unfinished export');

export type GetFailure =
| typeof unfinishedExport
| typeof exportNotFound
| GetFileError;

export class GetExportArchive extends Query<Either<GetFailure, Readable>> {
constructor(public readonly exportId: ExportId) {
Expand Down
4 changes: 4 additions & 0 deletions api/apps/api/src/modules/clone/export/domain/export/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ export class Export extends AggregateRoot {

isCloning = () => Boolean(this.importResourceId);

hasFinished = () => Boolean(this.archiveLocation);

isForeignExport = () => this.foreignExport;

isForProject() {
return this.resourceKind === ResourceKind.Project;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ResourceId } from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import { Command } from '@nestjs-architects/typed-cqrs';
import { Either } from 'fp-ts/lib/Either';
import { ExportId } from '../../export';
import {
exportNotFound,
unfinishedExport,
} from '../../export/application/get-archive.query';
import { SaveError } from './import.repository.port';

export const exportNotFound = Symbol('export not found');
export const unfinishedExport = Symbol('unfinished export');
export const invalidProjectExport = Symbol('invalid project export');

export type ImportProjectError =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import {
} from '@nestjs/cqrs';
import { Either, isLeft, left, right } from 'fp-ts/Either';
import { ExportRepository } from '../../export/application/export-repository.port';
import { Import } from '../domain/import/import';
import {
exportNotFound,
unfinishedExport,
} from '../../export/application/get-archive.query';
import { Import } from '../domain/import/import';
import {
ImportProject,
ImportProjectCommandResult,
ImportProjectError,
invalidProjectExport,
unfinishedExport,
} from './import-project.command';
import { ImportResourcePieces } from './import-resource-pieces.port';
import { ImportRepository } from './import.repository.port';
Expand All @@ -44,11 +46,11 @@ export class ImportProjectHandler
return left(exportNotFound);
}

if (!exportInstance.toSnapshot().archiveLocation) {
if (!exportInstance.hasFinished()) {
return left(unfinishedExport);
}

if (exportInstance.resourceKind !== ResourceKind.Project) {
if (!exportInstance.isForProject()) {
return left(invalidProjectExport);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { ResourceId } from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import { Command } from '@nestjs-architects/typed-cqrs';
import { Either } from 'fp-ts/lib/Either';
import { ExportId } from '../../export';
import {
exportNotFound,
unfinishedExport,
invalidProjectExport,
} from './import-project.command';
} from '../../export/application/get-archive.query';
import { SaveError } from './import.repository.port';

export const scenarioShellNotFound = Symbol('scenario shell not found');
export const invalidScenarioExport = Symbol('invalid scenario export');

export type ImportScenarioError =
| SaveError
| typeof exportNotFound
| typeof unfinishedExport
| typeof invalidProjectExport
| typeof invalidScenarioExport
| typeof scenarioShellNotFound;

export type ImportScenarioCommandResult = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ import { Either, isLeft, left, right } from 'fp-ts/Either';
import { Repository } from 'typeorm';
import { Scenario } from '../../../scenarios/scenario.api.entity';
import { ExportRepository } from '../../export/application/export-repository.port';
import { Import } from '../domain/import/import';
import {
exportNotFound,
invalidProjectExport,
unfinishedExport,
} from './import-project.command';
} from '../../export/application/get-archive.query';
import { Import } from '../domain/import/import';
import { invalidProjectExport } from './import-project.command';
import { ImportResourcePieces } from './import-resource-pieces.port';
import {
ImportScenario,
ImportScenarioCommandResult,
ImportScenarioError,
invalidScenarioExport,
scenarioShellNotFound,
} from './import-scenario.command';
import { ImportRepository } from './import.repository.port';
Expand Down Expand Up @@ -52,18 +53,15 @@ export class ImportScenarioHandler
return left(exportNotFound);
}

if (!exportInstance.toSnapshot().archiveLocation) {
if (!exportInstance.hasFinished()) {
return left(unfinishedExport);
}

if (
exportInstance.resourceKind !== ResourceKind.Scenario ||
exportInstance.importResourceId === undefined
) {
return left(invalidProjectExport);
if (exportInstance.isForProject() || !exportInstance.isCloning()) {
return left(invalidScenarioExport);
}

const { importResourceId } = exportInstance;
const importResourceId = exportInstance.importResourceId!;

const scenario = await this.scenarioRepo.findOne(importResourceId.value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ export class SchedulePieceExportHandler
this.eventBus.publish(new ExportPieceFailed(exportId, componentId));
return;
}
const {
resourceKind,
exportPieces,
importResourceId,
} = exportInstance.toSnapshot();
const { resourceKind, exportPieces } = exportInstance.toSnapshot();

const component = exportPieces.find(
(piece) => piece.id === componentId.value,
Expand Down
16 changes: 11 additions & 5 deletions api/apps/api/src/modules/projects/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ import {
ImplementsAcl,
IsMissingAclImplementation,
} from '@marxan-api/decorators/acl.decorator';
import { locationNotFound } from '@marxan-api/modules/clone/export/application/get-archive.query';
import {
GetLatestExportResponseDto,
GetLatestExportsResponseDto,
Expand All @@ -114,11 +113,14 @@ import {
cloningExportProvided,
invalidExportZipFile,
} from '../clone/infra/import/generate-export-from-zip-file.command';
import { exportNotFound } from '../clone/import/application/import-project.command';
import {
integrityCheckFailed,
invalidSignature,
} from '../clone/export/application/manifest-file-service.port';
import {
exportNotFound,
unfinishedExport,
} from '../clone/export/application/get-archive.query';

@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
Expand Down Expand Up @@ -707,9 +709,9 @@ export class ProjectsController {

if (isLeft(result)) {
switch (result.left) {
case locationNotFound:
throw new NotFoundException(
`Could not find export .zip location for export with ID: ${exportId} for project with ID: ${projectId}`,
case unfinishedExport:
throw new BadRequestException(
`Export with ID ${exportId} hasn't finished`,
);
case notAllowed:
throw new ForbiddenException(
Expand All @@ -719,6 +721,10 @@ export class ProjectsController {
throw new NotFoundException(
`Could not find project with ID: ${projectId}`,
);
case exportNotFound:
throw new NotFoundException(
`Could not find export with ID: ${exportId}`,
);

default:
throw new InternalServerErrorException();
Expand Down
11 changes: 8 additions & 3 deletions api/apps/api/src/modules/projects/projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import {
ExportProject,
GetExportArchive,
} from '@marxan-api/modules/clone';
import { GetFailure as GetArchiveLocationFailure } from '@marxan-api/modules/clone/export/application/get-archive.query';
import {
exportNotFound,
GetFailure as GetArchiveLocationFailure,
} from '@marxan-api/modules/clone/export/application/get-archive.query';
import { BlockGuard } from '@marxan-api/modules/projects/block-guard/block-guard.service';
import { ResourceId } from '@marxan/cloning/domain';
import { Readable } from 'stream';
Expand All @@ -54,7 +57,6 @@ import {
GenerateExportFromZipFileError,
} from '../clone/infra/import/generate-export-from-zip-file.command';
import {
exportNotFound,
ImportProject,
ImportProjectCommandResult,
ImportProjectError,
Expand Down Expand Up @@ -463,7 +465,10 @@ export class ProjectsService {
exportId: string,
): Promise<
Either<
GetArchiveLocationFailure | typeof notAllowed | typeof projectNotFound,
| GetArchiveLocationFailure
| typeof notAllowed
| typeof projectNotFound
| typeof exportNotFound,
Readable
>
> {
Expand Down
4 changes: 3 additions & 1 deletion api/apps/api/src/utils/acl.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ import {
} from '@nestjs/common';
import {
exportNotFound,
unfinishedExport,
} from '../modules/clone/export/application/get-archive.query';
import {
ImportProjectError,
invalidProjectExport,
unfinishedExport,
} from '../modules/clone/import/application/import-project.command';
import { saveError } from '../modules/clone/import/application/import.repository.port';

Expand Down
2 changes: 1 addition & 1 deletion api/apps/api/test/project/import-project.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export const getFixtures = async () => {
const exportInstance = await exportRepo.find(exportId);

expect(exportInstance).toBeDefined();
expect(exportInstance?.toSnapshot().foreignExport).toBe(true);
expect(exportInstance!.isForeignExport()).toBe(true);
},
ThenImportIsCompleted: async () => {
const res = await new Promise<ApiEventByTopicAndKind>(
Expand Down