diff --git a/api/apps/api/src/modules/projects/project.api.entity.ts b/api/apps/api/src/modules/projects/project.api.entity.ts index d1010128b2..d5adcfcac0 100644 --- a/api/apps/api/src/modules/projects/project.api.entity.ts +++ b/api/apps/api/src/modules/projects/project.api.entity.ts @@ -161,6 +161,11 @@ export class Project extends TimeUserEntityMetadata { description: "Display name of Country / Gid1 / Gid2 of project's area", }) planningAreaName?: string; + + @ApiPropertyOptional({ + isArray: true, + }) + customProtectedAreas?: string[]; } export class JSONAPIProjectData { diff --git a/api/apps/api/src/modules/projects/projects-crud.service.ts b/api/apps/api/src/modules/projects/projects-crud.service.ts index 1eb6e728ed..461d6c8ff9 100644 --- a/api/apps/api/src/modules/projects/projects-crud.service.ts +++ b/api/apps/api/src/modules/projects/projects-crud.service.ts @@ -25,6 +25,8 @@ import { import { UsersProjectsApiEntity } from './control-level/users-projects.api.entity'; import { Roles } from '@marxan-api/modules/users/role.api.entity'; import { AppInfoDTO } from '@marxan-api/dto/info.dto'; +import { DbConnections } from '@marxan-api/ormconfig.connections'; +import { ProtectedArea } from '@marxan/protected-areas'; const projectFilterKeyNames = [ 'name', @@ -67,6 +69,8 @@ export class ProjectsCrudService extends AppBaseService< private readonly planningAreasService: PlanningAreasService, @InjectRepository(UsersProjectsApiEntity) private readonly userProjects: Repository, + @InjectRepository(ProtectedArea, DbConnections.geoprocessingDB) + private readonly protectedAreas: Repository, ) { super(repository, 'project', 'projects', { logging: { muteAll: AppConfig.get('logging.muteAll', false) }, @@ -90,6 +94,7 @@ export class ProjectsCrudService extends AppBaseService< 'planningAreaId', 'planningAreaName', 'bbox', + 'customProtectedAreas', ], keyForAttribute: 'camelCase', users: { diff --git a/api/apps/api/src/modules/projects/projects.module.ts b/api/apps/api/src/modules/projects/projects.module.ts index af6ca39dee..211d58dff1 100644 --- a/api/apps/api/src/modules/projects/projects.module.ts +++ b/api/apps/api/src/modules/projects/projects.module.ts @@ -23,6 +23,8 @@ import { UsersProjectsApiEntity } from './control-level/users-projects.api.entit import { ProjectsListingController } from './projects-listing.controller'; import { ProjectDetailsController } from './project-details.controller'; import { ShapefilesModule } from '@marxan/shapefile-converter'; +import { ProtectedArea } from '@marxan/protected-areas'; +import { apiConnections } from '@marxan-api/ormconfig'; @Module({ imports: [ @@ -36,6 +38,10 @@ import { ShapefilesModule } from '@marxan/shapefile-converter'; ScenarioJobStatus, UsersProjectsApiEntity, ]), + TypeOrmModule.forFeature( + [ProtectedArea], + apiConnections.geoprocessingDB.name, + ), UsersModule, PlanningUnitsModule, ProtectedAreasModule, diff --git a/api/apps/api/src/modules/protected-areas/dto/iucn-protected-area-category.dto.ts b/api/apps/api/src/modules/protected-areas/dto/iucn-protected-area-category.dto.ts index 3515346db0..e30f87fe28 100644 --- a/api/apps/api/src/modules/protected-areas/dto/iucn-protected-area-category.dto.ts +++ b/api/apps/api/src/modules/protected-areas/dto/iucn-protected-area-category.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; -import { ProtectedArea } from '../protected-area.geo.entity'; import { IUCNCategory } from '@marxan/iucn'; +import { ProtectedArea } from '@marxan/protected-areas'; export class IUCNProtectedAreaCategoryDTO extends PickType(ProtectedArea, [ 'iucnCategory', diff --git a/api/apps/api/src/modules/protected-areas/protected-area.geo.entity.ts b/api/apps/api/src/modules/protected-areas/protected-area.geo.entity.ts index eda9f911f7..d7dc0795e8 100644 --- a/api/apps/api/src/modules/protected-areas/protected-area.geo.entity.ts +++ b/api/apps/api/src/modules/protected-areas/protected-area.geo.entity.ts @@ -1,84 +1,5 @@ -import { IUCNCategory } from '@marxan/iucn'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; - -@Entity('wdpa') -export class ProtectedArea { - @ApiProperty() - @PrimaryGeneratedColumn('uuid') - id!: string; - - /** - * WDPA id. - */ - @ApiProperty() - @Column('double precision', { name: 'wdpaid' }) - wdpaId?: number; - - /** - * Full name of the protected area. - */ - @ApiPropertyOptional() - @Column('character varying', { name: 'full_name' }) - fullName?: string; - - /** - * IUCN category. - * - * Only applies to IUCN-defined protected areas. - */ - @ApiPropertyOptional() - @Column('character varying', { name: 'iucn_cat' }) - iucnCategory?: IUCNCategory; - - /** - * Total length of the protected area's shape. - */ - @ApiPropertyOptional() - @Column('double precision', { name: 'shape_leng' }) - shapeLength?: number; - - /** - * Total area of the protected area's shape. - */ - @ApiPropertyOptional() - @Column('double precision', { name: 'shape_area' }) - shapeArea?: number; - - /** - * Country where the protected area is located. - * - * This references the admin_regions.gid_0 column. - */ - @ApiPropertyOptional() - @Column('character varying', { name: 'iso3' }) - countryId?: string; - - /** - * Protection status of the area. - * - * For example: "Inscribed", or "Designated". - */ - @ApiPropertyOptional() - @Column('text') - status?: string; - - /** - * Protection designation. - */ - @ApiPropertyOptional() - @Column('text', { name: 'desig' }) - designation?: string; - - /** - * Geometry for the protected area. - * - * GeoJSON representation when retrieved from db. - */ - @ApiPropertyOptional() - @Column('geometry', { name: 'the_geom' }) - theGeom?: Record; -} +import { ApiProperty } from '@nestjs/swagger'; +import { ProtectedArea } from '@marxan/protected-areas'; export class JSONAPIProtectedAreaData { @ApiProperty() diff --git a/api/apps/api/src/modules/protected-areas/protected-areas.module.ts b/api/apps/api/src/modules/protected-areas/protected-areas.module.ts index d2ec4cfa8a..022564edd0 100644 --- a/api/apps/api/src/modules/protected-areas/protected-areas.module.ts +++ b/api/apps/api/src/modules/protected-areas/protected-areas.module.ts @@ -2,17 +2,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ProtectedAreasController } from './protected-areas.controller'; -import { ProtectedArea } from './protected-area.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas'; import { ProtectedAreasService } from './protected-areas.service'; -import { apiConnections } from '../../ormconfig'; import { ProxyService } from '@marxan-api/modules/proxy/proxy.service'; +import { DbConnections } from '@marxan-api/ormconfig.connections'; @Module({ imports: [ - TypeOrmModule.forFeature( - [ProtectedArea], - apiConnections.geoprocessingDB.name, - ), + TypeOrmModule.forFeature([ProtectedArea], DbConnections.geoprocessingDB), ], providers: [ProtectedAreasService, ProxyService], controllers: [ProtectedAreasController], diff --git a/api/apps/api/src/modules/protected-areas/protected-areas.service.ts b/api/apps/api/src/modules/protected-areas/protected-areas.service.ts index 5b531c675a..ffeeeae378 100644 --- a/api/apps/api/src/modules/protected-areas/protected-areas.service.ts +++ b/api/apps/api/src/modules/protected-areas/protected-areas.service.ts @@ -6,14 +6,13 @@ import { AppInfoDTO } from '@marxan-api/dto/info.dto'; import { Repository, SelectQueryBuilder } from 'typeorm'; import { CreateProtectedAreaDTO } from './dto/create.protected-area.dto'; import { UpdateProtectedAreaDTO } from './dto/update.protected-area.dto'; -import { ProtectedArea } from './protected-area.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas'; import * as JSONAPISerializer from 'jsonapi-serializer'; import { AppBaseService, JSONAPISerializerConfig, } from '@marxan-api/utils/app-base.service'; -import { isNil } from 'lodash'; import { FetchSpecification } from 'nestjs-base-service'; import { IUCNProtectedAreaCategoryDTO, @@ -24,6 +23,7 @@ import { IsBoolean, IsOptional, IsUUID } from 'class-validator'; import { apiConnections } from '../../ormconfig'; import { AppConfig } from '@marxan-api/utils/config.utils'; import { IUCNCategory } from '@marxan/iucn'; +import { isDefined } from '@marxan/utils'; const protectedAreaFilterKeyNames = [ 'fullName', @@ -164,16 +164,13 @@ export class ProtectedAreasService extends AppBaseService< /** * List IUCN categories of protected areas. */ - async listProtectedAreaCategories(): Promise> { - const results = await this.repository + async listProtectedAreaCategories(): Promise> { + return await this.repository .createQueryBuilder(this.alias) .select(`${this.alias}.iucnCategory`, 'iucnCategory') .distinct(true) .getRawMany() - .then((results) => - results.map((i) => i.iucnCategory).filter((i) => !isNil(i)), - ); - return results; + .then((results) => results.map((i) => i.iucnCategory).filter(isDefined)); } /** diff --git a/api/apps/api/test/jest-e2e.json b/api/apps/api/test/jest-e2e.json index bc2a265262..44e755322f 100644 --- a/api/apps/api/test/jest-e2e.json +++ b/api/apps/api/test/jest-e2e.json @@ -66,6 +66,8 @@ "@marxan/iucn/(.*)": "/../../../libs/iucn/src/$1", "@marxan/iucn": "/../../../libs/iucn/src", "@marxan/shapefile-converter/(.*)": "/../../../libs/shapefile-converter/src/$1", - "@marxan/shapefile-converter": "/../../../libs/shapefile-converter/src" + "@marxan/shapefile-converter": "/../../../libs/shapefile-converter/src", + "@marxan/protected-areas/(.*)": "/../../../libs/protected-areas/src/$1", + "@marxan/protected-areas": "/../../../libs/protected-areas/src" } } diff --git a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.module.ts b/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.module.ts index 1725cbaf9d..2b229c2ebf 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.module.ts +++ b/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.module.ts @@ -5,7 +5,7 @@ import { CqrsModule } from '@nestjs/cqrs'; import { ProtectedAreasController } from './protected-areas.controller'; import { ProtectedAreasService } from './protected-areas.service'; import { TileModule } from '@marxan-geoprocessing/modules/tile/tile.module'; -import { ProtectedArea } from '@marxan-geoprocessing/modules/protected-areas/protected-areas.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas/protected-areas.geo.entity'; import { ProtectedAreaWorkerModule } from './worker/protected-area-worker.module'; @Module({ diff --git a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.service.ts b/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.service.ts index 88518d7e62..50702c1450 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.service.ts +++ b/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.service.ts @@ -7,7 +7,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { IsNumber, IsOptional, IsString } from 'class-validator'; -import { ProtectedArea } from '@marxan-geoprocessing/modules/protected-areas/protected-areas.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas/protected-areas.geo.entity'; import { BBox } from 'geojson'; import { Transform } from 'class-transformer'; import { nominatim2bbox } from '@marxan-geoprocessing/utils/bbox.utils'; diff --git a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.ts b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.ts index 67ddfb3ed4..d683ff125a 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.ts +++ b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.ts @@ -8,7 +8,7 @@ import { ShapefileService } from '@marxan/shapefile-converter'; import { WorkerProcessor } from '../../worker'; import { ProtectedAreasJobInput } from './worker-input'; -import { ProtectedArea } from '../protected-areas.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas/protected-areas.geo.entity'; @Injectable() export class ProtectedAreaProcessor diff --git a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-worker.module.ts b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-worker.module.ts index 96894d3507..4fc47ec6af 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-worker.module.ts +++ b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-worker.module.ts @@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CqrsModule } from '@nestjs/cqrs'; import { WorkerModule } from '../../worker'; import { ShapefilesModule } from '@marxan/shapefile-converter'; -import { ProtectedArea } from '../protected-areas.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas/protected-areas.geo.entity'; import { ProtectedAreaProcessor } from './protected-area-processor'; import { ProtectedAreaWorkerService } from './protected-area-worker.service'; diff --git a/api/apps/geoprocessing/test/integration/protected-areas/steps/shapefile-for-wdpa-world.ts b/api/apps/geoprocessing/test/integration/protected-areas/steps/shapefile-for-wdpa-world.ts index fd5435a609..e05f7d0bfe 100644 --- a/api/apps/geoprocessing/test/integration/protected-areas/steps/shapefile-for-wdpa-world.ts +++ b/api/apps/geoprocessing/test/integration/protected-areas/steps/shapefile-for-wdpa-world.ts @@ -5,7 +5,7 @@ import { v4 } from 'uuid'; import { Job } from 'bullmq'; import { ProtectedAreasJobInput } from '../../../../src/modules/protected-areas/worker/worker-input'; -import { ProtectedArea } from '../../../../src/modules/protected-areas/protected-areas.geo.entity'; +import { ProtectedArea } from '@marxan/protected-areas/protected-areas.geo.entity'; export const createWorld = ( app: INestApplication, diff --git a/api/apps/geoprocessing/test/jest-e2e.json b/api/apps/geoprocessing/test/jest-e2e.json index cecb9bbce9..fbc6327f80 100644 --- a/api/apps/geoprocessing/test/jest-e2e.json +++ b/api/apps/geoprocessing/test/jest-e2e.json @@ -39,6 +39,8 @@ "@marxan/iucn/(.*)": "/../../libs/iucn/src/$1", "@marxan/iucn": "/../../libs/iucn/src", "@marxan/shapefile-converter/(.*)": "/../../libs/shapefile-converter/src/$1", - "@marxan/shapefile-converter": "/../../libs/shapefile-converter/src" + "@marxan/shapefile-converter": "/../../libs/shapefile-converter/src", + "@marxan/protected-areas/(.*)": "/../../libs/protected-areas/src/$1", + "@marxan/protected-areas": "/../../libs/protected-areas/src" } } diff --git a/api/libs/protected-areas/src/index.ts b/api/libs/protected-areas/src/index.ts new file mode 100644 index 0000000000..8bc647d69e --- /dev/null +++ b/api/libs/protected-areas/src/index.ts @@ -0,0 +1 @@ +export { ProtectedArea } from './protected-areas.geo.entity'; diff --git a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.geo.entity.ts b/api/libs/protected-areas/src/protected-areas.geo.entity.ts similarity index 82% rename from api/apps/geoprocessing/src/modules/protected-areas/protected-areas.geo.entity.ts rename to api/libs/protected-areas/src/protected-areas.geo.entity.ts index 4b6011b810..0c7f8096f9 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/protected-areas.geo.entity.ts +++ b/api/libs/protected-areas/src/protected-areas.geo.entity.ts @@ -1,12 +1,9 @@ -/** - * @todo We are replicating the same code that we have in the api. If we update something here we should also replicate it in the api side. - */ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Check, Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; import { MultiPolygon } from 'geojson'; import { defaultSrid } from '@marxan/utils/geo'; import { IUCNCategory } from '@marxan/iucn'; -import { TimeUserEntityMetadata } from '../../types/time-user-entity-metadata'; +import { TimeUserEntityMetadata } from '@marxan/utils'; @Entity('wdpa') export class ProtectedArea extends TimeUserEntityMetadata { @@ -24,7 +21,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { /** * geometry column. */ - @ApiProperty() + @ApiPropertyOptional() @Index(`wdpa_geom_idx`, { spatial: true, }) @@ -38,6 +35,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) theGeom?: MultiPolygon | null; + @ApiPropertyOptional() @Column({ type: 'float8', name: 'wdpaid', @@ -45,6 +43,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) wdpaId?: number | null; + @ApiPropertyOptional() @Column({ name: 'full_name', type: 'varchar', @@ -52,6 +51,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) fullName?: string | null; + @ApiPropertyOptional() @Column({ type: 'varchar', name: 'iucn_cat', @@ -59,6 +59,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) iucnCategory?: IUCNCategory | null; + @ApiPropertyOptional() @Column({ type: 'float8', name: 'shape_leng', @@ -66,6 +67,9 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) shapeLength?: number | null; + @ApiPropertyOptional({ + description: `Total area of the protected area's shape.`, + }) @Column({ type: 'float8', name: 'shape_area', @@ -73,6 +77,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { }) shapeArea?: number | null; + @ApiPropertyOptional() @Column({ type: 'text', name: 'desig', @@ -85,6 +90,7 @@ export class ProtectedArea extends TimeUserEntityMetadata { * * This references the admin_regions.gid_0 column. */ + @ApiPropertyOptional() @Column({ type: 'character varying', name: 'iso3', diff --git a/api/libs/protected-areas/tsconfig.lib.json b/api/libs/protected-areas/tsconfig.lib.json new file mode 100644 index 0000000000..a6ef7f761c --- /dev/null +++ b/api/libs/protected-areas/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/protected-areas" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/api/libs/utils/src/index.ts b/api/libs/utils/src/index.ts index e93a58eb4f..67ffdcf4f2 100644 --- a/api/libs/utils/src/index.ts +++ b/api/libs/utils/src/index.ts @@ -1,3 +1,4 @@ export { isDefined } from './is-defined'; export { assertDefined } from './assert-defined'; export { FieldsOf } from './fields-of.type'; +export { TimeUserEntityMetadata } from './time-user-entity-metadata'; diff --git a/api/apps/geoprocessing/src/types/time-user-entity-metadata.ts b/api/libs/utils/src/time-user-entity-metadata.ts similarity index 90% rename from api/apps/geoprocessing/src/types/time-user-entity-metadata.ts rename to api/libs/utils/src/time-user-entity-metadata.ts index cc979db583..9d59966c13 100644 --- a/api/apps/geoprocessing/src/types/time-user-entity-metadata.ts +++ b/api/libs/utils/src/time-user-entity-metadata.ts @@ -1,8 +1,5 @@ import { Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; -/** - * duplicated in API - except the relations - */ export abstract class TimeUserEntityMetadata { @CreateDateColumn({ name: 'created_at', diff --git a/api/nest-cli.json b/api/nest-cli.json index 5e6b8d4806..0ee594ef06 100644 --- a/api/nest-cli.json +++ b/api/nest-cli.json @@ -169,6 +169,15 @@ "compilerOptions": { "tsConfigPath": "libs/shapefile-converter/tsconfig.lib.json" } + }, + "protected-areas": { + "type": "library", + "root": "libs/protected-areas", + "entryFile": "index", + "sourceRoot": "libs/protected-areas/src", + "compilerOptions": { + "tsConfigPath": "libs/protected-areas/tsconfig.lib.json" + } } } } \ No newline at end of file diff --git a/api/package.json b/api/package.json index 154a1fadb2..a5c029f081 100644 --- a/api/package.json +++ b/api/package.json @@ -201,7 +201,9 @@ "@marxan/iucn/(.*)": "/libs/iucn/src/$1", "@marxan/iucn": "/libs/iucn/src", "@marxan/shapefile-converter/(.*)": "/libs/shapefile-converter/src/$1", - "@marxan/shapefile-converter": "/libs/shapefile-converter/src" + "@marxan/shapefile-converter": "/libs/shapefile-converter/src", + "@marxan/protected-areas/(.*)": "/libs/protected-areas/src/$1", + "@marxan/protected-areas": "/libs/protected-areas/src" } } -} +} \ No newline at end of file diff --git a/api/tsconfig.json b/api/tsconfig.json index 974011101f..e36755af97 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -114,6 +114,12 @@ ], "@marxan/shapefile-converter/*": [ "libs/shapefile-converter/src/*" + ], + "@marxan/protected-areas": [ + "libs/protected-areas/src" + ], + "@marxan/protected-areas/*": [ + "libs/protected-areas/src/*" ] } },