Skip to content

Commit

Permalink
feat(CostSurface): Adds DELETE endpoint for Cost surfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
KevSanchez committed Oct 2, 2023
1 parent bb6847f commit 4a9316a
Show file tree
Hide file tree
Showing 24 changed files with 738 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiEventsService } from '@marxan-api/modules/api-events';
import { API_EVENT_KINDS } from '@marxan/api-events';
import { ExportJobInput } from '@marxan/cloning';
import { ClonePiece, ExportJobInput } from '@marxan/cloning';
import { ResourceKind } from '@marxan/cloning/domain';
import { Inject, Logger } from '@nestjs/common';
import {
Expand Down Expand Up @@ -59,6 +59,10 @@ export class SchedulePieceExportHandler
resourceId,
}));

if (piece === ClonePiece.ScenarioInputFolder) {
this.logger.log('SCENARIO INPUT FOLDER');
}

const job = await this.queue.add(`export-piece`, {
piece,
exportId: exportId.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { Project } from '@marxan-api/modules/projects/project.api.entity';
import { BaseServiceResource } from '@marxan-api/types/resource.interface';
import { Scenario } from '@marxan-api/modules/scenarios/scenario.api.entity';

export const costSurfaceResource: BaseServiceResource = {
className: 'CostSurface',
Expand Down Expand Up @@ -67,6 +69,10 @@ export class CostSurface {
type: 'timestamp without time zone',
})
lastModifiedAt!: Date;

@ApiProperty()
@OneToMany(() => Scenario, (scenario) => scenario.costSurface, {})
scenarios!: Scenario[];
}

export class JSONAPICostSurface {
Expand Down
4 changes: 4 additions & 0 deletions api/apps/api/src/modules/cost-surface/cost-surface.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { CostSurfaceSerializer } from '@marxan-api/modules/cost-surface/dto/cost
import { DeleteProjectModule } from '@marxan-api/modules/projects/delete-project/delete-project.module';
import { ProjectCostSurfaceApplicationModule } from '@marxan-api/modules/cost-surface/application/project/project-cost-surface-application.module';
import { CostSurfaceApplicationModule } from '@marxan-api/modules/cost-surface/application/cost-surface-application.module';
import { DeleteCostSurfaceModule } from '@marxan-api/modules/cost-surface/delete-cost-surface/delete-cost-surface.module';
import { ProjectCostSurfaceAdaptersModule } from '@marxan-api/modules/cost-surface/adapters/cost-surface-adapters.module';
import { CostRangeService } from '@marxan-api/modules/scenarios/cost-range-service';
import { CqrsModule } from '@nestjs/cqrs';

@Module({
imports: [
Expand All @@ -18,6 +20,8 @@ import { CostRangeService } from '@marxan-api/modules/scenarios/cost-range-servi
DeleteProjectModule,
TypeOrmModule.forFeature([CostSurface]),
ProjectAclModule,
DeleteCostSurfaceModule,
CqrsModule,
],
providers: [CostSurfaceService, CostSurfaceSerializer, CostRangeService],

Expand Down
123 changes: 123 additions & 0 deletions api/apps/api/src/modules/cost-surface/cost-surface.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import { Either, left, right } from 'fp-ts/lib/Either';
import { projectNotEditable } from '@marxan-api/modules/projects/projects.service';
import { UploadCostSurfaceShapefileDto } from '@marxan-api/modules/cost-surface/dto/upload-cost-surface-shapefile.dto';
import { UpdateCostSurfaceDto } from '@marxan-api/modules/cost-surface/dto/update-cost-surface.dto';
import { forbiddenError } from '@marxan-api/modules/access-control';
import { Scenario } from '@marxan-api/modules/scenarios/scenario.api.entity';
import {
DeleteCostSurfaceCommand,
deleteCostSurfaceFailed,
} from '@marxan-api/modules/cost-surface/delete-cost-surface/delete-cost-surface.command';
import { CostSurfaceCalculationPort } from '@marxan-api/modules/cost-surface/ports/project/cost-surface-calculation.port';
import { CommandBus } from '@nestjs/cqrs';

export const costSurfaceNotEditableWithinProject = Symbol(
`cost surface not editable within project`,
Expand All @@ -20,6 +27,10 @@ export const costSurfaceNotFoundForProject = Symbol(
export const costSurfaceNameAlreadyExistsForProject = Symbol(
`cost surface already exists for project`,
);
export const costSurfaceStillInUse = Symbol(`cost surface still in use`);
export const cannotDeleteDefaultCostSurface = Symbol(
`cannot delete default cost surface`,
);

@Injectable()
export class CostSurfaceService {
Expand All @@ -28,6 +39,7 @@ export class CostSurfaceService {
private readonly costSurfaceRepository: Repository<CostSurface>,
private readonly projectAclService: ProjectAclService,
private readonly calculateCost: CostSurfaceCalculationPort,
private readonly commandBus: CommandBus,
) {}

createDefaultCostSurfaceModel(): CostSurface {
Expand Down Expand Up @@ -92,6 +104,49 @@ export class CostSurfaceService {
return right(void 0);
}

async deleteCostSurface(
userId: string,
projectId: string,
costSurfaceId: string,
): Promise<
Either<
| typeof projectNotEditable
| typeof costSurfaceNotFoundForProject
| typeof costSurfaceStillInUse
| typeof cannotDeleteDefaultCostSurface
| typeof deleteCostSurfaceFailed,
true
>
> {
if (
!(await this.projectAclService.canEditCostSurfaceInProject(
userId,
projectId,
))
) {
return left(projectNotEditable);
}

const costSurface = await this.costSurfaceRepository.findOne({
where: { projectId, id: costSurfaceId },
relations: { scenarios: true },
});

if (!costSurface) {
return left(costSurfaceNotFoundForProject);
}
if (costSurface.isDefault) {
return left(cannotDeleteDefaultCostSurface);
}
if (costSurface.scenarios.length > 0) {
return left(costSurfaceStillInUse);
}

return await this.commandBus.execute(
new DeleteCostSurfaceCommand(costSurfaceId),
);
}

async update(
userId: string,
projectId: string,
Expand Down Expand Up @@ -135,6 +190,74 @@ export class CostSurfaceService {
return right(costSurface);
}

async getCostSurface(
userId: string,
projectId: string,
costSurfaceId: string,
): Promise<
Either<typeof forbiddenError | typeof costSurfaceNotFound, CostSurface>
> {
if (!(await this.projectAclService.canViewProject(userId, projectId))) {
return left(forbiddenError);
}

const costSurface = await this.costSurfaceRepository.findOne({
where: { projectId, id: costSurfaceId },
relations: { scenarios: true },
});

if (!costSurface) {
return left(costSurfaceNotFound);
}

const result = await this.costSurfaceRepository
.createQueryBuilder('cs')
.select('cs.id', 'costSurfaceId')
.addSelect('COUNT(s.id)', 'usage')
.leftJoin(Scenario, 's', 'cs.id = s.cost_surface_id')
.where('cs.project_id = :projectId', { projectId })
.andWhere('cs.id = :costSurfaceId', { costSurfaceId })
.groupBy('cs.id')
.getRawOne();

costSurface.scenarioUsageCount = result.usage ? Number(result.usage) : 0;

return right(costSurface);
}

async getCostSurfaces(
userId: string,
projectId: string,
): Promise<Either<typeof forbiddenError, CostSurface[]>> {
if (!(await this.projectAclService.canViewProject(userId, projectId))) {
return left(forbiddenError);
}
const costSurfaces = await this.costSurfaceRepository.find({
where: { projectId },
relations: { scenarios: true },
});

const usageCounts = await this.costSurfaceRepository
.createQueryBuilder('cs')
.select('cs.id', 'costSurfaceId')
.addSelect('COUNT(s.id)', 'usage')
.leftJoin(Scenario, 's', 'cs.id = s.cost_surface_id')
.where('cs.project_id = :projectId', { projectId })
.groupBy('cs.id')
.getRawMany();

const usageCountMap = new Map(
usageCounts.map((usageRow) => [usageRow.costSurfaceId, usageRow.usage]),
);

for (const costSurface of costSurfaces) {
const usage = usageCountMap.get(costSurface.id);
costSurface.scenarioUsageCount = usage ? Number(usage) : 0;
}

return right(costSurfaces);
}

static defaultCostSurfaceName(): string {
return `Default Cost Surface`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@nestjs/common';
import { ICommand, ofType, Saga } from '@nestjs/cqrs';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ScheduleCleanupForCostSurfaceUnusedResources } from '@marxan-api/modules/cost-surface/delete-cost-surface/schedule-cost-surface-unused-resources-cleanup.command';
import { CostSurfaceDeleted } from '@marxan-api/modules/cost-surface/events/cost-surface-deleted.event';

@Injectable()
export class CostSurfaceDeletedSaga {
@Saga()
costSurfaceDeletedDefault = (
events$: Observable<any>,
): Observable<ICommand> =>
events$.pipe(
ofType(CostSurfaceDeleted),
map(
(event) =>
new ScheduleCleanupForCostSurfaceUnusedResources(event.costSurfaceId),
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Command } from '@nestjs-architects/typed-cqrs';
import { Either } from 'fp-ts/lib/Either';

export const deleteCostSurfaceFailed = Symbol('delete-cost-surface-failed');

export type DeleteCostSurfaceFailed = typeof deleteCostSurfaceFailed;

export type DeleteCostSurfaceResponse = Either<DeleteCostSurfaceFailed, true>;

export class DeleteCostSurfaceCommand extends Command<DeleteCostSurfaceResponse> {
constructor(public readonly costSurfaceId: string) {
super();
}
}
Loading

0 comments on commit 4a9316a

Please sign in to comment.