diff --git a/api/apps/api/src/modules/geo-features/geo-features.service.ts b/api/apps/api/src/modules/geo-features/geo-features.service.ts index aaa1d4d13e..f6425ee675 100644 --- a/api/apps/api/src/modules/geo-features/geo-features.service.ts +++ b/api/apps/api/src/modules/geo-features/geo-features.service.ts @@ -327,8 +327,6 @@ export class GeoFeaturesService extends AppBaseService< 'An error occurred creating features for shapefile (changes have been rolled back)', String(err), ); - - console.log(err); } finally { // you need to release a queryRunner which was manually instantiated await apiQueryRunner.release(); diff --git a/api/apps/api/src/modules/projects/projects.controller.ts b/api/apps/api/src/modules/projects/projects.controller.ts index 956835beef..333337f850 100644 --- a/api/apps/api/src/modules/projects/projects.controller.ts +++ b/api/apps/api/src/modules/projects/projects.controller.ts @@ -52,10 +52,10 @@ import { JobStatusSerializer } from './dto/job-status.serializer'; import { PlanningAreaResponseDto } from './dto/planning-area-response.dto'; import { isLeft } from 'fp-ts/Either'; import { ShapefileUploadResponse } from './dto/project-upload-shapefile.dto'; -import { ShapefileService } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.service'; import { UploadShapefileDTO } from './dto/upload-shapefile.dto'; import { GeoFeaturesService } from '../geo-features/geo-features.service'; import { AppConfig } from '@marxan-api/utils/config.utils'; +import { ShapefileService } from '@marxan/shapefile-converter'; @UseGuards(JwtAuthGuard) @ApiBearerAuth() @@ -216,7 +216,7 @@ export class ProjectsController { @ApiOkResponse({ type: ShapefileUploadResponse }) @Post(`:id/features/shapefile`) @UseInterceptors( - FileInterceptor('shapefile', { + FileInterceptor('file', { ...uploadOptions, limits: { fileSize: (() => diff --git a/api/apps/api/src/modules/projects/projects.module.ts b/api/apps/api/src/modules/projects/projects.module.ts index 3f3560446f..af6ca39dee 100644 --- a/api/apps/api/src/modules/projects/projects.module.ts +++ b/api/apps/api/src/modules/projects/projects.module.ts @@ -11,7 +11,6 @@ import { CountriesModule } from '@marxan-api/modules/countries/countries.module' import { PlanningUnitsModule } from '@marxan-api/modules/planning-units/planning-units.module'; import { GeoFeaturesModule } from '@marxan-api/modules/geo-features/geo-features.module'; import { ApiEventsModule } from '@marxan-api/modules/api-events/api-events.module'; -import { ShapefilesModule } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.module'; import { ProtectedAreasModule } from './protected-areas/protected-areas.module'; import { ProjectsService } from './projects.service'; import { GeoFeatureSerializer } from './dto/geo-feature.serializer'; @@ -23,6 +22,7 @@ import { PlanningAreasModule } from './planning-areas'; import { UsersProjectsApiEntity } from './control-level/users-projects.api.entity'; import { ProjectsListingController } from './projects-listing.controller'; import { ProjectDetailsController } from './project-details.controller'; +import { ShapefilesModule } from '@marxan/shapefile-converter'; @Module({ imports: [ diff --git a/api/apps/api/test/jest-e2e.json b/api/apps/api/test/jest-e2e.json index f38cacb1ee..bc2a265262 100644 --- a/api/apps/api/test/jest-e2e.json +++ b/api/apps/api/test/jest-e2e.json @@ -64,6 +64,8 @@ "@marxan/geofeature-calculations/(.*)": "/../../../libs/geofeature-calculations/src/$1", "@marxan/geofeature-calculations": "/../../../libs/geofeature-calculations/src", "@marxan/iucn/(.*)": "/../../../libs/iucn/src/$1", - "@marxan/iucn": "/../../../libs/iucn/src" + "@marxan/iucn": "/../../../libs/iucn/src", + "@marxan/shapefile-converter/(.*)": "/../../../libs/shapefile-converter/src/$1", + "@marxan/shapefile-converter": "/../../../libs/shapefile-converter/src" } } diff --git a/api/apps/api/test/upload-feature/upload-feature.e2e-spec.ts b/api/apps/api/test/upload-feature/upload-feature.e2e-spec.ts new file mode 100644 index 0000000000..82a2a7ff86 --- /dev/null +++ b/api/apps/api/test/upload-feature/upload-feature.e2e-spec.ts @@ -0,0 +1,17 @@ +import { FixtureType } from '@marxan/utils/tests/fixture-type'; +import { getFixtures } from './upload-feature.fixtures'; + +let fixtures: FixtureType; + +beforeEach(async () => { + fixtures = await getFixtures(); +}); + +afterEach(async () => { + await fixtures?.cleanup(); +}); + +test(`custom feature upload`, async () => { + const result = await fixtures.WhenUploadingCustomFeature(); + await fixtures.ThenGeoFeaturesAreCreated(result); +}); diff --git a/api/apps/api/test/upload-feature/upload-feature.fixtures.ts b/api/apps/api/test/upload-feature/upload-feature.fixtures.ts new file mode 100644 index 0000000000..aa57b03989 --- /dev/null +++ b/api/apps/api/test/upload-feature/upload-feature.fixtures.ts @@ -0,0 +1,75 @@ +import { bootstrapApplication } from '../utils/api-application'; +import { GivenUserIsLoggedIn } from '../steps/given-user-is-logged-in'; +import { GivenProjectExists } from '../steps/given-project'; + +import { GeoFeature } from '@marxan-api/modules/geo-features/geo-feature.api.entity'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as request from 'supertest'; + +export const getFixtures = async () => { + const app = await bootstrapApplication(); + const token = await GivenUserIsLoggedIn(app); + const { projectId, cleanup } = await GivenProjectExists( + app, + token, + { + name: `Project ${Date.now()}`, + countryCode: undefined!, + }, + { + name: `Organization ${Date.now()}`, + }, + ); + const customFeatureName = `User custom feature ${Date.now()}`; + const customFeatureTag = `bioregional`; + const customFeatureDesc = `User custom feature desc`; + + const geoFeaturesApiRepo: Repository = app.get( + getRepositoryToken(GeoFeature), + ); + + return { + cleanup: async () => { + await geoFeaturesApiRepo.delete({ + projectId, + }); + await cleanup(); + await app.close(); + }, + WhenUploadingCustomFeature: async () => + request(app.getHttpServer()) + .post(`/api/v1/projects/${projectId}/features/shapefile`) + .set('Authorization', `Bearer ${token}`) + .attach(`file`, __dirname + `/wetlands.zip`) + .field({ + name: customFeatureName, + type: customFeatureTag, + description: customFeatureDesc, + }), + ThenGeoFeaturesAreCreated: async (result: request.Response) => { + expect(result.body).toEqual({ + success: true, + }); + expect( + await geoFeaturesApiRepo.find({ + where: { + projectId, + }, + }), + ).toEqual([ + { + id: expect.any(String), + featureClassName: customFeatureName, + description: customFeatureDesc, + alias: null, + propertyName: null, + intersection: null, + tag: customFeatureTag, + creationStatus: `done`, + projectId, + }, + ]); + }, + }; +}; diff --git a/api/apps/api/test/upload-feature/wetlands.zip b/api/apps/api/test/upload-feature/wetlands.zip new file mode 100644 index 0000000000..3228c443bb Binary files /dev/null and b/api/apps/api/test/upload-feature/wetlands.zip differ diff --git a/api/apps/geoprocessing/src/decoratos/shapefile.decorator.ts b/api/apps/geoprocessing/src/decoratos/shapefile.decorator.ts index 67f9a08b74..19582ca42d 100644 --- a/api/apps/geoprocessing/src/decoratos/shapefile.decorator.ts +++ b/api/apps/geoprocessing/src/decoratos/shapefile.decorator.ts @@ -5,7 +5,7 @@ import { ApiOkResponse, ApiOperation, } from '@nestjs/swagger'; -import { ShapefileGeoJSONResponseDTO } from '../modules/shapefiles/dto/shapefile.geojson.response.dto'; +import { ShapefileGeoJSONResponseDTO } from '../types/shapefile.geojson.response.dto'; export function ApiConsumesShapefile() { return applyDecorators( diff --git a/api/apps/geoprocessing/src/modules/planning-area/planning-area.module.ts b/api/apps/geoprocessing/src/modules/planning-area/planning-area.module.ts index e34d60b9a0..c9e2120a7c 100644 --- a/api/apps/geoprocessing/src/modules/planning-area/planning-area.module.ts +++ b/api/apps/geoprocessing/src/modules/planning-area/planning-area.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { PlanningAreaRepositoryModule } from '@marxan/planning-area-repository'; -import { ShapefilesModule } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.module'; +import { ShapefilesModule } from '@marxan/shapefile-converter'; import { geoprocessingConnections } from '@marxan-geoprocessing/ormconfig'; import { PlanningAreaService } from './planning-area.service'; import { PlanningAreaController } from './planning-area.controller'; diff --git a/api/apps/geoprocessing/src/modules/planning-area/planning-area.service.ts b/api/apps/geoprocessing/src/modules/planning-area/planning-area.service.ts index 9a5ca05949..3f2697186a 100644 --- a/api/apps/geoprocessing/src/modules/planning-area/planning-area.service.ts +++ b/api/apps/geoprocessing/src/modules/planning-area/planning-area.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { GeoJSON } from 'geojson'; import * as uuid from 'uuid'; import { CustomPlanningAreaRepository } from '@marxan/planning-area-repository'; -import { ShapefileService } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; import { GarbageCollectorConfig } from '@marxan-geoprocessing/modules/planning-area/garbage-collector-config'; import { SaveGeoJsonResult } from '@marxan/planning-area-repository/custom-planning-area.repository'; diff --git a/api/apps/geoprocessing/src/modules/planning-units/planning-units.controller.ts b/api/apps/geoprocessing/src/modules/planning-units/planning-units.controller.ts index 50386da617..e1e413b2c2 100644 --- a/api/apps/geoprocessing/src/modules/planning-units/planning-units.controller.ts +++ b/api/apps/geoprocessing/src/modules/planning-units/planning-units.controller.ts @@ -17,9 +17,9 @@ import { tileSpecification, } from './planning-units.service'; import { apiGlobalPrefixes } from '@marxan-geoprocessing/api.config'; -import { ShapefileService } from '../shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; import { ApiConsumesShapefile } from '../../decoratos/shapefile.decorator'; -import { ShapefileGeoJSONResponseDTO } from '../shapefiles/dto/shapefile.geojson.response.dto'; +import { ShapefileGeoJSONResponseDTO } from '../../types/shapefile.geojson.response.dto'; import { ApiOperation, diff --git a/api/apps/geoprocessing/src/modules/planning-units/planning-units.module.ts b/api/apps/geoprocessing/src/modules/planning-units/planning-units.module.ts index b35339a122..efbea4f632 100644 --- a/api/apps/geoprocessing/src/modules/planning-units/planning-units.module.ts +++ b/api/apps/geoprocessing/src/modules/planning-units/planning-units.module.ts @@ -2,9 +2,8 @@ import { Logger, Module } from '@nestjs/common'; import { TileModule } from '@marxan-geoprocessing/modules/tile/tile.module'; import { PlanningUnitsProcessor } from './planning-units.worker'; import { PlanningUnitsController } from './planning-units.controller'; -import { ShapefileService } from '../shapefiles/shapefiles.service'; +import { ShapefileService, FileService } from '@marxan/shapefile-converter'; import { PlanningUnitsService } from './planning-units.service'; -import { FileService } from '../files/files.service'; import { WorkerModule } from '../worker'; @Module({ diff --git a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.spec.ts b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.spec.ts index 9e54786915..71e35fe2db 100644 --- a/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.spec.ts +++ b/api/apps/geoprocessing/src/modules/protected-areas/worker/protected-area-processor.spec.ts @@ -5,7 +5,7 @@ import { Job } from 'bullmq'; import { FakeGeometryExtractor } from './__mocks__/geometry-extractor'; import { createJob } from './__mocks__/job'; -import { ShapefileService } from '../../shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; import { ProtectedAreaProcessor } from './protected-area-processor'; import { GeometryExtractor } from './geometry-extractor'; import { ProtectedAreasJobInput } from './worker-input'; 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 13d89ba4ea..ee19a30a39 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 @@ -4,7 +4,7 @@ import { Job } from 'bullmq'; import { WorkerProcessor } from '../../worker'; import { ProtectedAreasJobInput } from './worker-input'; -import { ShapefileService } from '../../shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; import { ProtectedArea } from '../protected-areas.geo.entity'; import { GeoJSON } from 'geojson'; import { GeometryExtractor } from './geometry-extractor'; 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 9c21d90c0e..f49d621e9a 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 @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CqrsModule } from '@nestjs/cqrs'; import { WorkerModule } from '../../worker'; -import { ShapefilesModule } from '../../shapefiles/shapefiles.module'; +import { ShapefilesModule } from '@marxan/shapefile-converter'; import { ProtectedArea } from '../protected-areas.geo.entity'; import { ProtectedAreaProcessor } from './protected-area-processor'; diff --git a/api/apps/geoprocessing/src/modules/surface-cost/adapters/shapefile-converter.ts b/api/apps/geoprocessing/src/modules/surface-cost/adapters/shapefile-converter.ts index d66aa7052b..ca3ff3ee51 100644 --- a/api/apps/geoprocessing/src/modules/surface-cost/adapters/shapefile-converter.ts +++ b/api/apps/geoprocessing/src/modules/surface-cost/adapters/shapefile-converter.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { GeoJSON } from 'geojson'; -import { ShapefileService } from '../../shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; import { CostSurfaceJobInput } from '../cost-surface-job-input'; import { ShapefileConverterPort } from '../ports/shapefile-converter/shapefile-converter.port'; diff --git a/api/apps/geoprocessing/src/modules/surface-cost/surface-cost.module.ts b/api/apps/geoprocessing/src/modules/surface-cost/surface-cost.module.ts index 286c751c77..3d0361933f 100644 --- a/api/apps/geoprocessing/src/modules/surface-cost/surface-cost.module.ts +++ b/api/apps/geoprocessing/src/modules/surface-cost/surface-cost.module.ts @@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ScenariosPlanningUnitGeoEntity } from '@marxan/scenarios-planning-unit'; import { CqrsModule } from '@nestjs/cqrs'; import { WorkerModule } from '@marxan-geoprocessing/modules/worker'; -import { ShapefilesModule } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.module'; +import { ShapefilesModule } from '@marxan/shapefile-converter'; import { SurfaceCostProcessor } from './application/surface-cost-processor'; import { SurfaceCostWorker } from './application/surface-cost-worker'; diff --git a/api/apps/geoprocessing/src/modules/shapefiles/dto/shapefile.geojson.response.dto.ts b/api/apps/geoprocessing/src/types/shapefile.geojson.response.dto.ts similarity index 100% rename from api/apps/geoprocessing/src/modules/shapefiles/dto/shapefile.geojson.response.dto.ts rename to api/apps/geoprocessing/src/types/shapefile.geojson.response.dto.ts diff --git a/api/apps/geoprocessing/src/utils/__mocks__/shapefile-service.ts b/api/apps/geoprocessing/src/utils/__mocks__/shapefile-service.ts index 7a8ff7699a..a056cf02d1 100644 --- a/api/apps/geoprocessing/src/utils/__mocks__/shapefile-service.ts +++ b/api/apps/geoprocessing/src/utils/__mocks__/shapefile-service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { ShapefileService } from '@marxan-geoprocessing/modules/shapefiles/shapefiles.service'; +import { ShapefileService } from '@marxan/shapefile-converter'; @Injectable() export class FakeShapefileService diff --git a/api/apps/geoprocessing/test/jest-e2e.json b/api/apps/geoprocessing/test/jest-e2e.json index 134ea17d7a..cecb9bbce9 100644 --- a/api/apps/geoprocessing/test/jest-e2e.json +++ b/api/apps/geoprocessing/test/jest-e2e.json @@ -37,6 +37,8 @@ "@marxan/geofeature-calculations/(.*)": "/../../libs/geofeature-calculations/src/$1", "@marxan/geofeature-calculations": "/../../libs/geofeature-calculations/src", "@marxan/iucn/(.*)": "/../../libs/iucn/src/$1", - "@marxan/iucn": "/../../libs/iucn/src" + "@marxan/iucn": "/../../libs/iucn/src", + "@marxan/shapefile-converter/(.*)": "/../../libs/shapefile-converter/src/$1", + "@marxan/shapefile-converter": "/../../libs/shapefile-converter/src" } } diff --git a/api/apps/geoprocessing/src/modules/files/files.module.ts b/api/libs/shapefile-converter/src/files/files.module.ts similarity index 100% rename from api/apps/geoprocessing/src/modules/files/files.module.ts rename to api/libs/shapefile-converter/src/files/files.module.ts diff --git a/api/apps/geoprocessing/src/modules/files/files.service.ts b/api/libs/shapefile-converter/src/files/files.service.ts similarity index 100% rename from api/apps/geoprocessing/src/modules/files/files.service.ts rename to api/libs/shapefile-converter/src/files/files.service.ts diff --git a/api/libs/shapefile-converter/src/index.ts b/api/libs/shapefile-converter/src/index.ts new file mode 100644 index 0000000000..3782574568 --- /dev/null +++ b/api/libs/shapefile-converter/src/index.ts @@ -0,0 +1,4 @@ +export { FilesModule } from './files/files.module'; +export { FileService } from './files/files.service'; +export { ShapefilesModule } from './shapefiles/shapefiles.module'; +export { ShapefileService } from './shapefiles/shapefiles.service'; diff --git a/api/apps/geoprocessing/src/modules/shapefiles/shapefiles.module.ts b/api/libs/shapefile-converter/src/shapefiles/shapefiles.module.ts similarity index 100% rename from api/apps/geoprocessing/src/modules/shapefiles/shapefiles.module.ts rename to api/libs/shapefile-converter/src/shapefiles/shapefiles.module.ts diff --git a/api/apps/geoprocessing/src/modules/shapefiles/shapefiles.service.ts b/api/libs/shapefile-converter/src/shapefiles/shapefiles.service.ts similarity index 100% rename from api/apps/geoprocessing/src/modules/shapefiles/shapefiles.service.ts rename to api/libs/shapefile-converter/src/shapefiles/shapefiles.service.ts diff --git a/api/libs/shapefile-converter/tsconfig.lib.json b/api/libs/shapefile-converter/tsconfig.lib.json new file mode 100644 index 0000000000..3bd1dd82a5 --- /dev/null +++ b/api/libs/shapefile-converter/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/shapefile-converter" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/api/nest-cli.json b/api/nest-cli.json index 4c7ebe4ef2..5e6b8d4806 100644 --- a/api/nest-cli.json +++ b/api/nest-cli.json @@ -160,6 +160,15 @@ "compilerOptions": { "tsConfigPath": "libs/iucn/tsconfig.lib.json" } + }, + "shapefile-converter": { + "type": "library", + "root": "libs/shapefile-converter", + "entryFile": "index", + "sourceRoot": "libs/shapefile-converter/src", + "compilerOptions": { + "tsConfigPath": "libs/shapefile-converter/tsconfig.lib.json" + } } } } \ No newline at end of file diff --git a/api/package.json b/api/package.json index da1f3ed26f..fd85ba0222 100644 --- a/api/package.json +++ b/api/package.json @@ -195,7 +195,9 @@ "@marxan/geofeature-calculations/(.*)": "/libs/geofeature-calculations/src/$1", "@marxan/geofeature-calculations": "/libs/geofeature-calculations/src", "@marxan/iucn/(.*)": "/libs/iucn/src/$1", - "@marxan/iucn": "/libs/iucn/src" + "@marxan/iucn": "/libs/iucn/src", + "@marxan/shapefile-converter/(.*)": "/libs/shapefile-converter/src/$1", + "@marxan/shapefile-converter": "/libs/shapefile-converter/src" } } -} +} \ No newline at end of file diff --git a/api/tsconfig.json b/api/tsconfig.json index e7bb55b9d6..974011101f 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -108,6 +108,12 @@ ], "@marxan/iucn/*": [ "libs/iucn/src/*" + ], + "@marxan/shapefile-converter": [ + "libs/shapefile-converter/src" + ], + "@marxan/shapefile-converter/*": [ + "libs/shapefile-converter/src/*" ] } },