From ca20bc5ffe144028ab7754770183fb01dc69ba60 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Wed, 13 Nov 2024 15:25:21 +0100 Subject: [PATCH 1/7] feat: add type property to the proposal document with database migration --- migrations/20241113130700-proposal-type.js | 12 +++++++ src/proposals/dto/update-proposal.dto.ts | 17 +++++++++ src/proposals/proposal-type.enum.ts | 5 +++ src/proposals/schemas/proposal.schema.ts | 25 +++++++++++++ test/Proposal.js | 41 ++++++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 migrations/20241113130700-proposal-type.js create mode 100644 src/proposals/proposal-type.enum.ts diff --git a/migrations/20241113130700-proposal-type.js b/migrations/20241113130700-proposal-type.js new file mode 100644 index 000000000..8cc924519 --- /dev/null +++ b/migrations/20241113130700-proposal-type.js @@ -0,0 +1,12 @@ +module.exports = { + async up(db, client) { + db.collection("Proposal").updateMany( + { type: { $exists: false } }, + { $set: { type: "Default Proposal" } }, + ); + }, + + async down(db, client) { + db.collection("Proposal").updateMany({}, { $unset: { type: 1 } }); + }, +}; diff --git a/src/proposals/dto/update-proposal.dto.ts b/src/proposals/dto/update-proposal.dto.ts index 541c96fbe..a10cd2aa6 100644 --- a/src/proposals/dto/update-proposal.dto.ts +++ b/src/proposals/dto/update-proposal.dto.ts @@ -4,12 +4,14 @@ import { IsArray, IsDateString, IsEmail, + IsEnum, IsObject, IsOptional, IsString, ValidateNested, } from "class-validator"; import { OwnableDto } from "../../common/dto/ownable.dto"; +import { ProposalType } from "../proposal-type.enum"; import { CreateMeasurementPeriodDto } from "./create-measurement-period.dto"; @ApiTags("proposals") @@ -133,6 +135,21 @@ export class UpdateProposalDto extends OwnableDto { @IsOptional() @IsString() readonly parentProposalId?: string; + + @ApiProperty({ + type: String, + required: false, + enum: [ + ProposalType.DefaultProposal, + ProposalType.DOORProposal, + ProposalType.Beamtime, + ], + description: + "Characterize type of proposal, either 'Default Proposal', 'DOOR Proposal' or 'Beamtime'", + }) + @IsOptional() + @IsEnum(ProposalType) + readonly type?: string; } export class PartialUpdateProposalDto extends PartialType(UpdateProposalDto) {} diff --git a/src/proposals/proposal-type.enum.ts b/src/proposals/proposal-type.enum.ts new file mode 100644 index 000000000..cad50be53 --- /dev/null +++ b/src/proposals/proposal-type.enum.ts @@ -0,0 +1,5 @@ +export enum ProposalType { + DefaultProposal = "Default Proposal", + DOORProposal = "DOOR Proposal", + Beamtime = "Beamtime", +} diff --git a/src/proposals/schemas/proposal.schema.ts b/src/proposals/schemas/proposal.schema.ts index 970931252..94b386a43 100644 --- a/src/proposals/schemas/proposal.schema.ts +++ b/src/proposals/schemas/proposal.schema.ts @@ -3,6 +3,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { Document } from "mongoose"; import { OwnableClass } from "src/common/schemas/ownable.schema"; +import { ProposalType } from "../proposal-type.enum"; import { MeasurementPeriodClass, MeasurementPeriodSchema, @@ -182,6 +183,30 @@ export class ProposalClass extends OwnableClass { ref: "Proposal", }) parentProposalId: string; + + @ApiProperty({ + type: String, + required: true, + enum: [ + ProposalType.DefaultProposal, + ProposalType.DOORProposal, + ProposalType.Beamtime, + ], + default: ProposalType.DefaultProposal, + description: + "Characterize type of proposal, either 'Default Proposal', 'DOOR Proposal' or 'Beamtime'", + }) + @Prop({ + type: String, + required: true, + enum: [ + ProposalType.DefaultProposal, + ProposalType.DOORProposal, + ProposalType.Beamtime, + ], + default: ProposalType.DefaultProposal, + }) + type: string; } export const ProposalSchema = SchemaFactory.createForClass(ProposalClass); diff --git a/test/Proposal.js b/test/Proposal.js index a6736a96d..1efa4ea26 100644 --- a/test/Proposal.js +++ b/test/Proposal.js @@ -171,6 +171,8 @@ describe("1500: Proposal: Simple Proposal", () => { .then((res) => { res.body.should.have.property("createdBy").and.be.string; res.body.should.have.property("updatedBy").and.be.string; + res.body.should.have.property("type").and.be.string; + res.body.type.should.be.equal("Default Proposal"); }); }); @@ -241,6 +243,45 @@ describe("1500: Proposal: Simple Proposal", () => { }); }); + it("0116: adds a new proposal with a type different than default", async () => { + const proposalType = "DOOR Proposal"; + const proposalWithType = { + ...TestData.ProposalCorrectComplete, + proposalId: faker.string.numeric(8), + type: proposalType, + }; + + return request(appUrl) + .post("/api/v3/Proposals") + .send(proposalWithType) + .set("Accept", "application/json") + .set({ Authorization: `Bearer ${accessTokenProposalIngestor}` }) + .expect(TestData.EntryCreatedStatusCode) + .expect("Content-Type", /json/) + .then((res) => { + res.body.should.have.property("ownerGroup").and.be.string; + res.body.should.have.property("proposalId").and.be.string; + res.body.type.should.be.equal(proposalType); + }); + }); + + it("0117: cannot add a new proposal with a type different than predefined proposal types", async () => { + const proposalType = "Incorrect type"; + const proposalWithIncorrectType = { + ...TestData.ProposalCorrectComplete, + proposalId: faker.string.numeric(8), + type: proposalType, + }; + + return request(appUrl) + .post("/api/v3/Proposals") + .send(proposalWithIncorrectType) + .set("Accept", "application/json") + .set({ Authorization: `Bearer ${accessTokenProposalIngestor}` }) + .expect(TestData.BadRequestStatusCode) + .expect("Content-Type", /json/); + }); + it("0120: updates a proposal with a new parent proposal", async () => { return request(appUrl) .patch("/api/v3/Proposals/" + proposalWithParentId) From ab65cf713f0d7f2599dba2bd10fa3c200c1b0ad7 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Fri, 15 Nov 2024 16:47:52 +0100 Subject: [PATCH 2/7] trying to load and use proposalTypes.json as allowed proposal types --- proposalTypes.json | 5 +++++ src/config/configuration.ts | 6 ++++++ src/proposals/dto/update-proposal.dto.ts | 11 +++-------- src/proposals/proposal-type.enum.ts | 5 ----- src/proposals/proposals.module.ts | 20 ++++++++++++++++++-- src/proposals/schemas/proposal.schema.ts | 17 ++--------------- 6 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 proposalTypes.json delete mode 100644 src/proposals/proposal-type.enum.ts diff --git a/proposalTypes.json b/proposalTypes.json new file mode 100644 index 000000000..1846fe873 --- /dev/null +++ b/proposalTypes.json @@ -0,0 +1,5 @@ +{ + "DefaultProposal": "Default Proposal", + "DOORProposal": "DOOR Proposal", + "Beamtime": "Beamtime" +} diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 1fc02fbdc..689a1594e 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -57,6 +57,11 @@ const configuration = () => { } }); + const proposalTypesData = fs.readFileSync("proposalTypes.json", "utf8"); + const proposalTypes: Record = JSON.parse( + proposalTypesData || '{"DefaultProposal": "Default Proposal"}', + ); + const config = { maxFileUploadSizeInMb: process.env.MAX_FILE_UPLOAD_SIZE || "16mb", // 16MB by default versions: { @@ -203,6 +208,7 @@ const configuration = () => { policyPublicationShiftInYears: process.env.POLICY_PUBLICATION_SHIFT ?? 3, policyRetentionShiftInYears: process.env.POLICY_RETENTION_SHIFT ?? -1, }, + proposalTypes: proposalTypes, }; return merge(config, localconfiguration); }; diff --git a/src/proposals/dto/update-proposal.dto.ts b/src/proposals/dto/update-proposal.dto.ts index a10cd2aa6..87730b93e 100644 --- a/src/proposals/dto/update-proposal.dto.ts +++ b/src/proposals/dto/update-proposal.dto.ts @@ -11,7 +11,6 @@ import { ValidateNested, } from "class-validator"; import { OwnableDto } from "../../common/dto/ownable.dto"; -import { ProposalType } from "../proposal-type.enum"; import { CreateMeasurementPeriodDto } from "./create-measurement-period.dto"; @ApiTags("proposals") @@ -139,16 +138,12 @@ export class UpdateProposalDto extends OwnableDto { @ApiProperty({ type: String, required: false, - enum: [ - ProposalType.DefaultProposal, - ProposalType.DOORProposal, - ProposalType.Beamtime, - ], + // TODO: Maybe the description should be updated based on the config if possible description: - "Characterize type of proposal, either 'Default Proposal', 'DOOR Proposal' or 'Beamtime'", + "Characterize type of proposal, use some of the configured values", }) @IsOptional() - @IsEnum(ProposalType) + @IsString() readonly type?: string; } diff --git a/src/proposals/proposal-type.enum.ts b/src/proposals/proposal-type.enum.ts deleted file mode 100644 index cad50be53..000000000 --- a/src/proposals/proposal-type.enum.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum ProposalType { - DefaultProposal = "Default Proposal", - DOORProposal = "DOOR Proposal", - Beamtime = "Beamtime", -} diff --git a/src/proposals/proposals.module.ts b/src/proposals/proposals.module.ts index 557081bdb..d1b302d22 100644 --- a/src/proposals/proposals.module.ts +++ b/src/proposals/proposals.module.ts @@ -1,4 +1,4 @@ -import { forwardRef, Module } from "@nestjs/common"; +import { BadRequestException, forwardRef, Module } from "@nestjs/common"; import { ProposalsService } from "./proposals.service"; import { ProposalsController } from "./proposals.controller"; import { MongooseModule } from "@nestjs/mongoose"; @@ -6,6 +6,7 @@ import { ProposalClass, ProposalSchema } from "./schemas/proposal.schema"; import { CaslAbilityFactory } from "src/casl/casl-ability.factory"; import { AttachmentsModule } from "src/attachments/attachments.module"; import { DatasetsModule } from "src/datasets/datasets.module"; +import { ConfigModule, ConfigService } from "@nestjs/config"; @Module({ imports: [ @@ -14,7 +15,11 @@ import { DatasetsModule } from "src/datasets/datasets.module"; MongooseModule.forFeatureAsync([ { name: ProposalClass.name, - useFactory: () => { + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => { + const proposalTypes = configService.get("proposalTypes"); + const proposalTypesArray = Object.values(proposalTypes); const schema = ProposalSchema; schema.pre("save", function (next) { @@ -23,6 +28,17 @@ import { DatasetsModule } from "src/datasets/datasets.module"; if (!this._id) { this._id = this.proposalId; } + + // TODO: Check if there is a better way of doing this. So far I haven't found a way to load the confituration into the schema and DTOs. + if (!this.type) { + this.type = proposalTypes?.DefaultProposal; + } else { + if (!proposalTypesArray.includes(this.type)) { + throw new BadRequestException( + `type must be one of the following values: ${proposalTypesArray.join(", ")}`, + ); + } + } next(); }); return schema; diff --git a/src/proposals/schemas/proposal.schema.ts b/src/proposals/schemas/proposal.schema.ts index 94b386a43..b9d1395a1 100644 --- a/src/proposals/schemas/proposal.schema.ts +++ b/src/proposals/schemas/proposal.schema.ts @@ -3,7 +3,6 @@ import { ApiProperty } from "@nestjs/swagger"; import { Document } from "mongoose"; import { OwnableClass } from "src/common/schemas/ownable.schema"; -import { ProposalType } from "../proposal-type.enum"; import { MeasurementPeriodClass, MeasurementPeriodSchema, @@ -186,25 +185,13 @@ export class ProposalClass extends OwnableClass { @ApiProperty({ type: String, - required: true, - enum: [ - ProposalType.DefaultProposal, - ProposalType.DOORProposal, - ProposalType.Beamtime, - ], - default: ProposalType.DefaultProposal, + required: false, description: "Characterize type of proposal, either 'Default Proposal', 'DOOR Proposal' or 'Beamtime'", }) @Prop({ type: String, - required: true, - enum: [ - ProposalType.DefaultProposal, - ProposalType.DOORProposal, - ProposalType.Beamtime, - ], - default: ProposalType.DefaultProposal, + required: false, }) type: string; } From 1e8e8018d6c4657d2510ae250cceb3029d84062e Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 18 Nov 2024 10:42:02 +0100 Subject: [PATCH 3/7] use the proposalTypes.json for validation and update documentation --- .gitignore | 1 + README.md | 22 +++++++++++++------ ...alTypes.json => proposalTypes.example.json | 0 src/proposals/dto/update-proposal.dto.ts | 1 - src/proposals/proposals.module.ts | 5 +++-- src/proposals/proposals.service.ts | 22 ++++++++++--------- src/proposals/schemas/proposal.schema.ts | 11 +++++++--- 7 files changed, 39 insertions(+), 23 deletions(-) rename proposalTypes.json => proposalTypes.example.json (100%) diff --git a/.gitignore b/.gitignore index a29dbc032..924c630ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /dist /node_modules functionalAccounts.json +proposalTypes.json loggers.json # Configs diff --git a/README.md b/README.md index 1f4c418bb..6bd2163c9 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,10 @@ Thank you for your interest in contributing to our project! 3. Add _.env_ file to project root folder. See [Environment variables](#environment-variables). 4. _Optional_ Add [functionalAccounts.json](#local-user-accounts) file to project root folder to create local users. 5. _Optional_ Add [loggers.json](#loggers-configuration) file to the root folder and configure multiple loggers. -6. `npm run start:dev` -7. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. -8. To be able to run the e2e tests with the same setup as in the Github actions you will need to run `npm run prepare:local` and after that run `npm run start:dev`. This will start all needed containers and copy some configuration to the right place. +6. _Optional_ Add [proposalTypes.json](#prpopsal-types-configuration) file to the root folder and configure the proposal types. +7. `npm run start:dev` +8. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. +9. To be able to run the e2e tests with the same setup as in the Github actions you will need to run `npm run prepare:local` and after that run `npm run start:dev`. This will start all needed containers and copy some configuration to the right place. ## Develop in a container using the docker-compose.dev file @@ -50,10 +51,11 @@ Thank you for your interest in contributing to our project! 2. docker-compose -f docker-compose.dev.yaml up -d 3. _Optional_ Mount [functionalAccounts.json](#local-user-accounts) file to a volume in the container to create local users. 4. _Optional_ Mount [loggers.json](#loggers-configuration) file to a volume in the container to configure multiple loggers. -5. _Optional_ change the container env variables -6. Attach to the container -7. `npm run start:dev` -8. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. +5. _Optional_ Mount [proposalTypes.json](#prpopsal-types-configuration) file to a volume in the container to configure the proposal types. +6. _Optional_ change the container env variables +7. Attach to the container +8. `npm run start:dev` +9. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. ## Test the app @@ -96,6 +98,12 @@ Providing a file called _loggers.json_ at the root of the project, locally or in The `loggers.json.example` file in the root directory showcases the example of configuration structure for the one or multiple loggers. `logger.service.ts` file contains the configuration handling process logic, and `src/loggers/loggingProviders/grayLogger.ts` includes actual usecase of grayLogger. +### Prpopsal types configuration + +Providing a file called _proposalTypes.json_ at the root of the project, locally or in the container, will be automatically loaded into the application configuration service under property called `proposalTypes` and used for validation against proposal creation and update. + +The `proposalTypes.json.example` file in the root directory showcases the example of configuration structure for proposal types. + ## Environment variables Valid environment variables for the .env file. See [.env.example](/.env.example) for examples value formats. diff --git a/proposalTypes.json b/proposalTypes.example.json similarity index 100% rename from proposalTypes.json rename to proposalTypes.example.json diff --git a/src/proposals/dto/update-proposal.dto.ts b/src/proposals/dto/update-proposal.dto.ts index 87730b93e..c16ef17cb 100644 --- a/src/proposals/dto/update-proposal.dto.ts +++ b/src/proposals/dto/update-proposal.dto.ts @@ -138,7 +138,6 @@ export class UpdateProposalDto extends OwnableDto { @ApiProperty({ type: String, required: false, - // TODO: Maybe the description should be updated based on the config if possible description: "Characterize type of proposal, use some of the configured values", }) diff --git a/src/proposals/proposals.module.ts b/src/proposals/proposals.module.ts index d1b302d22..3b85943e2 100644 --- a/src/proposals/proposals.module.ts +++ b/src/proposals/proposals.module.ts @@ -18,8 +18,8 @@ import { ConfigModule, ConfigService } from "@nestjs/config"; imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => { - const proposalTypes = configService.get("proposalTypes"); - const proposalTypesArray = Object.values(proposalTypes); + const proposalTypes = configService.get("proposalTypes") || "{}"; + const proposalTypesArray: string[] = Object.values(proposalTypes); const schema = ProposalSchema; schema.pre("save", function (next) { @@ -41,6 +41,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config"; } next(); }); + return schema; }, }, diff --git a/src/proposals/proposals.service.ts b/src/proposals/proposals.service.ts index 610163b32..09258fdc0 100644 --- a/src/proposals/proposals.service.ts +++ b/src/proposals/proposals.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Scope } from "@nestjs/common"; +import { Inject, Injectable, NotFoundException, Scope } from "@nestjs/common"; import { REQUEST } from "@nestjs/core"; import { Request } from "express"; import { InjectModel } from "@nestjs/mongoose"; @@ -97,15 +97,17 @@ export class ProposalsService { ): Promise { const username = (this.request.user as JWTUser).username; - return this.proposalModel - .findOneAndUpdate( - filter, - addUpdatedByField(updateProposalDto, username), - { - new: true, - }, - ) - .exec(); + const proposal = await this.proposalModel.findOne(filter); + + if (!proposal) { + throw new NotFoundException(`Proposal with filter: ${filter} not found`); + } + + Object.assign(proposal, addUpdatedByField(updateProposalDto, username)); + + const updatedProposal = new this.proposalModel(proposal); + + return updatedProposal.save(); } async remove(filter: FilterQuery): Promise { diff --git a/src/proposals/schemas/proposal.schema.ts b/src/proposals/schemas/proposal.schema.ts index b9d1395a1..fb728c3db 100644 --- a/src/proposals/schemas/proposal.schema.ts +++ b/src/proposals/schemas/proposal.schema.ts @@ -8,6 +8,9 @@ import { MeasurementPeriodSchema, } from "./measurement-period.schema"; +// NOTE: This is the proposal default type and it will be used if no proposalTypes.json config file is provided +const DEFAULT_PROPOSAL_TYPE = "Default Proposal"; + export type ProposalDocument = ProposalClass & Document; @Schema({ collection: "Proposal", @@ -185,13 +188,15 @@ export class ProposalClass extends OwnableClass { @ApiProperty({ type: String, - required: false, + required: true, + default: DEFAULT_PROPOSAL_TYPE, description: - "Characterize type of proposal, either 'Default Proposal', 'DOOR Proposal' or 'Beamtime'", + "Characterize type of proposal, use some of the configured values", }) @Prop({ type: String, - required: false, + required: true, + default: DEFAULT_PROPOSAL_TYPE, }) type: string; } From a7d85d6bbc55bac73bc3c321bcf7fd4b3bac4017 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 18 Nov 2024 10:54:32 +0100 Subject: [PATCH 4/7] show only warning if the proposalTypes.json is not provided --- src/config/configuration.ts | 19 +++++++++++++++++-- src/proposals/proposals.module.ts | 14 +++++--------- src/proposals/schemas/proposal.schema.ts | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 689a1594e..61dc7ce3b 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -2,6 +2,7 @@ import * as fs from "fs"; import { merge } from "lodash"; import localconfiguration from "./localconfiguration"; import { boolean } from "mathjs"; +import { DEFAULT_PROPOSAL_TYPE } from "src/proposals/schemas/proposal.schema"; const configuration = () => { const accessGroupsStaticValues = @@ -57,9 +58,23 @@ const configuration = () => { } }); - const proposalTypesData = fs.readFileSync("proposalTypes.json", "utf8"); + let proposalTypesData; + + try { + proposalTypesData = fs.readFileSync("proposalTypes.json", "utf8"); + } catch (error) { + // NOTE: If there is no proposalTypes.json file provided we just use the DEFAULT_PROPOSAL_TYPE type + console.warn(error); + } + + if (!proposalTypesData) { + console.warn( + `Proposal types configuration file not provided. The "${DEFAULT_PROPOSAL_TYPE}" type will be used`, + ); + } + const proposalTypes: Record = JSON.parse( - proposalTypesData || '{"DefaultProposal": "Default Proposal"}', + proposalTypesData || `{"DefaultProposal": "${DEFAULT_PROPOSAL_TYPE}"}`, ); const config = { diff --git a/src/proposals/proposals.module.ts b/src/proposals/proposals.module.ts index 3b85943e2..c684c2e87 100644 --- a/src/proposals/proposals.module.ts +++ b/src/proposals/proposals.module.ts @@ -29,16 +29,12 @@ import { ConfigModule, ConfigService } from "@nestjs/config"; this._id = this.proposalId; } - // TODO: Check if there is a better way of doing this. So far I haven't found a way to load the confituration into the schema and DTOs. - if (!this.type) { - this.type = proposalTypes?.DefaultProposal; - } else { - if (!proposalTypesArray.includes(this.type)) { - throw new BadRequestException( - `type must be one of the following values: ${proposalTypesArray.join(", ")}`, - ); - } + if (this.type && !proposalTypesArray.includes(this.type)) { + throw new BadRequestException( + `type must be one of the following values: ${proposalTypesArray.join(", ")}`, + ); } + next(); }); diff --git a/src/proposals/schemas/proposal.schema.ts b/src/proposals/schemas/proposal.schema.ts index fb728c3db..9ab2cf1ae 100644 --- a/src/proposals/schemas/proposal.schema.ts +++ b/src/proposals/schemas/proposal.schema.ts @@ -9,7 +9,7 @@ import { } from "./measurement-period.schema"; // NOTE: This is the proposal default type and it will be used if no proposalTypes.json config file is provided -const DEFAULT_PROPOSAL_TYPE = "Default Proposal"; +export const DEFAULT_PROPOSAL_TYPE = "Default Proposal"; export type ProposalDocument = ProposalClass & Document; @Schema({ From a31b24de7e757e7e9778f5eeafc9026c1bcc5d2b Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 18 Nov 2024 11:15:05 +0100 Subject: [PATCH 5/7] add proposalTypes.json in the test action --- .github/workflows/test.yml | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a757094f9..e564bcaf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -173,5 +173,6 @@ jobs: run: | cp CI/ESS/docker-compose.api.yaml docker-compose.yaml cp functionalAccounts.json.test functionalAccounts.json + cp proposalTypes.example.json proposalTypes.json docker compose up --build -d npm run test:api diff --git a/package.json b/package.json index 6b64c6d86..c8265d49f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test:api": "npm run test:api:jest --maxWorkers=50% && concurrently -k -s first \"wait-on http://localhost:3000/explorer/ && npm run test:api:mocha\" \"npm run start\"", "test:api:jest": "jest --config ./test/config/jest-e2e.json --maxWorkers=50%", "test:api:mocha": "mocha --config ./test/config/.mocharc.json -r chai/register-should.js", - "prepare:local": "docker-compose -f CI/E2E/docker-compose-local.yaml --env-file CI/E2E/.env.elastic-search up -d && cp functionalAccounts.json.test functionalAccounts.json" + "prepare:local": "docker-compose -f CI/E2E/docker-compose-local.yaml --env-file CI/E2E/.env.elastic-search up -d && cp functionalAccounts.json.test functionalAccounts.json && cp proposalTypes.example.json proposalTypes.json" }, "dependencies": { "@casl/ability": "^6.3.2", From 76117e9462a14a5f783b1b349f48c706bb5e2d22 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 18 Nov 2024 13:34:32 +0100 Subject: [PATCH 6/7] add default proposal type by default --- proposalTypes.example.json | 1 - src/config/configuration.ts | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/proposalTypes.example.json b/proposalTypes.example.json index 1846fe873..912c44040 100644 --- a/proposalTypes.example.json +++ b/proposalTypes.example.json @@ -1,5 +1,4 @@ { - "DefaultProposal": "Default Proposal", "DOORProposal": "DOOR Proposal", "Beamtime": "Beamtime" } diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 61dc7ce3b..e9eec5399 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -73,9 +73,10 @@ const configuration = () => { ); } - const proposalTypes: Record = JSON.parse( - proposalTypesData || `{"DefaultProposal": "${DEFAULT_PROPOSAL_TYPE}"}`, - ); + const proposalTypes: Record = { + ...JSON.parse(proposalTypesData || "{}"), + DefaultProposal: DEFAULT_PROPOSAL_TYPE, + }; const config = { maxFileUploadSizeInMb: process.env.MAX_FILE_UPLOAD_SIZE || "16mb", // 16MB by default From 8752f52a8f114e6bce4bc52d864dcdf3f8d178c2 Mon Sep 17 00:00:00 2001 From: martintrajanovski Date: Mon, 18 Nov 2024 15:01:52 +0100 Subject: [PATCH 7/7] load the proposalTypes.json using the existing functionality and add PROPOSAL_TYPES_FILE env variable --- .env.example | 1 + README.md | 1 + src/config/configuration.ts | 28 ++++++++-------------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 32b3cdc9a..869d056e4 100644 --- a/.env.example +++ b/.env.example @@ -79,3 +79,4 @@ ES_PASSWORD="duo-password" ES_REFRESH=<"wait_for"|"false"> LOGGERS_CONFIG_FILE="loggers.json" +PROPOSAL_TYPES_FILE="proposalTypes.json" diff --git a/README.md b/README.md index 6bd2163c9..dd85794f1 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Valid environment variables for the .env file. See [.env.example](/.env.example) | `ES_FIELDS_LIMIT` | number | | The total number of fields in an index. | 1000 | | `ES_REFRESH` | string | | If set to `wait_for`, Elasticsearch will wait till data is inserted into the specified index before returning a response. | false | | `LOGGERS_CONFIG_FILE` | string | | The file name for loggers configuration, located in the project root directory. | "loggers.json" | +| `PROPOSAL_TYPES_FILE` | string | | The file name for proposal types configuration, located in the project root directory. | "proposalTypes.json" | | `SWAGGER_PATH` | string | Yes | swaggerPath is the path where the swagger UI will be available| "explorer"| | `MAX_FILE_UPLOAD_SIZE` | string | Yes | Maximum allowed file upload size | "16mb"| diff --git a/src/config/configuration.ts b/src/config/configuration.ts index e9eec5399..971a2205a 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -39,9 +39,12 @@ const configuration = () => { modulePath: "./loggingProviders/defaultLogger", config: {}, }; - const jsonConfigMap: { [key: string]: object[] | boolean } = {}; + const jsonConfigMap: { [key: string]: object | object[] | boolean } = { + proposalTypes: {}, + }; const jsonConfigFileList: { [key: string]: string } = { loggers: process.env.LOGGERS_CONFIG_FILE || "loggers.json", + proposalTypes: process.env.PROPOSAL_TYPES_FILE || "proposalTypes.json", }; Object.keys(jsonConfigFileList).forEach((key) => { const filePath = jsonConfigFileList[key]; @@ -58,25 +61,10 @@ const configuration = () => { } }); - let proposalTypesData; - - try { - proposalTypesData = fs.readFileSync("proposalTypes.json", "utf8"); - } catch (error) { - // NOTE: If there is no proposalTypes.json file provided we just use the DEFAULT_PROPOSAL_TYPE type - console.warn(error); - } - - if (!proposalTypesData) { - console.warn( - `Proposal types configuration file not provided. The "${DEFAULT_PROPOSAL_TYPE}" type will be used`, - ); - } - - const proposalTypes: Record = { - ...JSON.parse(proposalTypesData || "{}"), + // NOTE: Add the default proposal type here + Object.assign(jsonConfigMap.proposalTypes, { DefaultProposal: DEFAULT_PROPOSAL_TYPE, - }; + }); const config = { maxFileUploadSizeInMb: process.env.MAX_FILE_UPLOAD_SIZE || "16mb", // 16MB by default @@ -224,7 +212,7 @@ const configuration = () => { policyPublicationShiftInYears: process.env.POLICY_PUBLICATION_SHIFT ?? 3, policyRetentionShiftInYears: process.env.POLICY_RETENTION_SHIFT ?? -1, }, - proposalTypes: proposalTypes, + proposalTypes: jsonConfigMap.proposalTypes, }; return merge(config, localconfiguration); };