From 2ea60995d19a9cc9ddb911e7c96966bcc330b260 Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Wed, 22 May 2024 16:48:59 +0200 Subject: [PATCH 01/10] generate and copy api types on install --- confiture-rest-api/.gitignore | 3 + confiture-rest-api/package.json | 4 +- .../src/generate-api-typings.ts | 24 +++++++ confiture-rest-api/tsconfig.build.json | 9 ++- confiture-web-app/.gitignore | 1 + package.json | 4 +- yarn.lock | 66 ++++++++++--------- 7 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 confiture-rest-api/src/generate-api-typings.ts diff --git a/confiture-rest-api/.gitignore b/confiture-rest-api/.gitignore index b6bb8bc1..47c00b7a 100644 --- a/confiture-rest-api/.gitignore +++ b/confiture-rest-api/.gitignore @@ -36,3 +36,6 @@ lerna-debug.log* .env src/generated + +# Generated API typings +confiture-api.ts diff --git a/confiture-rest-api/package.json b/confiture-rest-api/package.json index 0e958359..7e37f727 100644 --- a/confiture-rest-api/package.json +++ b/confiture-rest-api/package.json @@ -12,7 +12,8 @@ "lint": "eslint \"src/**/*.ts\" --fix", "migrate:dev": "prisma migrate dev", "migrate:prod": "prisma migrate deploy", - "postinstall": "prisma generate" + "postinstall": "prisma generate && yarn generate-api-types", + "generate-api-types": "nest start --entryFile generate-api-typings.js" }, "dependencies": { "@aws-sdk/client-s3": "^3.218.0", @@ -62,6 +63,7 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-simple-import-sort": "^12.1.0", "eslint-plugin-unused-imports": "^3.1.0", + "openapi-typescript": "^6.7.6", "prettier": "^3.1.1", "source-map-support": "^0.5.20", "ts-loader": "^9.2.3", diff --git a/confiture-rest-api/src/generate-api-typings.ts b/confiture-rest-api/src/generate-api-typings.ts new file mode 100644 index 00000000..e34bc4d4 --- /dev/null +++ b/confiture-rest-api/src/generate-api-typings.ts @@ -0,0 +1,24 @@ +import { NestFactory } from "@nestjs/core"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import { writeFile } from "fs/promises"; +import openapiTS, { OpenAPI3 } from "openapi-typescript"; +import { resolve } from "path"; + +import { AppModule } from "./app.module"; + +async function main() { + const app = await NestFactory.create(AppModule); + + const config = new DocumentBuilder().setTitle("Confiture API").build(); + const document = SwaggerModule.createDocument(app, config); + + const ast = await openapiTS(document as OpenAPI3); + const fileContent = "/* eslint-disable */\n" + ast; + const resolvedPath = resolve(process.cwd(), "./confiture-api.ts"); + await writeFile(resolvedPath, fileContent, { + encoding: "utf-8" + }); + console.log("✅ Typings saved to", resolvedPath); +} + +main(); diff --git a/confiture-rest-api/tsconfig.build.json b/confiture-rest-api/tsconfig.build.json index 052134cd..08260a7f 100644 --- a/confiture-rest-api/tsconfig.build.json +++ b/confiture-rest-api/tsconfig.build.json @@ -1,4 +1,11 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "scripts"] + "exclude": [ + "node_modules", + "test", + "dist", + "**/*spec.ts", + "scripts", + "date-poll-api.ts" + ] } diff --git a/confiture-web-app/.gitignore b/confiture-web-app/.gitignore index eecbde4e..75d821b8 100644 --- a/confiture-web-app/.gitignore +++ b/confiture-web-app/.gitignore @@ -34,3 +34,4 @@ coverage accessibilite.numerique.gouv.fr src/methodologies.json src/criteres.json +src/types/confiture-api.ts \ No newline at end of file diff --git a/package.json b/package.json index bd9a5476..5d5ec557 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "lint-staged": "^15.2.0" }, "scripts": { - "heroku-postbuild": "yarn workspaces run build" + "heroku-postbuild": "yarn workspaces run build", + "copytypes": "yarn workspace confiture-rest-api run generate-api-types && cp ./confiture-rest-api/confiture-api.ts ./confiture-web-app/src/types", + "postinstall": "yarn copytypes" }, "dependencies": { "pm2": "^5.4.0" diff --git a/yarn.lock b/yarn.lock index 5dd55513..8d727e10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -857,6 +857,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + "@gouvfr/dsfr@1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@gouvfr/dsfr/-/dsfr-1.12.1.tgz#c25df1df5d3bacc4b847a1856c1d7f22219304a8" @@ -2825,7 +2830,7 @@ amp@0.3.1, amp@~0.3.1: resolved "https://registry.yarnpkg.com/amp/-/amp-0.3.1.tgz#6adf8d58a74f361e82c1fa8d389c079e139fc47d" integrity sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw== -ansi-colors@4.1.3, ansi-colors@^4.1.1: +ansi-colors@4.1.3, ansi-colors@^4.1.1, ansi-colors@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== @@ -4681,7 +4686,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -6920,6 +6925,18 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" +openapi-typescript@^6.7.6: + version "6.7.6" + resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-6.7.6.tgz#4f387199203bd7bfb94545cbc613751b52e3fa37" + integrity sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw== + dependencies: + ansi-colors "^4.1.3" + fast-glob "^3.3.2" + js-yaml "^4.1.0" + supports-color "^9.4.0" + undici "^5.28.4" + yargs-parser "^21.1.1" + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -8132,16 +8149,7 @@ string-argv@0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8182,14 +8190,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8261,6 +8262,11 @@ supports-color@^8.0.0, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" +supports-color@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== + supports-hyperlinks@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" @@ -8673,6 +8679,13 @@ undici@5.1.1: resolved "https://registry.yarnpkg.com/undici/-/undici-5.1.1.tgz#356427b0d1f032ca4cf85537b1e1694a52090438" integrity sha512-CmK9JzLSMGx+2msOao8LhkKn3J7eKo2M50v0KZQ2XbiHcGqLS1HiIj01ceIm3jbUYlspw/FTSb6nMdSNyvVyaQ== +undici@^5.28.4: + version "5.28.4" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + unhead@1.8.9: version "1.8.9" resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.8.9.tgz#f13ecee175381d634d085e52a7945c6e7937b56d" @@ -9010,7 +9023,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9028,15 +9041,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -9113,7 +9117,7 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@21.1.1: +yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== From 4206d44812ceb06f0b6ffbc4949e6d3b794ec740 Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 24 May 2024 14:43:52 +0200 Subject: [PATCH 02/10] delete dist folder before generating types --- confiture-rest-api/package.json | 2 +- confiture-rest-api/tsconfig.build.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/confiture-rest-api/package.json b/confiture-rest-api/package.json index 7e37f727..94e38fb1 100644 --- a/confiture-rest-api/package.json +++ b/confiture-rest-api/package.json @@ -13,7 +13,7 @@ "migrate:dev": "prisma migrate dev", "migrate:prod": "prisma migrate deploy", "postinstall": "prisma generate && yarn generate-api-types", - "generate-api-types": "nest start --entryFile generate-api-typings.js" + "generate-api-types": "rimraf dist && nest start --entryFile generate-api-typings.js" }, "dependencies": { "@aws-sdk/client-s3": "^3.218.0", diff --git a/confiture-rest-api/tsconfig.build.json b/confiture-rest-api/tsconfig.build.json index 08260a7f..82089442 100644 --- a/confiture-rest-api/tsconfig.build.json +++ b/confiture-rest-api/tsconfig.build.json @@ -6,6 +6,6 @@ "dist", "**/*spec.ts", "scripts", - "date-poll-api.ts" + "confiture-api.ts" ] } From d004f6bd2f89d2ec96cdb5d6522eec6c8436e14e Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 31 May 2024 10:56:25 +0200 Subject: [PATCH 03/10] disable config validation when generating types --- confiture-rest-api/package.json | 2 +- confiture-rest-api/src/app.module.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/confiture-rest-api/package.json b/confiture-rest-api/package.json index 94e38fb1..684bdd9e 100644 --- a/confiture-rest-api/package.json +++ b/confiture-rest-api/package.json @@ -13,7 +13,7 @@ "migrate:dev": "prisma migrate dev", "migrate:prod": "prisma migrate deploy", "postinstall": "prisma generate && yarn generate-api-types", - "generate-api-types": "rimraf dist && nest start --entryFile generate-api-typings.js" + "generate-api-types": "rimraf dist && GENERATE_TYPES=1 nest start --entryFile generate-api-typings.js" }, "dependencies": { "@aws-sdk/client-s3": "^3.218.0", diff --git a/confiture-rest-api/src/app.module.ts b/confiture-rest-api/src/app.module.ts index 149f65d6..937c4ff0 100644 --- a/confiture-rest-api/src/app.module.ts +++ b/confiture-rest-api/src/app.module.ts @@ -13,7 +13,9 @@ import { UserMiddleware } from "./auth/user.middleware"; imports: [ ConfigModule.forRoot({ isGlobal: true, - validationSchema: configValidationSchema + validationSchema: !process.env.GENERATE_TYPES + ? configValidationSchema + : undefined }), FeedbackModule, AuditsModule, From 9ce778137339926ff1110290918137f97940f8d1 Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 31 May 2024 16:19:59 +0200 Subject: [PATCH 04/10] use generated types in report --- .../src/audits/dto/audit-report.dto.ts | 36 +++--- .../src/components/report/ReportErrors.vue | 6 +- confiture-web-app/src/types/report.ts | 105 +----------------- confiture-web-app/src/types/types.ts | 2 + confiture-web-app/src/utils.ts | 9 +- 5 files changed, 33 insertions(+), 125 deletions(-) diff --git a/confiture-rest-api/src/audits/dto/audit-report.dto.ts b/confiture-rest-api/src/audits/dto/audit-report.dto.ts index 0ac3e5be..ec457a70 100644 --- a/confiture-rest-api/src/audits/dto/audit-report.dto.ts +++ b/confiture-rest-api/src/audits/dto/audit-report.dto.ts @@ -37,24 +37,7 @@ export class AuditReportDto { */ accessibilityRate: number; - /** - * @example { - * total: 106; - * compliant: 30; - * notCompliant: 46; - * blocking: 12; - * applicable: 76; - * notApplicable: 30; - * } - */ - criteriaCount: { - total: number; - compliant: number; - notCompliant: number; - blocking: number; - applicable: number; - notApplicable: number; - }; + criteriaCount: CriteriaCount; /** Global distribution of criteria by result */ resultDistribution: ResultDistribution; @@ -68,6 +51,21 @@ export class AuditReportDto { results: ReportCriterionResult[]; } +class CriteriaCount { + /** @example 106 */ + total: number; + /** @example 30 */ + compliant: number; + /** @example 46 */ + notCompliant: number; + /** @example 12 */ + blocking: number; + /** @example 76 */ + applicable: number; + /** @example 30 */ + notApplicable: number; +} + class RawAndPercentage { /** * @example 47 @@ -187,6 +185,8 @@ class ReportCriterionResult { userImpact: CriterionResultUserImpact | null; notApplicableComment: string | null; + + quickWin: boolean; } class ExampleImage { diff --git a/confiture-web-app/src/components/report/ReportErrors.vue b/confiture-web-app/src/components/report/ReportErrors.vue index a02a250e..304208e0 100644 --- a/confiture-web-app/src/components/report/ReportErrors.vue +++ b/confiture-web-app/src/components/report/ReportErrors.vue @@ -17,9 +17,9 @@ const defaultUserImpactFillters = [ null ]; -const userImpactFilters = ref>( - defaultUserImpactFillters -); +const userImpactFilters = ref< + Array +>(defaultUserImpactFillters); const disabledResetFilters = computed( () => diff --git a/confiture-web-app/src/types/report.ts b/confiture-web-app/src/types/report.ts index dad6f324..993997a4 100644 --- a/confiture-web-app/src/types/report.ts +++ b/confiture-web-app/src/types/report.ts @@ -1,51 +1,8 @@ -import { AuditFile, AuditType, CriteriumResult } from "../types"; +import { CriteriumResult } from "../types"; +import { paths } from "./confiture-api"; -export interface AuditReport { - consultUniqueId: string; - - contactEmail?: string; - contactFormUrl?: string; - - procedureInitiator?: string; - procedureName: string; - procedureUrl?: string; - - creationDate?: string; - publishDate?: string; - updateDate?: string; - - notCompliantContent?: string; - derogatedContent?: string; - notInScopeContent?: string; - notes?: string; - notesFiles: AuditFile[]; - - auditType: AuditType; - - context: AuditReportContext; - - accessibilityRate: number; - - criteriaCount: { - total: number; - compliant: number; - notCompliant: number; - blocking: number; - applicable: number; - notApplicable: number; - }; - - /** Global distribution of criteria by result */ - resultDistribution: ResultDistribution; - - /** Distribution of criteria by page */ - pageDistributions: PageResultDistribution[]; - - /** Distribution of criteria by topic */ - topicDistributions: TopicResultDistribution[]; - - results: Array; -} +export type AuditReport = + paths["/reports/{consultUniqueId}"]["get"]["responses"]["200"]["content"]["application/json"]; export type ReportCriteriumResult = Omit & { exampleImages: { @@ -54,57 +11,3 @@ export type ReportCriteriumResult = Omit & { filename: string; }[]; }; - -interface ResultDistribution { - compliant: { - raw: number; - percentage: number; - }; - notCompliant: { - raw: number; - percentage: number; - }; - notApplicable: { - raw: number; - percentage: number; - }; -} - -interface PageResultDistribution extends ResultDistribution { - name: string; -} - -interface TopicResultDistribution extends ResultDistribution { - name: string; -} - -interface AuditReportContext { - referencial: string; - - auditorName: string; - auditorEmail: string | null; - auditorOrganisation: string; - - technologies: string[]; - - samples: PageSample[]; - - tools: string[]; - - environments: Environment[]; -} - -interface PageSample { - // number: number; - id: number; - order: number; - name: string; - url: string; -} - -interface Environment { - platform: string; - operatingSystem: string; - assistiveTechnology: string; - browser: string; -} diff --git a/confiture-web-app/src/types/types.ts b/confiture-web-app/src/types/types.ts index fe73fb97..5e9ffab8 100644 --- a/confiture-web-app/src/types/types.ts +++ b/confiture-web-app/src/types/types.ts @@ -25,6 +25,8 @@ export enum AuditType { FULL = "FULL" } +export type AuditTypeString = `${AuditType}`; + export enum AuditStatus { NOT_STARTED = "NOT_STARTED", IN_PROGRESS = "IN_PROGRESS", diff --git a/confiture-web-app/src/utils.ts b/confiture-web-app/src/utils.ts index 31c3bdcb..efe2c416 100644 --- a/confiture-web-app/src/utils.ts +++ b/confiture-web-app/src/utils.ts @@ -9,6 +9,7 @@ import { AuditReport, AuditStatus, AuditType, + AuditTypeString, CriterionResultUserImpact, CriteriumResultStatus } from "./types"; @@ -45,7 +46,7 @@ const FORMATTED_USER_IMPACT = { * Format a criterion result user impact type string into French. */ export function formatUserImpact( - userImpact: CriterionResultUserImpact + userImpact: CriterionResultUserImpact | `${CriterionResultUserImpact}` ): string { return FORMATTED_USER_IMPACT[userImpact]; } @@ -60,7 +61,9 @@ const FORMATTED_STATUS = { /** * Format a criterion result status type string into French. */ -export function formatStatus(status: CriteriumResultStatus): string { +export function formatStatus( + status: CriteriumResultStatus | `${CriteriumResultStatus}` +): string { return FORMATTED_STATUS[status]; } @@ -73,7 +76,7 @@ const CRITERIA_COUNT = { /** * Return the number of criteria for a given audit type. */ -export function getCriteriaCount(auditType: AuditType): number { +export function getCriteriaCount(auditType: AuditTypeString): number { return CRITERIA_COUNT[auditType]; } From 0580f8cfce4dc3400d1aac9874a485add4b9566e Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 13 Dec 2024 15:58:00 +0100 Subject: [PATCH 05/10] use more generated types --- confiture-rest-api/prisma/schema.prisma | 7 +++++++ .../src/components/report/ReportErrors.vue | 12 ++++++++---- .../src/components/report/getReportErrors.ts | 6 +++--- .../components/report/getReportImprovements.ts | 2 +- .../src/components/ui/FileUpload.vue | 18 ++++++++++-------- confiture-web-app/src/types/report.ts | 15 ++++++--------- confiture-web-app/src/types/types.ts | 13 +++++-------- 7 files changed, 40 insertions(+), 33 deletions(-) diff --git a/confiture-rest-api/prisma/schema.prisma b/confiture-rest-api/prisma/schema.prisma index 3c1a51b2..bded5da5 100644 --- a/confiture-rest-api/prisma/schema.prisma +++ b/confiture-rest-api/prisma/schema.prisma @@ -102,7 +102,9 @@ model TestEnvironment { assistiveTechnology String browser String + /// @DtoEntityHidden audit Audit? @relation(fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade) + /// @DtoEntityHidden auditUniqueId String? @@unique([platform, operatingSystem, assistiveTechnology, browser, auditUniqueId]) @@ -115,12 +117,15 @@ model AuditedPage { url String // parent audit when the page is a user made page + /// @DtoEntityHidden audit Audit? @relation(name: "UserPages", fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade) auditUniqueId String? // parent audit when the page is a transverse page + /// @DtoEntityHidden auditTransverse Audit? @relation(name: "TransversePage") + /// @DtoEntityHidden results CriterionResult[] } @@ -203,7 +208,9 @@ model AuditFile { key String thumbnailKey String? + /// @DtoEntityHidden audit Audit? @relation(fields: [auditUniqueId], references: [editUniqueId], onDelete: Cascade) + /// @DtoEntityHidden auditUniqueId String? } diff --git a/confiture-web-app/src/components/report/ReportErrors.vue b/confiture-web-app/src/components/report/ReportErrors.vue index 304208e0..9d8e9f1b 100644 --- a/confiture-web-app/src/components/report/ReportErrors.vue +++ b/confiture-web-app/src/components/report/ReportErrors.vue @@ -2,7 +2,11 @@ import { computed, ref } from "vue"; import { useReportStore } from "../../store"; -import { CriterionResultUserImpact, CriteriumResultStatus } from "../../types"; +import { + CriterionResultUserImpact, + CriteriumResultStatus, + ReportUserImpact +} from "../../types"; import { getReportErrors } from "./getReportErrors"; import ReportCriteria from "./ReportCriteria.vue"; import ReportErrorCriterium from "./ReportErrorCriterium.vue"; @@ -17,9 +21,9 @@ const defaultUserImpactFillters = [ null ]; -const userImpactFilters = ref< - Array ->(defaultUserImpactFillters); +const userImpactFilters = ref>( + defaultUserImpactFillters +); const disabledResetFilters = computed( () => diff --git a/confiture-web-app/src/components/report/getReportErrors.ts b/confiture-web-app/src/components/report/getReportErrors.ts index 31cc50f7..3fb6188c 100644 --- a/confiture-web-app/src/components/report/getReportErrors.ts +++ b/confiture-web-app/src/components/report/getReportErrors.ts @@ -4,9 +4,9 @@ import rgaa from "../../criteres.json"; import { ReportStoreState } from "../../store"; import { AuditReport, - CriterionResultUserImpact, CriteriumResultStatus, - ReportCriteriumResult + ReportCriteriumResult, + ReportUserImpact } from "../../types"; export type ReportError = { @@ -24,7 +24,7 @@ export type ReportError = { export function getReportErrors( report: ReportStoreState, quickWinFilter: boolean, - userImpactFilters: Array + userImpactFilters: Array ): ReportError[] { const resultsGroupedByPage = { // include pages with no errors diff --git a/confiture-web-app/src/components/report/getReportImprovements.ts b/confiture-web-app/src/components/report/getReportImprovements.ts index a0c5d8d3..bee5ae7d 100644 --- a/confiture-web-app/src/components/report/getReportImprovements.ts +++ b/confiture-web-app/src/components/report/getReportImprovements.ts @@ -63,7 +63,7 @@ export function getReportImprovements( improvements: sortBy( results.filter(hasImprovement).map(getImprovementObject), "criterium" - ) + ) as ReportImprovement["topics"][0]["improvements"] }; }) ), diff --git a/confiture-web-app/src/components/ui/FileUpload.vue b/confiture-web-app/src/components/ui/FileUpload.vue index 86890819..8e3fe690 100644 --- a/confiture-web-app/src/components/ui/FileUpload.vue +++ b/confiture-web-app/src/components/ui/FileUpload.vue @@ -4,12 +4,14 @@ import { computed, Ref, ref } from "vue"; import { useIsOffline } from "../../composables/useIsOffline"; import { useUniqueId } from "../../composables/useUniqueId"; import { FileErrorMessage } from "../../enums"; -import { AuditFile } from "../../types"; +import { AuditFile, NotesFile } from "../../types"; import { formatBytes, getUploadUrl } from "../../utils"; +type ComponentFile = NotesFile | AuditFile; + export interface Props { acceptedFormats?: Array; - auditFiles: AuditFile[]; + auditFiles: ComponentFile[]; errorMessage?: FileErrorMessage | null; maxFileSize?: string; multiple?: boolean; @@ -84,21 +86,21 @@ function deleteFile(file: AuditFile) { emit("delete-file", file); } -function getFileName(auditFile: AuditFile) { +function getFileName(auditFile: ComponentFile) { return auditFile.originalFilename; } -function getFullFileName(auditFile: AuditFile) { +function getFullFileName(auditFile: ComponentFile) { return getFileName(auditFile) + " (" + getFileDetails(auditFile) + ")"; } -function getFileDetails(auditFile: AuditFile) { +function getFileDetails(auditFile: ComponentFile) { const name = auditFile.originalFilename; const extension = name.substring(name.lastIndexOf(".") + 1).toUpperCase(); return extension + " – " + formatBytes(auditFile.size); } -function isViewable(auditFile: AuditFile) { +function isViewable(auditFile: ComponentFile) { return ( auditFile.mimetype.startsWith("image") || auditFile.mimetype.includes("pdf") ); @@ -158,7 +160,7 @@ function onFileRequestFinished() {
    -
  • +
  • Supprimer {{ getFullFileName(auditFile) }} diff --git a/confiture-web-app/src/types/report.ts b/confiture-web-app/src/types/report.ts index 993997a4..162fc934 100644 --- a/confiture-web-app/src/types/report.ts +++ b/confiture-web-app/src/types/report.ts @@ -1,13 +1,10 @@ -import { CriteriumResult } from "../types"; -import { paths } from "./confiture-api"; +import { components, paths } from "./confiture-api"; export type AuditReport = paths["/reports/{consultUniqueId}"]["get"]["responses"]["200"]["content"]["application/json"]; -export type ReportCriteriumResult = Omit & { - exampleImages: { - key: string; - thumbnailKey: string; - filename: string; - }[]; -}; +export type ReportCriteriumResult = + components["schemas"]["ReportCriterionResult"]; + +export type ReportUserImpact = + components["schemas"]["ReportCriterionResult"]["userImpact"]; diff --git a/confiture-web-app/src/types/types.ts b/confiture-web-app/src/types/types.ts index 5e9ffab8..36b8e462 100644 --- a/confiture-web-app/src/types/types.ts +++ b/confiture-web-app/src/types/types.ts @@ -1,3 +1,5 @@ +import { components } from "./confiture-api"; + export interface AuditRecipent { id: number; name: string; @@ -109,14 +111,9 @@ export enum CriterionResultUserImpact { BLOCKING = "BLOCKING" } -export interface AuditFile { - id: number; - originalFilename: string; - size: number; - key: string; - mimetype: string; - thumbnailKey: string; -} +export type AuditFile = components["schemas"]["AuditFile"]; + +export type NotesFile = components["schemas"]["NotesFile"]; export interface CriteriumResult { // ID From 6bc4deda2974652647e6e4350c90ad4f0df4b971 Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 13 Dec 2024 16:29:20 +0100 Subject: [PATCH 06/10] use generated types for account and audit listing --- .../src/audits/audit.service.ts | 5 ++- .../src/audits/audits.controller.ts | 2 + .../src/audits/dto/audit-listing-item.dto.ts | 16 ++++++++ .../components/account/settings/Profile.vue | 4 +- confiture-web-app/src/store/results.ts | 4 +- confiture-web-app/src/types/account.ts | 37 +++---------------- 6 files changed, 31 insertions(+), 37 deletions(-) create mode 100644 confiture-rest-api/src/audits/dto/audit-listing-item.dto.ts diff --git a/confiture-rest-api/src/audits/audit.service.ts b/confiture-rest-api/src/audits/audit.service.ts index 5088ea34..3d0c51f2 100644 --- a/confiture-rest-api/src/audits/audit.service.ts +++ b/confiture-rest-api/src/audits/audit.service.ts @@ -20,6 +20,7 @@ import { FileStorageService } from "./file-storage.service"; import { UpdateAuditDto } from "./dto/update-audit.dto"; import { UpdateResultsDto } from "./dto/update-results.dto"; import { PatchAuditDto } from "./dto/patch-audit.dto"; +import { AuditListingItemDto } from "./dto/audit-listing-item.dto"; const AUDIT_EDIT_INCLUDE = { recipients: true, @@ -1287,7 +1288,7 @@ export class AuditService { }); } - async getAuditsByAuditorEmail(email: string) { + async getAuditsByAuditorEmail(email: string): Promise { const audits = await this.prisma.audit.findMany({ where: { auditorEmail: email, @@ -1314,7 +1315,7 @@ export class AuditService { } }); - const unorderedAudits = audits.map((a) => { + const unorderedAudits: AuditListingItemDto[] = audits.map((a) => { const allResults = [ ...a.transverseElementsPage.results, ...a.pages.flatMap((p) => p.results) diff --git a/confiture-rest-api/src/audits/audits.controller.ts b/confiture-rest-api/src/audits/audits.controller.ts index e9bcfad3..d47cbff5 100644 --- a/confiture-rest-api/src/audits/audits.controller.ts +++ b/confiture-rest-api/src/audits/audits.controller.ts @@ -39,6 +39,7 @@ import { UploadImageDto } from "./dto/upload-image.dto"; import { AuthRequired } from "src/auth/auth-required.decorator"; import { User } from "src/auth/user.decorator"; import { AuthenticationJwtPayload } from "src/auth/jwt-payloads"; +import { AuditListingItemDto } from "./dto/audit-listing-item.dto"; @Controller("audits") @ApiTags("Audits") @@ -76,6 +77,7 @@ export class AuditsController { */ @Get() @AuthRequired() + @ApiOkResponse({ type: AuditListingItemDto, isArray: true }) async getAuditList(@User() user: AuthenticationJwtPayload) { return this.auditService.getAuditsByAuditorEmail(user.email); } diff --git a/confiture-rest-api/src/audits/dto/audit-listing-item.dto.ts b/confiture-rest-api/src/audits/dto/audit-listing-item.dto.ts new file mode 100644 index 00000000..159bd815 --- /dev/null +++ b/confiture-rest-api/src/audits/dto/audit-listing-item.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { AuditType } from "@prisma/client"; + +export class AuditListingItemDto { + procedureName: string; + editUniqueId: string; + consultUniqueId: string; + creationDate: Date; + @ApiProperty({ enum: AuditType }) + auditType: AuditType; + complianceLevel: number; + @ApiProperty({ enum: ["NOT_STARTED", "COMPLETED", "IN_PROGRESS"] }) + status: "NOT_STARTED" | "COMPLETED" | "IN_PROGRESS"; + estimatedCsvSize: number; + statementIsPublished: boolean; +} diff --git a/confiture-web-app/src/components/account/settings/Profile.vue b/confiture-web-app/src/components/account/settings/Profile.vue index 1376666c..f2bca03c 100644 --- a/confiture-web-app/src/components/account/settings/Profile.vue +++ b/confiture-web-app/src/components/account/settings/Profile.vue @@ -37,8 +37,8 @@ const showActions = computed(() => { function updateProfile() { accountStore .updateProfile({ - name: name.value || null, - orgName: orgName.value || null + name: name.value, + orgName: orgName.value }) .then(() => { notify("success", undefined, "Profil mis à jour avec succès"); diff --git a/confiture-web-app/src/store/results.ts b/confiture-web-app/src/store/results.ts index eff50d44..130e75c6 100644 --- a/confiture-web-app/src/store/results.ts +++ b/confiture-web-app/src/store/results.ts @@ -120,7 +120,7 @@ export const useResultsStore = defineStore("results", { everyCriteriumAreTested(): boolean { const auditStore = useAuditStore(); const transversePageId = - auditStore.currentAudit?.transverseElementsPage.id; + auditStore.currentAudit?.transverseElementsPage!.id; return !( this.allResults @@ -159,7 +159,7 @@ export const useResultsStore = defineStore("results", { const auditStore = useAuditStore(); const transversePageId = - auditStore.currentAudit?.transverseElementsPage.id; + auditStore.currentAudit?.transverseElementsPage!.id; const r = Object.values(this.data) .flatMap(Object.values) diff --git a/confiture-web-app/src/types/account.ts b/confiture-web-app/src/types/account.ts index c4b95c62..52c4b704 100644 --- a/confiture-web-app/src/types/account.ts +++ b/confiture-web-app/src/types/account.ts @@ -1,34 +1,9 @@ -import { AuditStatus, AuditType } from "./types"; +import { components, paths } from "./confiture-api"; -export interface Account { - id: string; - email: string; - name?: string; - orgName?: string; -} +export type UpdateProfileRequestData = + paths["/profile"]["patch"]["requestBody"]["content"]["application/json"]; -export interface UpdateProfileRequestData { - /** John Doe */ - name: string | null; - /** ACME */ - orgName?: string | null; -} +export type AccountDeletionResponse = + paths["/auth/account"]["delete"]["responses"]["200"]["content"]["application/json"]; -export interface AccountDeletionResponse { - feedbackToken: string; -} - -export interface AccountAudit { - procedureName: string; - status: - | AuditStatus.NOT_STARTED - | AuditStatus.IN_PROGRESS - | AuditStatus.COMPLETED; - creationDate: string; - auditType: AuditType; - complianceLevel: number; - editUniqueId: string; - consultUniqueId: string; - estimatedCsvSize: number; - statementIsPublished: boolean; -} +export type AccountAudit = components["schemas"]["AuditListingItemDto"]; From 4b8b5f266e209f9a2e02f013d6440026a789d5e8 Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 13 Dec 2024 16:57:32 +0100 Subject: [PATCH 07/10] hide some props --- confiture-rest-api/prisma/schema.prisma | 2 ++ confiture-rest-api/src/audits/audits.controller.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/confiture-rest-api/prisma/schema.prisma b/confiture-rest-api/prisma/schema.prisma index bded5da5..c21ee1b5 100644 --- a/confiture-rest-api/prisma/schema.prisma +++ b/confiture-rest-api/prisma/schema.prisma @@ -189,7 +189,9 @@ model StoredFile { key String thumbnailKey String + /// @DtoEntityHidden criterionResult CriterionResult? @relation(fields: [criterionResultId], references: [id], onDelete: Cascade, onUpdate: Cascade) + /// @DtoEntityHidden criterionResultId Int? } diff --git a/confiture-rest-api/src/audits/audits.controller.ts b/confiture-rest-api/src/audits/audits.controller.ts index d47cbff5..38b7de12 100644 --- a/confiture-rest-api/src/audits/audits.controller.ts +++ b/confiture-rest-api/src/audits/audits.controller.ts @@ -40,6 +40,7 @@ import { AuthRequired } from "src/auth/auth-required.decorator"; import { User } from "src/auth/user.decorator"; import { AuthenticationJwtPayload } from "src/auth/jwt-payloads"; import { AuditListingItemDto } from "./dto/audit-listing-item.dto"; +import { StoredFile } from "src/generated/nestjs-dto/storedFile.entity"; @Controller("audits") @ApiTags("Audits") @@ -148,6 +149,7 @@ export class AuditsController { @Post("/:uniqueId/results/examples") @UseInterceptors(FileInterceptor("image")) + @ApiCreatedResponse({ type: StoredFile }) async uploadExampleImage( @Param("uniqueId") uniqueId: string, @UploadedFile( From 29f37cd92a5c77017b2fa2e0c0d3d7a1a52cb17c Mon Sep 17 00:00:00 2001 From: Adrien Boutigny Date: Fri, 13 Dec 2024 17:23:25 +0100 Subject: [PATCH 08/10] use generated types in feedback form --- confiture-web-app/src/pages/FeedbackPage.vue | 39 +++++++++++++------- confiture-web-app/src/types/types.ts | 18 --------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/confiture-web-app/src/pages/FeedbackPage.vue b/confiture-web-app/src/pages/FeedbackPage.vue index 81c17864..35ec73ca 100644 --- a/confiture-web-app/src/pages/FeedbackPage.vue +++ b/confiture-web-app/src/pages/FeedbackPage.vue @@ -11,8 +11,12 @@ import PageMeta from "../components/PageMeta"; import DsfrField from "../components/ui/DsfrField.vue"; import { useNotifications } from "../composables/useNotifications"; import { usePreviousRoute } from "../composables/usePreviousRoute"; +import { paths } from "../types/confiture-api"; import { captureWithPayloads } from "../utils"; +export type CreateFeedbackRequestData = + paths["/feedback"]["post"]["requestBody"]["content"]["application/json"]; + const availableRadioAnswers = [ { label: "Oui", slug: "yes", emoji: emojiYes }, { label: "Moyen", slug: "medium", emoji: emojiMedium }, @@ -29,14 +33,14 @@ const availableJobs = [ "Autre" ]; -const easyToUse = ref(""); -const easyToUnderstand = ref(""); +const easyToUse = ref(); +const easyToUnderstand = ref(); const feedback = ref(""); const suggestions = ref(""); const contact = ref(); const name = ref(""); const email = ref(""); -const occupations = ref([]); +const occupations = ref([]); const showSuccess = ref(false); @@ -46,18 +50,23 @@ const notify = useNotifications(); * Submit form and display success notice */ function submitFeedback() { + const body: CreateFeedbackRequestData = { + easyToUse: easyToUse.value!, + easyToUnderstand: easyToUnderstand.value!, + feedback: feedback.value, + suggestions: suggestions.value, + ...(contact.value === "yes" && { + email: email.value, + name: name.value, + occupations: + // FIXME: the @nestjs/swagger CLI plugin generating the API types doesnt seem to pick up on the each option + // see: https://github.com/nestjs/swagger/issues/2027 + occupations.value as unknown as CreateFeedbackRequestData["occupations"] + }) + }; + ky.post("/api/feedback", { - json: { - easyToUse: easyToUse.value, - easyToUnderstand: easyToUnderstand.value, - feedback: feedback.value, - suggestions: suggestions.value, - ...(contact.value === "yes" && { - email: email.value, - name: name.value, - occupations: occupations.value - }) - } + json: body }) .then(() => { showSuccess.value = true; @@ -130,6 +139,7 @@ const previousPageName = type="radio" name="easyToUse" :value="answer.label" + required />