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

use cost-surfaces as part of path for cost surface-related endpoints (rather than cost-surface) [MRXN23-479] [MRXN23-480] #1541

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
10 changes: 9 additions & 1 deletion .github/workflows/tests-api-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ jobs:
- 'implicit-permissions'
- 'integration'
- 'legacy-project-import'
- 'project'
- 'projects/blm-calibration'
- 'projects/crud'
- 'projects/project-feature-tags'
- 'projects/user-projects'
- 'projects/cost-surfaces'
- 'projects/project-cloning'
- 'projects/project-scenario-comparison'
- 'projects/project-summaries'
- 'projects/published-projects'
- 'project-jobs-status'
- 'project-planning-areas-tiles'
- 'project-planning-units'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export class CostSurfaceService {
.leftJoin(Scenario, 's', 'cs.id = s.cost_surface_id')
.where('cs.project_id = :projectId', { projectId })
.andWhere('cs.id = :costSurfaceId', { costSurfaceId })
.orderBy('cs.name', 'ASC')
.groupBy('cs.id')
.getRawOne();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import { projectResource } from '@marxan-api/modules/projects/project.api.entity';
import { apiGlobalPrefixes } from '@marxan-api/api.config';
import { ApiConsumesShapefile } from '@marxan-api/decorators/shapefile.decorator';
import {
Expand Down Expand Up @@ -53,8 +52,7 @@ import {
JSONAPICostSurface,
} from '@marxan-api/modules/cost-surface/cost-surface.api.entity';

//@Todo refactor all endpoints to use cost-surfaces instead of singular cost-surface
@ApiTags(projectResource.className)
@ApiTags('Project - Cost Surfaces')
@Controller(`${apiGlobalPrefixes.v1}/projects`)
export class ProjectCostSurfaceController {
constructor(
Expand All @@ -66,7 +64,7 @@ export class ProjectCostSurfaceController {

@ImplementsAcl()
@UseGuards(JwtAuthGuard)
@Get(`:projectId/cost-surface/:costSurfaceId`)
@Get(`:projectId/cost-surfaces/:costSurfaceId`)
@ApiOkResponse({ type: CostSurfaceResult })
async getCostSurfaces(
@Param('projectId') projectId: string,
Expand All @@ -92,7 +90,7 @@ export class ProjectCostSurfaceController {

@ImplementsAcl()
@UseGuards(JwtAuthGuard)
@Get(`:projectId/cost-surface/`)
@Get(`:projectId/cost-surfaces/`)
@ApiOkResponse({ type: CostSurfaceResultPlural })
async getCostSurface(
@Param('projectId') projectId: string,
Expand Down Expand Up @@ -120,7 +118,7 @@ export class ProjectCostSurfaceController {
@ApiConsumesShapefile({ withGeoJsonResponse: false })
@GeometryFileInterceptor(GeometryKind.Complex)
@ApiTags(asyncJobTag)
@Post(`:projectId/cost-surface/shapefile`)
@Post(`:projectId/cost-surfaces/shapefile`)
async processCostSurfaceShapefile(
@Param('projectId') projectId: string,
@Req() req: RequestWithAuthenticatedUser,
Expand Down Expand Up @@ -161,7 +159,7 @@ export class ProjectCostSurfaceController {
})
@ApiUnauthorizedResponse()
@ApiForbiddenResponse()
@Patch(`:projectId/cost-surface/:costSurfaceId`)
@Patch(`:projectId/cost-surfaces/:costSurfaceId`)
async updateCostSurface(
@Param('projectId') projectId: string,
@Param('costSurfaceId') costSurfaceId: string,
Expand Down Expand Up @@ -198,7 +196,7 @@ export class ProjectCostSurfaceController {
name: 'projectId',
description: 'The id of the Project that the Cost Surface is associated to',
})
@Delete(`:projectId/cost-surface/:costSurfaceId`)
@Delete(`:projectId/cost-surfaces/:costSurfaceId`)
async deleteCostSurface(
@Param('projectId') projectId: string,
@Param('costSurfaceId') costSurfaceId: string,
Expand Down Expand Up @@ -229,7 +227,7 @@ export class ProjectCostSurfaceController {
name: 'projectId',
description: 'The id of the Project that the Cost Surface is associated to',
})
@Get(`:projectId/cost-surface/:costSurfaceId/cost-range`)
@Get(`:projectId/cost-surfaces/:costSurfaceId/cost-range`)
@ApiOkResponse({ type: CostRangeDto })
async getCostRange(
@Param('projectId') projectId: string,
Expand Down
127 changes: 127 additions & 0 deletions api/apps/api/src/modules/projects/projects.blm.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
BadRequestException,
Body,
Controller, ForbiddenException,
Get, InternalServerErrorException,
NotFoundException,
Param, Patch, Req, UseGuards
} from '@nestjs/common';

import { projectResource } from './project.api.entity';
import {
ApiBearerAuth, ApiOkResponse,
ApiOperation,
ApiParam, ApiTags
} from '@nestjs/swagger';
import { apiGlobalPrefixes } from '@marxan-api/api.config';
import { JwtAuthGuard } from '@marxan-api/guards/jwt-auth.guard';

import { RequestWithAuthenticatedUser } from '@marxan-api/app.controller';
import {
ProjectsService
} from './projects.service';
import { isLeft } from 'fp-ts/Either';
import { inlineJobTag } from '@marxan-api/dto/inline-job-tag';
import { UpdateProjectBlmRangeDTO } from '@marxan-api/modules/projects/dto/update-project-blm-range.dto';
import { invalidRange } from '@marxan-api/modules/projects/blm';
import {
planningUnitAreaNotFound,
updateFailure,
} from '@marxan-api/modules/projects/blm/change-project-blm-range.command';
import { ProjectBlmValuesResponseDto } from '@marxan-api/modules/projects/dto/project-blm-values-response.dto';
import { forbiddenError } from '@marxan-api/modules/access-control';
import {
projectNotFound as blmProjectNotFound,
unknownError as blmUnknownError,
} from '../blm';
import {
ImplementsAcl
} from '@marxan-api/decorators/acl.decorator';

@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiTags('Project - BLM')
@Controller(`${apiGlobalPrefixes.v1}/projects`)
export class ProjectBLMController {
constructor(
private readonly projectsService: ProjectsService,
) {}

@ImplementsAcl()
@ApiOperation({
description: 'Updates the project BLM range and calculate its values',
})
@ApiParam({
name: 'id',
description: 'ID of the Project',
})
@ApiOkResponse({ type: ProjectBlmValuesResponseDto })
@ApiTags(inlineJobTag)
@Patch(':id/calibration')
async updateBlmRange(
@Param('id') id: string,
@Body() { range }: UpdateProjectBlmRangeDTO,
@Req() req: RequestWithAuthenticatedUser,
): Promise<ProjectBlmValuesResponseDto> {
const result = await this.projectsService.updateBlmValues(
id,
req.user.id,
range,
);

if (isLeft(result)) {
switch (result.left) {
case invalidRange:
throw new BadRequestException(`Invalid range: ${range}`);
case planningUnitAreaNotFound:
throw new NotFoundException(
`Could not find project BLM values for project with ID: ${id}`,
);
case updateFailure:
throw new InternalServerErrorException(
`Could not update with range ${range} project BLM values for project with ID: ${id}`,
);
case forbiddenError:
throw new ForbiddenException();
default:
throw new InternalServerErrorException();
}
}

return result.right;
}

@ImplementsAcl()
@ApiOperation({
description: 'Shows the project BLM values of a project',
})
@ApiParam({
name: 'id',
description: 'ID of the Project',
})
@ApiOkResponse({ type: ProjectBlmValuesResponseDto })
@ApiTags(inlineJobTag)
@Get(':id/calibration')
async getProjectBlmValues(
@Param('id') id: string,
@Req() req: RequestWithAuthenticatedUser,
): Promise<ProjectBlmValuesResponseDto> {
const result = await this.projectsService.findProjectBlm(id, req.user.id);

if (isLeft(result))
switch (result.left) {
case blmProjectNotFound:
throw new NotFoundException(
`Could not find project BLM values for project with ID: ${id}`,
);
case forbiddenError:
throw new ForbiddenException();
case blmUnknownError:
throw new InternalServerErrorException();
default:
const _exhaustiveCheck: never = result.left;
throw _exhaustiveCheck;
}
return result.right;
}
}
Loading
Loading