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: connect export and import in case is a clonning proces using ar… #981

Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -1,4 +1,4 @@
import { ArchiveLocation } from '@marxan/cloning/domain';
import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain';
import { Failure as ArchiveReadError } from '@marxan/cloning/infrastructure/archive-reader.port';
import { UserId } from '@marxan/domain-ids';
import { Command } from '@nestjs-architects/typed-cqrs';
Expand All @@ -18,6 +18,7 @@ export class ImportProject extends Command<
constructor(
public readonly archiveLocation: ArchiveLocation,
public readonly ownerId: UserId,
public readonly importResourceId?: ResourceId,
) {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ImportProjectHandler
async execute({
archiveLocation,
ownerId,
importResourceId,
}: ImportProject): Promise<
Either<ImportProjectError, ImportProjectCommandResult>
> {
Expand All @@ -38,8 +39,12 @@ export class ImportProjectHandler
if (isLeft(exportConfigOrError)) return exportConfigOrError;

const exportConfig = exportConfigOrError.right as ProjectExportConfigContent;
const importResourceId = ResourceId.create();
const projectId = importResourceId;

const resourceId = importResourceId
? importResourceId
: ResourceId.create();

const projectId = resourceId;

const pieces = this.importResourcePieces.resolveForProject(
projectId,
Expand All @@ -49,7 +54,7 @@ export class ImportProjectHandler

const importRequest = this.eventPublisher.mergeObjectContext(
Import.newOne(
importResourceId,
projectId,
ResourceKind.Project,
projectId,
ownerId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArchiveLocation } from '@marxan/cloning/domain';
import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain';
import { Failure as ArchiveReadError } from '@marxan/cloning/infrastructure/archive-reader.port';
import { UserId } from '@marxan/domain-ids';
import { Command } from '@nestjs-architects/typed-cqrs';
Expand All @@ -18,6 +18,7 @@ export class ImportScenario extends Command<
constructor(
public readonly archiveLocation: ArchiveLocation,
public readonly ownerId: UserId,
public readonly importResourceId: ResourceId,
) {
super();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ const getFixtures = async () => {
}).compile();
await sandbox.init();

let resourceId: ResourceId;
const ownerId = UserId.create();

const events: IEvent[] = [];
Expand All @@ -99,6 +98,7 @@ const getFixtures = async () => {
const importResourcePieces: FakeImportResourcePieces = sandbox.get(
ImportResourcePieces,
);
const importResourceId = ResourceId.create();

return {
GivenExtractingArchiveFails: () => {
Expand All @@ -113,14 +113,13 @@ const getFixtures = async () => {
importResourcePieces.mockEqualPieces();
},
WhenRequestingImport: async () => {
const importResult = await sut.execute(
new ImportScenario(new ArchiveLocation(`whatever`), ownerId),
return sut.execute(
new ImportScenario(
new ArchiveLocation(`whatever`),
ownerId,
importResourceId,
),
);
if (isRight(importResult))
resourceId = new ResourceId(
repo.entities[importResult.right.importId].resourceId,
);
return importResult;
},
ThenRequestImportIsSaved: (
importResult: PromiseType<ReturnType<ImportScenarioHandler['execute']>>,
Expand All @@ -131,6 +130,16 @@ const getFixtures = async () => {
(importResult as Right<ImportScenarioCommandResult>).right.importId
],
).toBeDefined();
expect(
repo.entities[
(importResult as Right<ImportScenarioCommandResult>).right.importId
].resourceId,
).toEqual(importResourceId.value);
expect(
repo.entities[
(importResult as Right<ImportScenarioCommandResult>).right.importId
].ownerId,
).toEqual(ownerId.value);
},
ThenImportFails: (
importResult: PromiseType<ReturnType<ImportScenarioHandler['execute']>>,
Expand All @@ -143,7 +152,7 @@ const getFixtures = async () => {
).toEqual([
{
importId: expect.any(ImportId),
resourceId,
resourceId: importResourceId,
resourceKind: ResourceKind.Scenario,
},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class ImportScenarioHandler
async execute({
archiveLocation,
ownerId,
importResourceId,
}: ImportScenario): Promise<
Either<ImportScenarioError, ImportScenarioCommandResult>
> {
Expand All @@ -38,7 +39,6 @@ export class ImportScenarioHandler
if (isLeft(exportConfigOrError)) return exportConfigOrError;

const exportConfig = exportConfigOrError.right as ScenarioExportConfigContent;
const importResourceId = ResourceId.create();

const pieces = this.importResourcePieces.resolveForScenario(
importResourceId,
Expand Down
81 changes: 76 additions & 5 deletions api/apps/api/src/modules/clone/infra/export/archive-ready.saga.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,87 @@
import {
ArchiveLocation,
ResourceId,
ResourceKind,
} from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import { Injectable } from '@nestjs/common';
import { ICommand, ofType, Saga } from '@nestjs/cqrs';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { from, Observable } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { ExportRepository } from '../../export/application/export-repository.port';
import { ArchiveReady } from '../../export/domain';
import { ImportProject } from '../../import/application/import-project.command';
import { ImportScenario } from '../../import/application/import-scenario.command';
import { MarkExportAsFinished } from './mark-export-as-finished.command';

type CommandMapper = (
archiveLocation: string,
ownerId: string,
importResourceId: string,
) => ICommand;

@Injectable()
export class ArchiveReadySaga {
constructor(private readonly exportRepository: ExportRepository) {}

private commandMapper: Record<ResourceKind, CommandMapper> = {
project: (
archiveLocation: string,
ownerId: string,
importResourceId: string,
) =>
new ImportProject(
new ArchiveLocation(archiveLocation),
new UserId(ownerId),
new ResourceId(importResourceId),
),
scenario: (
archiveLocation: string,
ownerId: string,
importResourceId: string,
) =>
new ImportScenario(
new ArchiveLocation(archiveLocation),
new UserId(ownerId),
new ResourceId(importResourceId),
),
};

private async getCommands(event: ArchiveReady) {
const exportInstance = await this.exportRepository.find(event.exportId);

if (!exportInstance) throw new Error('cant find export');

const {
resourceKind,
archiveLocation,
importResourceId,
ownerId,
} = exportInstance.toSnapshot();

if (!exportInstance.isClonning())
return [new MarkExportAsFinished(event.exportId)];

if (!archiveLocation || !importResourceId)
throw new Error(
'When clonning, the archiveLocation and importResourceId should be ready',
);

const importCommand = this.commandMapper[resourceKind](
archiveLocation,
ownerId,
importResourceId,
);

return [new MarkExportAsFinished(event.exportId), importCommand];
}

@Saga()
emitApiEvents = (events$: Observable<any>): Observable<ICommand> =>
events$.pipe(
emitApiEvents = (events$: Observable<any>) => {
return events$.pipe(
ofType(ArchiveReady),
map((event) => new MarkExportAsFinished(event.exportId)),
mergeMap((event) => from(this.getCommands(event))),
mergeMap((commands) => from(commands)),
);
};
}
3 changes: 2 additions & 1 deletion api/apps/api/src/modules/projects/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ export class ProjectsController {
projectId,
req.user.id,
dto.scenarioIds,
false,
);

if (isLeft(result)) {
Expand All @@ -624,7 +625,7 @@ export class ProjectsController {
}
}
return {
id: result.right,
id: result.right.exportId.value,
};
}

Expand Down
17 changes: 12 additions & 5 deletions api/apps/api/src/modules/projects/projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
} from '../clone/import/application/import-project.command';
import { PlanningUnitGridShape } from '@marxan/scenarios-planning-unit';
import { UserId } from '@marxan/domain-ids';
import { ExportProjectCommandResult } from '../clone/export/application/export-project.command';

export { validationFailed } from '../planning-areas';

Expand Down Expand Up @@ -341,7 +342,13 @@ export class ProjectsService {
projectId: string,
userId: string,
scenarioIds: string[],
): Promise<Either<typeof forbiddenError | typeof projectNotFound, string>> {
clonning: boolean,
): Promise<
Either<
typeof forbiddenError | typeof projectNotFound,
ExportProjectCommandResult
>
> {
await this.blockGuard.ensureThatProjectIsNotBlocked(projectId);

const canExportProject = await this.projectAclService.canExportProject(
Expand All @@ -350,17 +357,17 @@ export class ProjectsService {
);

if (!canExportProject) return left(forbiddenError);
//TODO add clonning logic and return value
const { exportId, importResourceId } = await this.commandBus.execute(

const res = await this.commandBus.execute(
new ExportProject(
new ResourceId(projectId),
scenarioIds,
new UserId(userId),
false,
clonning,
),
);

return right(exportId.value);
return right(res);
}

async remove(
Expand Down