Skip to content

Commit

Permalink
Merge pull request #1244 from PublicMapping/fix/mvm/delete-archived-p…
Browse files Browse the repository at this point in the history
…rojects

Fix deletion of maps w/ archived regions
  • Loading branch information
maurizi authored Jul 20, 2022
2 parents 943c1dc + bf93267 commit be421ca
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

### Fixed
- Fix server crashed caused by archived maps w/ an archived region [#1244](https://github.com/PublicMapping/districtbuilder/pull/1244)

## [1.18.0]
### Added
Expand Down
5 changes: 4 additions & 1 deletion src/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"form-data": "4.0.0",
"geojson2shp": "0.5.0",
"handlebars": "4.7.7",
"jest-mock": "^28.1.3",
"lodash": "4.17.21",
"nestjs-typeorm-paginate": "3.1.3",
"nodemailer": "6.7.2",
Expand All @@ -70,7 +71,8 @@
"simplify-geojson": "^1.0.5",
"threads": "1.7.0",
"topojson-client": "3.1.0",
"typeorm": "0.2.41"
"typeorm": "0.2.41",
"uuid": "^8.3.2"
},
"devDependencies": {
"@nestjs/cli": "8.2.4",
Expand All @@ -91,6 +93,7 @@
"@types/pug": "2.0.6",
"@types/supertest": "2.0.11",
"@types/topojson-client": "3.1.0",
"@types/uuid": "^8.3.4",
"@types/validator": "13.7.1",
"@typescript-eslint/eslint-plugin": "5.7.0",
"@typescript-eslint/parser": "5.7.0",
Expand Down
95 changes: 95 additions & 0 deletions src/server/src/projects/controllers/projects.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Test } from "@nestjs/testing";
import { ModuleMocker, MockFunctionMetadata } from "jest-mock";
import * as uuid from "uuid";

import { ProjectsController } from "./projects.controller";
import { Project } from "../entities/project.entity";
import { ProjectsService } from "../services/projects.service";
import { DeepPartial } from "typeorm";
import { RegionConfig } from "../../region-configs/entities/region-config.entity";
import { DEFAULT_PINNED_METRIC_FIELDS, ProjectVisibility } from "../../../../shared/constants";
import { CrudRequest } from "@nestjsx/crud";
import { TopologyService } from "../../districts/services/topology.service";

const moduleMocker = new ModuleMocker(global);

describe("ProjectsController", () => {
let controller: ProjectsController;
let topologyService: TopologyService;
const userId = "1";
const projectId = uuid.v4();
const regionConfig: DeepPartial<RegionConfig> = {
id: uuid.v4(),
name: "Delaware",
regionCode: "DE",
countryCode: "US",
s3URI: "s3://global-districtbuilder-dev-us-east-1/regions/US/DE/2020-09-09T19:50:10.921Z/",
archived: true,
hidden: false,
version: new Date("2020-09-09T19:50:10.921Z")
};
const project: DeepPartial<Project> = {
id: projectId,
name: "My Map",
regionConfig: regionConfig,
regionConfigVersion: regionConfig.version,
numberOfDistricts: 2,
districts: {
type: "FeatureCollection",
features: []
},
user: { id: userId },
createdDt: new Date("2022-07-12T19:50:10.921Z"),
updatedDt: new Date("2022-07-13T19:50:10.921Z"),
advancedEditingEnabled: false,
lockedDistricts: [false, false, false],
visibility: ProjectVisibility.Private,
archived: false,
isFeatured: false,
populationDeviation: 0,
pinnedMetricFields: DEFAULT_PINNED_METRIC_FIELDS,
numberOfMembers: [1, 1],
planscoreUrl: ""
};
/* eslint-disable functional/immutable-data */
// @ts-ignore
project.districtsDefinition = [0, 1, 2];
/* eslint-enable */

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [ProjectsController]
})
.useMocker(token => {
if (token === ProjectsService) {
return {
findOne: jest.fn().mockResolvedValue(project),
updateOne: (req: unknown, data: DeepPartial<Project>) => Promise.resolve(data)
};
}
if (typeof token === "function") {
const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
const Mock = moduleMocker.generateFromMetadata(mockMetadata);
// eslint-disable-next-line
return new Mock();
}
})
.compile();

controller = moduleRef.get(ProjectsController);
topologyService = moduleRef.get(TopologyService);
});

describe("updateOne", () => {
it("should not load topology when geojson updates are not needed", async () => {
const result = await controller.updateOne(
projectId,
{ parsed: { authPersist: { userId } }, options: null } as unknown as CrudRequest,
{ archived: true }
);
expect(result).toBeDefined();
// eslint-disable-next-line
expect(topologyService.get).not.toHaveBeenCalled();
});
});
});
45 changes: 23 additions & 22 deletions src/server/src/projects/controllers/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,28 +670,29 @@ export class ProjectsController implements CrudController<Project> {
}
validateNumberOfMembers(dto, existingProject.numberOfDistricts);

