Skip to content

Commit

Permalink
Feat/marxan 1445 grant project owner role on imported cloned project …
Browse files Browse the repository at this point in the history
…to user (#967)

* feat: domain-ids libs package

* feat: grant roles after importing projects/scenarios

* fix: import piece events handler unit test

* fix: piece importer tests

* fix: typeorm import repository integration test

* fix: remove features test data after running tests
  • Loading branch information
aciddaute authored Apr 8, 2022
1 parent 6731cb0 commit 7d30532
Show file tree
Hide file tree
Showing 40 changed files with 333 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddOwnerIdColumnToImportsTable1649329520250
implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE imports ADD COLUMN owner_id uuid NOT NULL;
`);

await queryRunner.query(`
ALTER TABLE imports
ADD CONSTRAINT imports_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id);
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE imports DROP COLUMN owner_id;
`);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ResourceKind } from '@marxan/cloning/domain';
import { Column, Entity, OneToMany, PrimaryColumn } from 'typeorm';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryColumn,
} from 'typeorm';
import { User } from '../../../../users/user.api.entity';
import { Import } from '../../domain';
import { ImportComponentEntity } from './import-components.api.entity';

Expand All @@ -14,6 +22,9 @@ export class ImportEntity {
@Column({ type: 'uuid', name: 'project_id' })
projectId!: string;

@Column({ type: 'uuid', name: 'owner_id' })
ownerId!: string;

@Column({
type: 'enum',
name: 'resource_kind',
Expand All @@ -32,6 +43,15 @@ export class ImportEntity {
})
components!: ImportComponentEntity[];

@ManyToOne(() => User, (user) => user.id, {
onDelete: 'CASCADE',
})
@JoinColumn({
name: 'owner_id',
referencedColumnName: 'id',
})
owner!: User;

static fromAggregate(importAggregate: Import): ImportEntity {
const snapshot = importAggregate.toSnapshot();

Expand All @@ -45,6 +65,7 @@ export class ImportEntity {
entity.components = snapshot.importPieces.map(
ImportComponentEntity.fromSnapshot,
);
entity.ownerId = snapshot.ownerId;

return entity;
}
Expand All @@ -57,6 +78,7 @@ export class ImportEntity {
resourceKind: this.resourceKind,
archiveLocation: this.archiveLocation,
importPieces: this.components.map((component) => component.toSnapshot()),
ownerId: this.ownerId,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ResourceId,
ResourceKind,
} from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { Logger } from '@nestjs/common';
import {
Expand Down Expand Up @@ -131,6 +132,8 @@ const getFixtures = async () => {
}).compile();
await sandbox.init();

const ownerId = UserId.create();

const events: IEvent[] = [];
const commands: ICommand[] = [];

Expand All @@ -156,6 +159,7 @@ const getFixtures = async () => {
importResourceId,
ResourceKind.Project,
projectId,
ownerId,
new ArchiveLocation('/tmp/location.zip'),
pieces,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArchiveLocation } 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';
import { Either } from 'fp-ts/lib/Either';
import { SaveError } from './import.repository.port';
Expand All @@ -14,7 +15,10 @@ export type ImportProjectCommandResult = {
export class ImportProject extends Command<
Either<ImportProjectError, ImportProjectCommandResult>
> {
constructor(public readonly archiveLocation: ArchiveLocation) {
constructor(
public readonly archiveLocation: ArchiveLocation,
public readonly ownerId: UserId,
) {
super();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,36 @@ import {
ResourceId,
ResourceKind,
} from '@marxan/cloning/domain';
import {
Failure as ArchiveFailure,
invalidFiles,
} from '@marxan/cloning/infrastructure/archive-reader.port';
import {
ExportConfigContent,
ProjectExportConfigContent,
} from '@marxan/cloning/infrastructure/clone-piece-data/export-config';
import { UserId } from '@marxan/domain-ids';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { CqrsModule, EventBus, IEvent } from '@nestjs/cqrs';
import { Test } from '@nestjs/testing';
import { Either, isLeft, isRight, left, Right, right } from 'fp-ts/Either';
import { PromiseType } from 'utility-types';
import { ExportConfigReader } from './export-config-reader';
import { MemoryImportRepository } from '../adapters/memory-import.repository.adapter';
import {
ImportComponent,
ImportId,
ImportRequested,
PieceImportRequested,
} from '../domain';
import {
Failure as ArchiveFailure,
invalidFiles,
} from '@marxan/cloning/infrastructure/archive-reader.port';
import { ImportResourcePieces } from './import-resource-pieces.port';
import { ImportRepository } from './import.repository.port';
import { ImportProjectHandler } from './import-project.handler';
import { ImportComponentStatuses } from '../domain/import/import-component-status';
import { ExportConfigReader } from './export-config-reader';
import {
ImportProject,
ImportProjectCommandResult,
} from './import-project.command';
import { ImportComponentStatuses } from '../domain/import/import-component-status';
import { ImportProjectHandler } from './import-project.handler';
import { ImportResourcePieces } from './import-resource-pieces.port';
import { ImportRepository } from './import.repository.port';

let fixtures: FixtureType<typeof getFixtures>;

Expand Down Expand Up @@ -83,6 +84,7 @@ const getFixtures = async () => {
await sandbox.init();

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

const events: IEvent[] = [];
sandbox.get(EventBus).subscribe((event) => events.push(event));
Expand Down Expand Up @@ -110,7 +112,7 @@ const getFixtures = async () => {
},
WhenRequestingImport: async () => {
const importResult = await sut.execute(
new ImportProject(new ArchiveLocation(`whatever`)),
new ImportProject(new ArchiveLocation(`whatever`), ownerId),
);
if (isRight(importResult))
resourceId = new ResourceId(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class ImportProjectHandler

async execute({
archiveLocation,
ownerId,
}: ImportProject): Promise<
Either<ImportProjectError, ImportProjectCommandResult>
> {
Expand All @@ -51,6 +52,7 @@ export class ImportProjectHandler
importResourceId,
ResourceKind.Project,
projectId,
ownerId,
archiveLocation,
pieces,
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArchiveLocation } 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';
import { Either } from 'fp-ts/lib/Either';
import { SaveError } from './import.repository.port';
Expand All @@ -14,7 +15,10 @@ export type ImportScenarioCommandResult = {
export class ImportScenario extends Command<
Either<ImportScenarioError, ImportScenarioCommandResult>
> {
constructor(public readonly archiveLocation: ArchiveLocation) {
constructor(
public readonly archiveLocation: ArchiveLocation,
public readonly ownerId: UserId,
) {
super();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ProjectExportConfigContent,
ScenarioExportConfigContent,
} from '@marxan/cloning/infrastructure/clone-piece-data/export-config';
import { UserId } from '@marxan/domain-ids';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { CqrsModule, EventBus, IEvent } from '@nestjs/cqrs';
import { Test } from '@nestjs/testing';
Expand Down Expand Up @@ -85,6 +86,7 @@ const getFixtures = async () => {
await sandbox.init();

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

const events: IEvent[] = [];
sandbox.get(EventBus).subscribe((event) => events.push(event));
Expand Down Expand Up @@ -112,7 +114,7 @@ const getFixtures = async () => {
},
WhenRequestingImport: async () => {
const importResult = await sut.execute(
new ImportScenario(new ArchiveLocation(`whatever`)),
new ImportScenario(new ArchiveLocation(`whatever`), ownerId),
);
if (isRight(importResult))
resourceId = new ResourceId(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class ImportScenarioHandler

async execute({
archiveLocation,
ownerId,
}: ImportScenario): Promise<
Either<ImportScenarioError, ImportScenarioCommandResult>
> {
Expand All @@ -52,6 +53,7 @@ export class ImportScenarioHandler
importResourceId,
ResourceKind.Scenario,
new ResourceId(exportConfig.projectId),
ownerId,
archiveLocation,
pieces,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export interface ImportSnapshot {
archiveLocation: string;
importPieces: ImportComponentSnapshot[];
projectId: string;
ownerId: string;
}
6 changes: 6 additions & 0 deletions api/apps/api/src/modules/clone/import/domain/import/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@marxan/cloning/domain';
import { AggregateRoot } from '@nestjs/cqrs';
import { Either, left, right } from 'fp-ts/Either';
import { UserId } from '@marxan/domain-ids';
import { AllPiecesImported } from '../events/all-pieces-imported.event';
import { ImportBatchFailed } from '../events/import-batch-failed.event';
import { ImportRequested } from '../events/import-requested.event';
Expand Down Expand Up @@ -33,6 +34,7 @@ export class Import extends AggregateRoot {
private readonly resourceId: ResourceId,
private readonly resourceKind: ResourceKind,
private readonly projectId: ResourceId,
private readonly ownerId: UserId,
private readonly archiveLocation: ArchiveLocation,
private readonly pieces: ImportComponent[],
) {
Expand Down Expand Up @@ -60,6 +62,7 @@ export class Import extends AggregateRoot {
new ResourceId(snapshot.resourceId),
snapshot.resourceKind,
new ResourceId(snapshot.projectId),
new UserId(snapshot.ownerId),
new ArchiveLocation(snapshot.archiveLocation),
snapshot.importPieces.map(ImportComponent.fromSnapshot),
);
Expand All @@ -69,6 +72,7 @@ export class Import extends AggregateRoot {
resourceId: ResourceId,
kind: ResourceKind,
projectId: ResourceId,
ownerId: UserId,
archiveLocation: ArchiveLocation,
pieces: ImportComponent[],
): Import {
Expand All @@ -78,6 +82,7 @@ export class Import extends AggregateRoot {
resourceId,
kind,
projectId,
ownerId,
archiveLocation,
pieces,
);
Expand Down Expand Up @@ -184,6 +189,7 @@ export class Import extends AggregateRoot {
importPieces: this.pieces.map((piece) => piece.toSnapshot()),
archiveLocation: this.archiveLocation.value,
projectId: this.projectId.value,
ownerId: this.ownerId.value,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const getFixtures = async () => {
projectId: v4(),
resourceKind: ResourceKind.Project,
uris: [],
ownerId: v4(),
};
},
WhenJobFinishes: async (input: ImportJobInput) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ResourceId,
ResourceKind,
} from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { Logger } from '@nestjs/common';
import { CommandBus, CommandHandler, CqrsModule, ICommand } from '@nestjs/cqrs';
Expand Down Expand Up @@ -113,6 +114,7 @@ const getFixtures = async () => {
}).compile();
await sandbox.init();

const ownerId = UserId.create();
const commands: ICommand[] = [];
sandbox.get(CommandBus).subscribe((command) => {
commands.push(command);
Expand All @@ -136,6 +138,7 @@ const getFixtures = async () => {
importResourceId,
ResourceKind.Project,
projectId,
ownerId,
new ArchiveLocation('/tmp/foo.zip'),
[importComponent],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class SchedulePieceImportHandler
resourceKind,
projectId,
importPieces,
ownerId,
} = importInstance.toSnapshot();

const component = importPieces.find(
Expand All @@ -74,6 +75,7 @@ export class SchedulePieceImportHandler
componentId: componentId.value,
pieceResourceId: resourceId,
projectId,
ownerId,
resourceKind,
uris,
});
Expand Down
8 changes: 7 additions & 1 deletion api/apps/api/src/modules/projects/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,11 +746,17 @@ export class ProjectsController {
@UseInterceptors(FileInterceptor('file'))
async importProject(
@UploadedFile() file: Express.Multer.File,
@Req() req: RequestWithAuthenticatedUser,
): Promise<RequestProjectImportResponseDto> {
const idsOrError = await this.projectsService.importProject(file);
const idsOrError = await this.projectsService.importProject(
file,
req.user.id,
);

if (isLeft(idsOrError)) {
switch (idsOrError.left) {
case forbiddenError:
throw new ForbiddenException();
case archiveCorrupted:
throw new BadRequestException('Missing export config file');
case invalidFiles:
Expand Down
Loading

0 comments on commit 7d30532

Please sign in to comment.