const staticMetadata = (await this.getGeoUnitTopology(existingProject.regionConfig))
.staticMetadata;
const allowedDemographicFields = getDemographicsMetricFields(staticMetadata).map(
([, field]) => field
);
const allowedVotingFields: readonly string[] =
getVotingMetricFields(staticMetadata).map(([, field]) => field) || [];
if (
dto.pinnedMetricFields &&
dto.pinnedMetricFields.some(
field =>
!(
CORE_METRIC_FIELDS.includes(field) ||
allowedDemographicFields.includes(field) ||
allowedVotingFields.includes(field)
)
)
) {
throw new BadRequestException({
error: "Bad Request",
message: { pinnedMetricFields: [`Field not allowed in "pinnedMetricFields"`] }
} as Errors<UpdateProjectDto>);
if (dto.pinnedMetricFields) {
const staticMetadata = (await this.getGeoUnitTopology(existingProject.regionConfig))
.staticMetadata;
const allowedDemographicFields = getDemographicsMetricFields(staticMetadata).map(
([, field]) => field
);
const allowedVotingFields: readonly string[] =
getVotingMetricFields(staticMetadata).map(([, field]) => field) || [];
if (
dto.pinnedMetricFields.some(
field =>
!(
CORE_METRIC_FIELDS.includes(field) ||
allowedDemographicFields.includes(field) ||
allowedVotingFields.includes(field)
)
)
) {
throw new BadRequestException({
error: "Bad Request",
message: { pinnedMetricFields: [`Field not allowed in "pinnedMetricFields"`] }
} as Errors<UpdateProjectDto>);
}
}

const dataWithDefinitions =
Expand Down
18 changes: 9 additions & 9 deletions src/server/src/projects/entities/update-project.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,43 @@ import { DistrictsDefinition, UpdateProjectData } from "../../../../shared/entit
export class UpdateProjectDto implements UpdateProjectData {
@IsNotEmpty({ message: "Please enter a name for your project" })
@IsOptional()
readonly name: string;
readonly name?: string;

@IsArray()
@ArrayNotEmpty()
@IsOptional()
readonly districtsDefinition: DistrictsDefinition;
readonly districtsDefinition?: DistrictsDefinition;

@IsArray()
@ArrayNotEmpty()
@IsOptional()
readonly lockedDistricts: readonly boolean[];
readonly lockedDistricts?: readonly boolean[];

@IsArray()
@ArrayNotEmpty()
@IsOptional()
readonly numberOfMembers: readonly number[];
readonly numberOfMembers?: readonly number[];

@IsBoolean()
@IsOptional()
readonly advancedEditingEnabled: boolean;
readonly advancedEditingEnabled?: boolean;

@IsOptional()
@IsNumber()
@Max(100, { message: "Population deviation must be between 0% and 100%" })
@Min(0, { message: "Population deviation must be between 0% and 100%" })
readonly populationDeviation: number;
readonly populationDeviation?: number;

@IsEnum(ProjectVisibility)
@IsOptional()
readonly visibility: ProjectVisibility;
readonly visibility?: ProjectVisibility;

@IsOptional()
@IsArray()
@ArrayNotEmpty()
readonly pinnedMetricFields: string[];
readonly pinnedMetricFields?: string[];

@IsBoolean()
@IsOptional()
readonly archived: boolean;
readonly archived?: boolean;
}
44 changes: 44 additions & 0 deletions src/server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,13 @@
terminal-link "^2.0.0"
v8-to-istanbul "^8.1.0"

"@jest/schemas@^28.1.3":
version "28.1.3"
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905"
integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==
dependencies:
"@sinclair/typebox" "^0.24.1"

"@jest/source-map@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf"
Expand Down Expand Up @@ -765,6 +772,18 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"

"@jest/types@^28.1.3":
version "28.1.3"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b"
integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==
dependencies:
"@jest/schemas" "^28.1.3"
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^17.0.8"
chalk "^4.0.0"

"@jonkemp/package-utils@^1.0.6":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@jonkemp/package-utils/-/package-utils-1.0.7.tgz#6550ea56c9bd61bb4161c99e7ca38b972ad3a25d"
Expand Down Expand Up @@ -1026,6 +1045,11 @@
domhandler "^4.2.0"
selderee "^0.6.0"

"@sinclair/typebox@^0.24.1":
version "0.24.19"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.19.tgz#5297278e0d8a1aea084685a3216074910ac6c113"
integrity sha512-gHJu8cdYTD5p4UqmQHrxaWrtb/jkH5imLXzuBypWhKzNkW0qfmgz+w1xaJccWVuJta1YYUdlDiPHXRTR4Ku0MQ==

"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
Expand Down Expand Up @@ -1489,6 +1513,11 @@
dependencies:
"@types/geojson" "*"

"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==

"@types/[email protected]":
version "13.7.1"
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.1.tgz#cdab1b4779f6b1718a08de89d92d2603b71950cb"
Expand All @@ -1506,6 +1535,13 @@
dependencies:
"@types/yargs-parser" "*"

"@types/yargs@^17.0.8":
version "17.0.10"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a"
integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==
dependencies:
"@types/yargs-parser" "*"

"@types/[email protected]":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
Expand Down Expand Up @@ -4907,6 +4943,14 @@ jest-mock@^27.5.1:
"@jest/types" "^27.5.1"
"@types/node" "*"

jest-mock@^28.1.3:
version "28.1.3"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da"
integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==
dependencies:
"@jest/types" "^28.1.3"
"@types/node" "*"

jest-pnp-resolver@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
Expand Down
20 changes: 11 additions & 9 deletions src/shared/entities.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,17 @@ export interface CreateReferenceLayerData {
readonly layer_color: ReferenceLayerColors;
}

export type UpdateProjectData = Pick<
IProject,
| "name"
| "districtsDefinition"
| "advancedEditingEnabled"
| "lockedDistricts"
| "pinnedMetricFields"
| "visibility"
| "archived"
export type UpdateProjectData = Partial<
Pick<
IProject,
| "name"
| "districtsDefinition"
| "advancedEditingEnabled"
| "lockedDistricts"
| "pinnedMetricFields"
| "visibility"
| "archived"
>
>;

export type ProjectTemplateId = string;
Expand Down

0 comments on commit be421ca

Please sign in to comment.