diff --git a/.changeset/khaki-bobcats-join.md b/.changeset/khaki-bobcats-join.md new file mode 100644 index 000000000..964183b1b --- /dev/null +++ b/.changeset/khaki-bobcats-join.md @@ -0,0 +1,5 @@ +--- +"@frameless/overige-objecten-api": major +--- + +Create Overige Objecten API applicatie diff --git a/.eslintignore b/.eslintignore index 5f2540c3a..8d3e985f6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -29,3 +29,4 @@ gql **/yarn-error.log **/.strapi-updater.json **/generated +apps/overige-objecten-api/src/types diff --git a/.eslintrc.js b/.eslintrc.js index 0a6282ee6..6506eec92 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -51,6 +51,8 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: [ + './apps/overige-objecten-api/tsconfig.json', + './apps/overige-objecten-api/tsconfig.test.json', './apps/kennisbank-dashboard/src/admin/tsconfig.json', './apps/kennisbank-dashboard/tsconfig.json', './apps/kennisbank-frontend/tsconfig.json', diff --git a/.gitignore b/.gitignore index ba124820f..6706cd8d3 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,8 @@ dist/ # Sitemap plugin **/**/public/*.xml **/**/public/*.txt + +############################ +# overige-objecten-api +############################ +apps/overige-objecten-api/src/types \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index da6f99f1c..f5dbe9f78 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,3 +19,4 @@ yarn.lock .strapi-updater.json generated gql +apps/overige-objecten-api/src/types diff --git a/Dockerfile.dev b/Dockerfile.dev index 29ef6091e..c59d748f0 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -15,6 +15,7 @@ COPY ./apps/pdc-sc/package.json apps/pdc-sc/package.json COPY ./apps/vth-dashboard/package.json apps/vth-dashboard/package.json COPY ./apps/vth-frontend/package.json apps/vth-frontend/package.json COPY ./apps/kennisbank-dashboard/package.json apps/kennisbank-dashboard/package.json +COPY ./apps/overige-objecten-api/package.json apps/overige-objecten-api/package.json COPY ./apps/kennisbank-frontend/package.json apps/kennisbank-frontend/package.json COPY ./packages/catalogi-data/package.json packages/catalogi-data/package.json COPY ./packages/preview-button/package.json packages/preview-button/package.json @@ -53,7 +54,8 @@ RUN npm run build --workspace @frameless/upl && \ npm run build --workspace @frameless/strapi-plugin-open-forms-embed && \ npm run build --workspace @frameless/strapi-plugin-uuid-field && \ npm run build --workspace @frameless/strapi-plugin-env-label && \ - npm run build --workspace @frameless/strapi-plugin-language + npm run build --workspace @frameless/strapi-plugin-language && \ + npm run build --workspace @frameless/overige-objecten-api # Build target production # ########################### diff --git a/README.md b/README.md index 439cfad23..44f478316 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ To run the application as a Docker container on your Mac, follow these steps: SURVEY_RUN_GUID= SURVEY_RUN_URL= TRANSFER_TOKEN_SALT=anotherRandomLongString== + OVERIGE_OBJECTEN_API_PORT="" # overige-objecten-api + OVERIGE_OBJECTEN_API_CORS="" # overige-objecten-api Example value for more then one domain 'http://localhost:3000, http://localhost:3001' ``` 4. Run the Docker Image: @@ -135,49 +137,51 @@ To run the application as a Docker container on your Mac, follow these steps: -| Variable name | Description | Type | Default Value | Application | Note | -| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `ADMIN_JWT_SECRET` | Secret for signing JWTs for the Admin panel | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `APP_KEYS` | The secret key for session cookie signing | `String` | | strapi-dashboard | [Server configuration](https://docs.strapi.io/dev-docs/configurations/server#available-options) | -| `API_TOKEN_SALT` | Salt for generating API tokens | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `CSP_CONNECT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `connect-src` | | | | | -| `CSP_FONT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `font-src` | | | | | -| `CSP_FORM_ACTION_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `form-action` | | | | | -| `CSP_FRAME_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `frame-src` | | | | | -| `CSP_IMG_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `img-src` | | | | | -| `CSP_SCRIPT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `script-src` | | | | | -| `CSP_STYLE_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `style-src` | | | | | -| `CSP_WORKER_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `worker-src` | | | | | -| `DATABASE_CLIENT` | Database client to use | `String` | sqlite | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_HOST` | Database host | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_NAME` | Database name | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_PASSWORD` | Database password | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_PORT` | Database port | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_SSL` | For SSL database connection. Use an object to pass certificate files as strings. | `Boolean` or `Object` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_USERNAME` | Database username | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `FRONTEND_PUBLIC_URL` | | `String` | | strapi-dashboard | | -| `HOST` | | `Number` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `I18N_DEBUG` | When `I18N_DEBUG` is set, the `i18next` package will log debug information. | `String` | | frontend | | -| `JWT_SECRET` | Secret for signing JWTs for the Users-Permissions plugin | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `MATOMO_HOST` | PDC ONLY / Optional: URL for Matomo | `URL` | | FRONTEND | | -| `MATOMO_SITE_ID` | PDC ONLY / Optional: container ID for Matomo | `String` | | FRONTEND | | -| `NODE_ENV` | | `production` \| `development` | | | | -| `OPEN_FORMS_API_TOKEN` | PDC ONLY: A Token for Open Forms API | `String` | | strapi-dashboard / frontend | | -| `OPEN_FORMS_API_URL` | PDC ONLY: URL for Open Forms API (usually the origin + /api/v2) | `String` | | frontend / strapi-dashboard | | -| `OPEN_FORMS_CSS_URL` | PDC ONLY / Optional: URL for Open Forms CSS | `String` | | frontend | | -| `OPEN_FORMS_SDK_URL` | PDC ONLY / Optional : URL for Open Forms SDK | `String` | | frontend | | -| `PANDOSEARCH_API_URL` | PDC ONLY | `String` | | frontend | | -| `PGADMIN_DEFAULT_EMAIL` | | `String` | | Database | | -| `PGADMIN_DEFAULT_PASSWORD` | | `String` | | Database | | -| `PORT` | Port on which the server should be running. | `Number` | 1337 | strapi-dashboard | | -| `PREVIEW_SECRET_TOKEN` | The secret used for the Strapi preview plugin should have the same value for both the frontend and the Strapi dashboard. | `String` | | strapi-dashboard frontend | | -| `STRAPI_PRIVATE_URL` | | `String` | | frontend | | -| `STRAPI_PUBLIC_URL` | | `URL` | | Frontend | The Strapi dashboard URL, e.g.,`http://localhost:1337/` | -| `TRANSFER_TOKEN_SALT` | Salt for generating Transfer tokens. If no transfer token salt is defined, transfer features will be disabled. | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) Secrets can be generated manually by running `node -p "require('crypto').randomBytes(48).toString('base64');"` | -| `OGONE_PAYMENT_SERVICE_URL` | PDC ONLY: URL for Open Forms/Payment | `URL` | | Frontend | | -| `SURVEY_RUN_GUID` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | -| `SURVEY_RUN_APIKEY` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | -| `SURVEY_RUN_URL` | PDC ONLY: Used for the Survey service | `URL` | | Fronend | | +| Variable name | Description | Type | Default Value | Application | Note | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `ADMIN_JWT_SECRET` | Secret for signing JWTs for the Admin panel | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `APP_KEYS` | The secret key for session cookie signing | `String` | | strapi-dashboard | [Server configuration](https://docs.strapi.io/dev-docs/configurations/server#available-options) | +| `API_TOKEN_SALT` | Salt for generating API tokens | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `CSP_CONNECT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `connect-src` | | | | | +| `CSP_FONT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `font-src` | | | | | +| `CSP_FORM_ACTION_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `form-action` | | | | | +| `CSP_FRAME_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `frame-src` | | | | | +| `CSP_IMG_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `img-src` | | | | | +| `CSP_SCRIPT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `script-src` | | | | | +| `CSP_STYLE_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `style-src` | | | | | +| `CSP_WORKER_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `worker-src` | | | | | +| `DATABASE_CLIENT` | Database client to use | `String` | sqlite | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_HOST` | Database host | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_NAME` | Database name | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_PASSWORD` | Database password | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_PORT` | Database port | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_SSL` | For SSL database connection. Use an object to pass certificate files as strings. | `Boolean` or `Object` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_USERNAME` | Database username | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `FRONTEND_PUBLIC_URL` | | `String` | | strapi-dashboard | | +| `HOST` | | `Number` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `I18N_DEBUG` | When `I18N_DEBUG` is set, the `i18next` package will log debug information. | `String` | | frontend | | +| `JWT_SECRET` | Secret for signing JWTs for the Users-Permissions plugin | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `MATOMO_HOST` | PDC ONLY / Optional: URL for Matomo | `URL` | | FRONTEND | | +| `MATOMO_SITE_ID` | PDC ONLY / Optional: container ID for Matomo | `String` | | FRONTEND | | +| `NODE_ENV` | | `production` \| `development` | | | | +| `OPEN_FORMS_API_TOKEN` | PDC ONLY: A Token for Open Forms API | `String` | | strapi-dashboard / frontend | | +| `OPEN_FORMS_API_URL` | PDC ONLY: URL for Open Forms API (usually the origin + /api/v2) | `String` | | frontend / strapi-dashboard | | +| `OPEN_FORMS_CSS_URL` | PDC ONLY / Optional: URL for Open Forms CSS | `String` | | frontend | | +| `OPEN_FORMS_SDK_URL` | PDC ONLY / Optional : URL for Open Forms SDK | `String` | | frontend | | +| `PANDOSEARCH_API_URL` | PDC ONLY | `String` | | frontend | | +| `PGADMIN_DEFAULT_EMAIL` | | `String` | | Database | | +| `PGADMIN_DEFAULT_PASSWORD` | | `String` | | Database | | +| `PORT` | Port on which the server should be running. | `Number` | 1337 | strapi-dashboard | | +| `PREVIEW_SECRET_TOKEN` | The secret used for the Strapi preview plugin should have the same value for both the frontend and the Strapi dashboard. | `String` | | strapi-dashboard frontend | | +| `STRAPI_PRIVATE_URL` | | `String` | | frontend | | +| `STRAPI_PUBLIC_URL` | | `URL` | | Frontend | The Strapi dashboard URL, e.g.,`http://localhost:1337/` | +| `TRANSFER_TOKEN_SALT` | Salt for generating Transfer tokens. If no transfer token salt is defined, transfer features will be disabled. | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) Secrets can be generated manually by running `node -p "require('crypto').randomBytes(48).toString('base64');"` | +| `OGONE_PAYMENT_SERVICE_URL` | PDC ONLY: URL for Open Forms/Payment | `URL` | | Frontend | | +| `SURVEY_RUN_GUID` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | +| `SURVEY_RUN_APIKEY` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | +| `SURVEY_RUN_URL` | PDC ONLY: Used for the Survey service | `URL` | | Fronend | | +| OVERIGE_OBJECTEN_API_PORT | | Number | | Overige-objecten-api | | +| OVERIGE_OBJECTEN_API_CORS | This environment variable manages CORS (Cross-Origin Resource Sharing), allowing the server to specify permitted external origins for resource loading. For multiple domains, separate them with a comma (e.g., '[http://localhost:3000](http://localhost:3000), [http://localhost:3001](http://localhost:3001)'). | URL | | Overige-objecten-api | | ## Start the server without Docker diff --git a/apps/overige-objecten-api/README.md b/apps/overige-objecten-api/README.md new file mode 100644 index 000000000..4f38fd85d --- /dev/null +++ b/apps/overige-objecten-api/README.md @@ -0,0 +1,80 @@ +# overige-objecten-api + +The **overige-objecten-api** is a microservice built with Node.js that maps certain PDC product fields to the **kennisartikel** and **VAC** fields. + +## Features + +- OpenAPI documentation. +- Request validation against **kennisartikel** and **VAC** schemas, ensuring that each field has the correct type. +- Automatic generation of TypeScript interfaces for **kennisartikel** and **VAC** schemas. +- Provides clear and descriptive error messages in JSON format when validation fails. + +## Getting Started + +You can run the server locally in two ways: + +> Note: Before proceeding, ensure you generate an API token from the Strapi dashboard by following these steps: + +1. Go to the PDC Strapi dashboard. +2. Navigate to Settings > API Tokens and select Create New API Token. +3. Allow access only to the product API by checking the boxes for find and findOne. +4. Once you've configured the permissions, click Save. + +After generating the token, include it in your request headers as a Bearer token. Here's an example: + +```ts +Authorization: Bearer API_TOKEN +``` + +### 1. Start with Docker + +Ensure that you have the following environment variables in the `.pdc.prod.env` file before starting: + +```shell +OVERIGE_OBJECTEN_API_PORT=4001 +OVERIGE_OBJECTEN_API_CORS=http://localhost:3000 # If using multiple domains, separate them with a comma (e.g., 'http://localhost:3000, http://localhost:3001'). +``` + +To start the service, open a terminal in the project root and run: + +```shell +cd bin && bash ./deploy.sh pdc-dashboard prod up --build +``` + +### 2. Start without Docker + +Create an .env file in the apps/overige-objecten-api directory with the following environment variables: + +```shell +STRAPI_PRIVATE_URL=http://127.0.0.1:1337/ +OVERIGE_OBJECTEN_API_PORT=4001 +OVERIGE_OBJECTEN_API_CORS='' # Required for client-side application +``` + +Then, follow these steps: + +1. Build the project: + + ```shell + yarn build + + ``` + +2. Start the PDC-dashboard server: + + ```shell + yarn workspace @frameless/pdc-dashboard dev + + ``` + +3. Start the overige-objecten-api server: + + ```shell + yarn workspace @frameless/overige-objecten-api dev + + ``` + +## API Endpoints + +- Kennisartikel API: [http://localhost:4001/api/v1/objects](http://localhost:4001/api/v1/objects) +- OpenAPI specification: [http://localhost:4001/api/v1/openapi.json](http://localhost:4001/api/v1/openapi.json) diff --git a/apps/overige-objecten-api/jest.config.ts b/apps/overige-objecten-api/jest.config.ts new file mode 100644 index 000000000..890bde934 --- /dev/null +++ b/apps/overige-objecten-api/jest.config.ts @@ -0,0 +1,21 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + // to obtain access to the matchers. + moduleFileExtensions: ['ts', 'js', 'json', 'node'], + setupFilesAfterEnv: ['/src/tests/jest.setup.ts'], + modulePaths: [''], + testEnvironment: 'node', + roots: ['/src'], + transform: { + '^.+\\.(ts)$': [ + 'ts-jest', + { + tsconfig: 'tsconfig.test.json', + }, + ], + }, +}; + +export default config; diff --git a/apps/overige-objecten-api/package.json b/apps/overige-objecten-api/package.json new file mode 100644 index 000000000..63ea3ef53 --- /dev/null +++ b/apps/overige-objecten-api/package.json @@ -0,0 +1,51 @@ +{ + "name": "@frameless/overige-objecten-api", + "version": "0.0.0", + "private": true, + "author": "@frameless", + "description": "Overige Objecten API for PDC", + "license": "EUPL-1.2", + "keywords": [], + "scripts": { + "prebuild": "yarn clean && yarn generate-types", + "build": "npm-run-all --parallel build:*", + "build:server": "tsc -p ./tsconfig.json", + "build:openapi": "mkdir -p dist/src/docs && cp -R src/docs/ dist/src/docs/", + "watch": "tsc -p ./tsconfig.json -w", + "start": "NODE_ENV=production node ./dist/src/server.js", + "dev": "NODE_ENV=development nodemon src/server.ts", + "clean": "rimraf dist src/types", + "generate-types": "openapi-typescript src/docs/openapi.yaml --output src/types/openapi.ts", + "test": "OVERIGE_OBJECTEN_API_PORT=3000 jest --coverage --forceExit --verbose", + "test:watch": "OVERIGE_OBJECTEN_API_PORT=3000 jest --watch" + }, + "dependencies": { + "cors": "2.8.5", + "dotenv": "16.4.5", + "express": "4.21.0", + "express-openapi-validator": "5.3.7", + "js-yaml": "4.1.0", + "swagger-ui-express": "5.0.1" + }, + "devDependencies": { + "@types/cors": "2.8.17", + "@types/jest": "29.5.12", + "@types/supertest": "6.0.2", + "@types/swagger-ui-express": "4.1.6", + "@types/yamljs": "0.2.34", + "jest": "29.7.0", + "nodemon": "3.1.7", + "openapi-typescript": "7.4.1", + "rimraf": "6.0.1", + "supertest": "7.0.0", + "ts-jest": "29.2.3", + "ts-node": "10.9.2", + "typescript": "5.0.4", + "jest-fetch-mock": "3.0.3" + }, + "repository": { + "type": "git+ssh", + "url": "git@github.com:frameless/strapi.git", + "directory": "apps/overige-objecten-api" + } +} diff --git a/apps/overige-objecten-api/src/__mocks__/getStrapiKennisartikelData.ts b/apps/overige-objecten-api/src/__mocks__/getStrapiKennisartikelData.ts new file mode 100644 index 000000000..cbb1cc5cf --- /dev/null +++ b/apps/overige-objecten-api/src/__mocks__/getStrapiKennisartikelData.ts @@ -0,0 +1,90 @@ +export const getStrapiKennisartikelData = () => { + return { + data: { + products: { + meta: { + pagination: { + total: 1, + page: 1, + pageSize: 10, + pageCount: 1, + }, + }, + data: [ + { + id: '50', + attributes: { + title: 'Aansprakelijk stellen gemeente, schade melden', + slug: 'aansprakelijk-stellen-gemeente-schade-melden', + uuid: 'a9058a3e-6dd9-480c-a074-e38026bd4ffd', + locale: 'nl', + updatedAt: '2024-10-14T09:22:27.349Z', + createdAt: '2024-04-02T09:23:53.092Z', + metaTags: { + keymatch: 'schade, aansprakelijkheid', + title: 'Schade melden gemeente', + description: 'Hebt u schade door ons? Dan kunt u hier vergoeding voor vragen. ', + }, + sections: [ + { + id: '36', + content: + '

Voorwaarden

  • U kunt laten zien dat de gemeente verantwoordelijk is voor uw schade.

  • U hebt geen rechtsbijstandsverzekering en u bent niet verzekerd voor de schade. Denk bijvoorbeeld aan een autoverzekering, zorgverzekering, particuliere aansprakelijkheidsverzekering en telefoonverzekering. Bent u wel verzekerd? Ga dan naar uw verzekeringsmaatschappij.

', + kennisartikelCategorie: 'voorwaarden', + component: 'ComponentComponentsUtrechtRichText', + }, + { + id: '37', + content: + '

Wat stuurt u mee?

Stuur alle gevraagde bewijsstukken mee. Zo kunnen we uw aanvraag sneller en beter beoordelen.

  • Foto’s van het voorwerp of de situatie die de schade veroorzaakte

  • Foto’s van de plek waar de schade ontstond

  • Foto’s van de schade

  • Ondertekende getuigenverklaringen (pdf, 240 kB) (alleen nodig als er getuigen waren)

  • Herstelofferte of reparatienota

  • Veroorzaakte een auto of een ander voertuig van de gemeente de schade? Dan is een compleet ingevuld Europees schadeformulier (aanrijdingsformulier) verplicht.

Verder kunt u denken aan:

  • een rapport van een schade-expert

  • een proces-verbaal van de politie

', + kennisartikelCategorie: 'aanvraag', + component: 'ComponentComponentsUtrechtRichText', + }, + {}, + { + id: '38', + content: '

Hulp nodig bij het invullen? Bel ons dan via telefoonnummer 14 030.

', + kennisartikelCategorie: 'contact', + component: 'ComponentComponentsUtrechtRichText', + }, + {}, + { + id: '39', + content: + '

Na uw melding

Binnen 4 weken krijgt u een bericht. U hoort dan of wij vinden dat de gemeente aansprakelijk is voor de opgelopen schade. Of u hoort of wij hier meer tijd voor nodig hebben.

', + kennisartikelCategorie: null, + component: 'ComponentComponentsUtrechtRichText', + }, + { + id: '40', + content: + '

Meer informatie

', + kennisartikelCategorie: null, + component: 'ComponentComponentsUtrechtRichText', + }, + ], + kennisartikelMetadata: { + uuid: '3c1d6811-a43f-4d92-8c48-19df0a491852', + doelgroep: 'eu_burger', + productAanwezig: true, + productValtOnder: null, + afdelingen: [ + { + afdelingId: 'c38039ec-3079-47d1-a13c-c16fe8277b62', + afdelingnaam: 'Demo afdeling', + }, + ], + verantwoordelijkeOrganisatie: { + owmsIdentifier: 'http://standaarden.overheid.nl/owms/terms/Utrecht_(gemeente)', + owmsPrefLabel: 'Gemeente Utrecht', + owmsEndDate: '2024-10-13T22:00:00.000Z', + }, + upnUri: 'http://standaarden.overheid.nl/owms/terms/UPL-naam_nog_niet_beschikbaar', + }, + }, + }, + ], + }, + }, + }; +}; diff --git a/apps/overige-objecten-api/src/__mocks__/index.ts b/apps/overige-objecten-api/src/__mocks__/index.ts new file mode 100644 index 000000000..8bdde9000 --- /dev/null +++ b/apps/overige-objecten-api/src/__mocks__/index.ts @@ -0,0 +1,4 @@ +export { getStrapiKennisartikelData } from './getStrapiKennisartikelData'; +export { objectsResponseData } from './objectsResponseData'; +export { kennisartikelObject } from './kennisartikelObject'; +export { vacObject } from './vacObject'; diff --git a/apps/overige-objecten-api/src/__mocks__/kennisartikelObject.ts b/apps/overige-objecten-api/src/__mocks__/kennisartikelObject.ts new file mode 100644 index 000000000..4792979b4 --- /dev/null +++ b/apps/overige-objecten-api/src/__mocks__/kennisartikelObject.ts @@ -0,0 +1,40 @@ +export const kennisartikelObject = () => ({ + url: 'http://localhost:3000/api/v1/objecttypes/kennisartikel', + uuid: 'a9058a3e-6dd9-480c-a074-e38026bd4ffd', + upnUri: 'http://standaarden.overheid.nl/owms/terms/UPL-naam_nog_niet_beschikbaar', + publicatieDatum: '2024-04-02', + productAanwezig: true, + productValtOnder: null, + verantwoordelijkeOrganisatie: { + url: 'http://localhost:3000/api/v1/objecttypes/kennisartikel#verantwoordelijkeOrganisatie', + owmsIdentifier: 'http://standaarden.overheid.nl/owms/terms/Utrecht_(gemeente)', + owmsPrefLabel: 'Gemeente Utrecht', + owmsEndDate: '2024-10-13T22:00:00.000Z', + }, + locaties: null, + doelgroep: 'eu-burger', + afdelingen: [ + { + afdelingId: 'c38039ec-3079-47d1-a13c-c16fe8277b62', + afdelingnaam: 'Demo afdeling', + }, + ], + beschikbareTalen: ['nl'], + vertalingen: [ + { + datumWijziging: '2024-10-14T09:22:27.349Z', + taal: 'nl', + titel: 'Aansprakelijk stellen gemeente, schade melden', + trefwoorden: [ + { + trefwoord: 'schade', + }, + { + trefwoord: 'aansprakelijkheid', + }, + ], + vereisten: + '

Voorwaarden

  • U kunt laten zien dat de gemeente verantwoordelijk is voor uw schade.

  • U hebt geen rechtsbijstandsverzekering en u bent niet verzekerd voor de schade. Denk bijvoorbeeld aan een autoverzekering, zorgverzekering, particuliere aansprakelijkheidsverzekering en telefoonverzekering. Bent u wel verzekerd? Ga dan naar uw verzekeringsmaatschappij.

', + }, + ], +}); diff --git a/apps/overige-objecten-api/src/__mocks__/objectsResponseData.ts b/apps/overige-objecten-api/src/__mocks__/objectsResponseData.ts new file mode 100644 index 000000000..ac3e0b0bd --- /dev/null +++ b/apps/overige-objecten-api/src/__mocks__/objectsResponseData.ts @@ -0,0 +1,73 @@ +export const objectsResponseData = ({ type }: { type?: 'kennisartikel' | 'vac' }) => { + const data = { + results: [ + { + url: 'http://localhost:3000/api/v1/objecttypes/kennisartikel', + uuid: 'a9058a3e-6dd9-480c-a074-e38026bd4ffd', + upnUri: 'http://standaarden.overheid.nl/owms/terms/UPL-naam_nog_niet_beschikbaar', + publicatieDatum: '2024-04-02', + productAanwezig: true, + productValtOnder: null, + verantwoordelijkeOrganisatie: { + url: 'http://localhost:3000/api/v1/objecttypes/kennisartikel#verantwoordelijkeOrganisatie', + owmsIdentifier: 'http://standaarden.overheid.nl/owms/terms/Utrecht_(gemeente)', + owmsPrefLabel: 'Gemeente Utrecht', + owmsEndDate: '2024-10-13T22:00:00.000Z', + }, + locaties: null, + doelgroep: 'eu-burger', + afdelingen: [ + { + afdelingId: 'c38039ec-3079-47d1-a13c-c16fe8277b62', + afdelingnaam: 'Demo afdeling', + }, + ], + beschikbareTalen: ['nl'], + vertalingen: [ + { + datumWijziging: '2024-10-14T09:22:27.349Z', + taal: 'nl', + titel: 'Aansprakelijk stellen gemeente, schade melden', + trefwoorden: [{ trefwoord: 'schade' }, { trefwoord: 'aansprakelijkheid' }], + vereisten: + '

Voorwaarden

  • U kunt laten zien dat de gemeente verantwoordelijk is voor uw schade.

  • U hebt geen rechtsbijstandsverzekering en u bent niet verzekerd voor de schade. Denk bijvoorbeeld aan een autoverzekering, zorgverzekering, particuliere aansprakelijkheidsverzekering en telefoonverzekering. Bent u wel verzekerd? Ga dan naar uw verzekeringsmaatschappij.

', + }, + ], + }, + { + url: 'http://localhost:3000/api/v1/objecttypes/vac', + uuid: 'a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6', + vraag: 'Wat is het proces om een paspoort aan te vragen?', + status: 'actief', + antwoord: + 'U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren.', + doelgroep: 'eu-burger', + }, + { + url: 'http://localhost:3000/api/v1/objecttypes/vac', + uuid: 'b2c3d4e5-f6g7-8h9i-0j1k-l2m3n4o5p6q7', + vraag: 'Hoe kan ik een rijbewijs aanvragen?', + status: 'actief', + antwoord: + 'Voor het aanvragen van een rijbewijs moet u een aanvraagformulier invullen en uw identiteitsbewijs meenemen naar het gemeentehuis.', + doelgroep: 'eu-burger', + }, + { + url: 'http://localhost:3000/api/v1/objecttypes/vac', + uuid: 'c3d4e5f6-g7h8-9i0j-k1l2-m3n4o5p6q7r8', + vraag: 'Wat moet ik doen bij verhuizing?', + status: 'actief', + antwoord: 'Bij verhuizing moet u zich binnen 5 dagen inschrijven op uw nieuwe adres bij de gemeente.', + doelgroep: 'eu-burger', + }, + ], + }; + switch (type) { + case 'kennisartikel': + return data.results.filter(({ url }) => url.endsWith('kennisartikel')); + case 'vac': + return data.results.filter(({ url }) => url.endsWith('vac')); + default: + return data; + } +}; diff --git a/apps/overige-objecten-api/src/__mocks__/vacObject.ts b/apps/overige-objecten-api/src/__mocks__/vacObject.ts new file mode 100644 index 000000000..b16b427b7 --- /dev/null +++ b/apps/overige-objecten-api/src/__mocks__/vacObject.ts @@ -0,0 +1,9 @@ +export const vacObject = () => ({ + url: 'http://localhost:3000/api/v1/objecttypes/vac', + uuid: 'a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6', + vraag: 'Wat is het proces om een paspoort aan te vragen?', + status: 'actief', + antwoord: + 'U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren.', + doelgroep: 'eu-burger', +}); diff --git a/apps/overige-objecten-api/src/controllers/index.ts b/apps/overige-objecten-api/src/controllers/index.ts new file mode 100644 index 000000000..d4f232ab2 --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/index.ts @@ -0,0 +1,3 @@ +export { getAllObjectsController, getObjectByUUIDController } from './objects'; +export { openAPIController } from './openapi'; +export { objecttypesController } from './objecttypes'; diff --git a/apps/overige-objecten-api/src/controllers/objects/index.test.ts b/apps/overige-objecten-api/src/controllers/objects/index.test.ts new file mode 100644 index 000000000..27383f179 --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/objects/index.test.ts @@ -0,0 +1,416 @@ +import fetchMock from 'jest-fetch-mock'; +import request from 'supertest'; +import { getStrapiKennisartikelData, kennisartikelObject, objectsResponseData, vacObject } from '../../__mocks__'; +import app from '../../server'; + +jest.mock('../../utils/getTheServerURL.ts', () => ({ + getTheServerURL: () => 'http://localhost:3000', +})); + +jest.mock('../../utils/getPaginatedResponse.ts'); + +fetchMock.enableMocks(); +describe('Objects controller', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + beforeEach(() => { + fetchMock.resetMocks(); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('GET /api/objects', () => { + it('should return kennisartikel & VAC by default', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(objectsResponseData({})); + }); + describe('pagination', () => { + it('should response the whole data by default', async () => { + const spy = jest + .spyOn(require('../../utils/getPaginatedResponse'), 'getPaginatedResponse') + .mockImplementation(() => + Promise.resolve({ + page: 1, + pageSize: 10, + count: 1, + total: 100, + next: null, + previous: null, + }), + ); + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual({ + ...objectsResponseData({}), + page: 1, + pageSize: 10, + count: 1, + total: 100, + next: null, + previous: null, + }); + }); + it('should response the second page when page=2&pageSize=10', async () => { + const spy = jest + .spyOn(require('../../utils/getPaginatedResponse'), 'getPaginatedResponse') + .mockImplementation(() => + Promise.resolve({ + page: 2, + pageSize: 10, + count: 1, + total: 100, + next: 'http://localhost:4001/api/v1/objects?page=2&pageSize=10', + previous: null, + }), + ); + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects?page=2&pageSize=10') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual({ + ...objectsResponseData({}), + page: 2, + pageSize: 10, + count: 1, + total: 100, + next: 'http://localhost:4001/api/v1/objects?page=2&pageSize=10', + previous: null, + }); + spy.mockRestore(); + }); + }); + it('should return kennisartikel objects when type is kennisartikel', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get(`/api/v1/objects?type=${encodeURIComponent('http://localhost:4001/api/v1/objecttypes/kennisartikel')}`) + .set('Authorization', 'Token YOUR_API_TOKEN'); + + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(objectsResponseData({ type: 'kennisartikel' })); + }); + it('should return kennisartikel objects when type is vac', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get(`/api/v1/objects?type=${encodeURIComponent('http://localhost:4001/api/v1/objecttypes/vac')}`) + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(objectsResponseData({ type: 'vac' })); + }); + it('should return 400 when type is not an encoded URL', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects?type=http://localhost:4001/api/v1/objecttypes/vac') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(400); + expect(response.ok).toBe(false); + }); + it('should return 400 when type is empty', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app).get('/api/v1/objects?type=').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(400); + expect(response.ok).toBe(false); + }); + it('should return 400 when type is not a valid URL', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects?type=invalid') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(400); + expect(response.ok).toBe(false); + }); + it('should return 200 and empty array when no data is returned', async () => { + const spy = jest.spyOn(require('../../utils/vacData.ts'), 'vacData').mockImplementation(() => []); + fetchMock.mockResponseOnce(JSON.stringify([{}])); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual({ results: [] }); + spy.mockRestore(); + }); + it('should return 500 when fetch fails', async () => { + fetchMock.mockRejectOnce(new Error('Fetch failed')); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(500); + expect(response.ok).toBe(false); + }); + it('should return 500 when fetch fails with error message', async () => { + fetchMock.mockRejectOnce(new Error('Fetch failed')); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(500); + expect(response.ok).toBe(false); + expect(response.text).toBe(JSON.stringify({ message: 'Fetch failed' })); + }); + it('should return 401 when authorization header is missing', async () => { + fetchMock.mockResponseOnce(JSON.stringify([{}])); + + const response = await request(app).get('/api/v1/objects'); + expect(response.status).toBe(401); + expect(response.text).toBe( + JSON.stringify([{ path: '/api/v1/objects', message: "'Authorization' header required" }]), + ); + expect(response.ok).toBe(false); + }); + it('should return 500 when fetch fails with error message', async () => { + fetchMock.mockRejectOnce(new Error('Fetch failed')); + const response = await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(500); + expect(response.ok).toBe(false); + expect(response.text).toBe(JSON.stringify({ message: 'Fetch failed' })); + }); + describe('validate kennisartikel fields with express-openapi-validator', () => { + it('should return 500 and error message when of the required fields are missing', async () => { + const data = getStrapiKennisartikelData().data.products.data.map((data) => { + return { + ...data, + attributes: { + ...data.attributes, + title: null, + }, + }; + }); + fetchMock.mockResponseOnce(JSON.stringify({ data: { products: { data } } })); + const consoleSpy = jest.spyOn(console, 'log'); + await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(consoleSpy).toHaveBeenCalledWith('Response body fails validation: ', [ + { errorCode: 'type.openapi.validation', message: 'must be array', path: '/response' }, + { errorCode: 'type.openapi.validation', message: 'must be array', path: '/response' }, + { errorCode: 'type.openapi.validation', message: 'must be array', path: '/response' }, + { + errorCode: 'type.openapi.validation', + message: 'must be string', + path: '/response/results/0/vertalingen/0/titel', + }, + { + errorCode: 'required.openapi.validation', + message: "must have required property 'vraag'", + path: '/response/results/0/vraag', + }, + { + errorCode: 'oneOf.openapi.validation', + message: 'must match exactly one schema in oneOf', + path: '/response/results/0', + }, + { + errorCode: 'oneOf.openapi.validation', + message: 'must match exactly one schema in oneOf', + path: '/response', + }, + ]); + consoleSpy.mockRestore(); + }); + }); + describe('validate VAC fields with express-openapi-validator', () => { + it('should return 500 and error message when of the required fields are missing', async () => { + const consoleSpy = jest.spyOn(console, 'log'); + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const spy = jest.spyOn(require('../../utils/vacData.ts'), 'vacData').mockImplementation(() => [ + { + url: 'http://localhost:4001/api/v1/objecttypes/vac', + uuid: 'a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6', + vraag: 'Wat is het proces om een paspoort aan te vragen?', + antwoord: + 'U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren.', + doelgroep: 'eu-burger', + }, + ]); + await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(consoleSpy).toHaveBeenCalledWith('Response body fails validation: ', [ + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { + path: '/response/results/1/upnUri', + message: "must have required property 'upnUri'", + errorCode: 'required.openapi.validation', + }, + { + path: '/response/results/1/status', + message: "must have required property 'status'", + errorCode: 'required.openapi.validation', + }, + { + path: '/response/results/1', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + { + path: '/response', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + ]); + spy.mockRestore(); + consoleSpy.mockRestore(); + }); + }); + }); + describe('GET /api/objects/:id', () => { + it('should return 200 and kennisartikel object when uuid is valid', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects/a9058a3e-6dd9-480c-a074-e38026bd4ffd') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(kennisartikelObject()); + }); + it('should return 200 and vac object when uuid is valid', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects/a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(vacObject()); + }); + it('should return 200 and Kennisartikel object when uuid is valid', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects/a9058a3e-6dd9-480c-a074-e38026bd4ffd') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(200); + expect(response.ok).toBe(true); + expect(response.body).toStrictEqual(kennisartikelObject()); + }); + it('should return 404 when id is not found', async () => { + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const response = await request(app) + .get('/api/v1/objects/not-exist-uuid') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(404); + expect(response.ok).toBe(false); + expect(response.body).toStrictEqual({ message: 'Object not found' }); + }); + it('should return 500 when fetch fails with error message', async () => { + fetchMock.mockRejectOnce(new Error('Fetch failed')); + const response = await request(app) + .get('/api/v1/objects/a9058a3e-6dd9-480c-a074-e38026bd4ffd') + .set('Authorization', 'Token YOUR_API_TOKEN'); + expect(response.status).toBe(500); + expect(response.ok).toBe(false); + expect(response.text).toBe(JSON.stringify({ message: 'Fetch failed' })); + }); + it('should return 404 when no data is returned', async () => { + fetchMock.mockResponseOnce(JSON.stringify([])); + const response = await request(app) + .get('/api/v1/objects/a9058a3e-6dd9-480c-a074-e38026bd4ffd') + .set('Authorization', 'Token YOUR_API_TOKEN'); + + expect(response.status).toBe(404); + expect(response.ok).toBe(false); + expect(response.body).toStrictEqual({ message: 'Object not found' }); + }); + describe('validate kennisartikel fields with express-openapi-validator', () => { + it('should return 500 and error message when of the required fields are missing', async () => { + const consoleSpy = jest.spyOn(console, 'log'); + const data = getStrapiKennisartikelData().data.products.data.map((data) => { + return { + ...data, + attributes: { + ...data.attributes, + title: null, + }, + }; + }); + fetchMock.mockResponseOnce(JSON.stringify({ data: { products: { data } } })); + await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + expect(consoleSpy).toHaveBeenCalledWith('Response body fails validation: ', [ + { + path: '/response', + message: 'must be array', + errorCode: 'type.openapi.validation', + }, + { + path: '/response', + message: 'must be array', + errorCode: 'type.openapi.validation', + }, + { + path: '/response', + message: 'must be array', + errorCode: 'type.openapi.validation', + }, + { + path: '/response/results/0/vertalingen/0/titel', + message: 'must be string', + errorCode: 'type.openapi.validation', + }, + { + path: '/response/results/0/vraag', + message: "must have required property 'vraag'", + errorCode: 'required.openapi.validation', + }, + { + path: '/response/results/0', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + { + path: '/response', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + ]); + consoleSpy.mockRestore(); + }); + }); + describe('validate VAC fields with express-openapi-validator', () => { + it('should return 500 and error message when of the required fields are missing', async () => { + const consoleSpy = jest.spyOn(console, 'log'); + fetchMock.mockResponseOnce(JSON.stringify(getStrapiKennisartikelData())); + const spy = jest.spyOn(require('../../utils/vacData.ts'), 'vacData').mockImplementation(() => [ + { + url: 'http://localhost:4001/api/v1/objecttypes/vac', + uuid: 'a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6', + vraag: 'Wat is het proces om een paspoort aan te vragen?', + antwoord: + 'U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren.', + doelgroep: 'eu-burger', + }, + ]); + await request(app).get('/api/v1/objects').set('Authorization', 'Token YOUR_API_TOKEN'); + + expect(consoleSpy).toHaveBeenCalledWith('Response body fails validation: ', [ + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { path: '/response', message: 'must be array', errorCode: 'type.openapi.validation' }, + { + path: '/response/results/1/upnUri', + message: "must have required property 'upnUri'", + errorCode: 'required.openapi.validation', + }, + { + path: '/response/results/1/status', + message: "must have required property 'status'", + errorCode: 'required.openapi.validation', + }, + { + path: '/response/results/1', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + { + path: '/response', + message: 'must match exactly one schema in oneOf', + errorCode: 'oneOf.openapi.validation', + }, + ]); + consoleSpy.mockRestore(); + spy.mockRestore(); + }); + }); + }); +}); diff --git a/apps/overige-objecten-api/src/controllers/objects/index.ts b/apps/overige-objecten-api/src/controllers/objects/index.ts new file mode 100644 index 000000000..d4cfc034a --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/objects/index.ts @@ -0,0 +1,111 @@ +import type { RequestHandler } from 'express'; +import { GET_ALL_PRODUCTS, GET_PRODUCT_BY_UUID } from '../../queries'; +import type { StrapiProductType } from '../../strapi-product-type'; +import { fetchData, generateKennisartikelObject, getPaginatedResponse, getTheServerURL, vacData } from '../../utils'; + +export const getAllObjectsController: RequestHandler = async (req, res, next) => { + try { + const locale = req.query?.locale || 'nl'; + const type = (req.query?.type as string) || ''; + const tokenAuth = req.headers?.authorization?.split(' ')[1]; + const serverURL = getTheServerURL(req); + const kennisartikelSchemaURL = new URL('api/v1/objecttypes/kennisartikel', serverURL).href; + const vacSchemaURL = new URL('api/v1/objecttypes/vac', serverURL).href; + const vacObjects = vacData({ url: vacSchemaURL }); + const graphqlURL = new URL('/graphql', process.env.STRAPI_PRIVATE_URL); + // default pagination prams + const limit = parseInt(req.query?.limit as string, 10) || -1; + const start = parseInt(req.query?.start as string, 10) || 0; + const page = parseInt(req.query?.page as string, 10); + const pageSize = parseInt(req.query?.pageSize as string, 10); + // Validate page and pageSize + const isValidPage = typeof page === 'number' && page > 0; + const isValidPageSize = typeof pageSize === 'number' && pageSize > 0; + const paginationParams = { + page: isValidPage ? page : undefined, + pageSize: isValidPageSize ? pageSize : undefined, + limit: !isValidPage && !isValidPageSize ? limit : undefined, + start: !isValidPage && !isValidPageSize ? start : undefined, + }; + // Fetch product data from GraphQL + const { data } = await fetchData({ + url: graphqlURL.href, + query: GET_ALL_PRODUCTS, + variables: { locale, ...paginationParams }, + headers: { + Authorization: tokenAuth?.startsWith('Token') ? tokenAuth : `Token ${tokenAuth}`, + }, + }); + + const pagination = await getPaginatedResponse(req, data?.products); + + // Check if product data is available + const products = data?.products?.data || []; + + if (products.length === 0) return res.status(200).json({ results: [] }); + const kennisartikel = products.map(({ attributes }) => + generateKennisartikelObject({ attributes, url: kennisartikelSchemaURL }), + ); + + // Set response content type + res.set('Content-Type', 'application/json'); + + // Send results based on the requested type + if (type.endsWith('kennisartikel')) return res.status(200).json(kennisartikel); + + if (type.endsWith('vac')) return res.status(200).json(vacObjects); + + // If no specific type, return both kennisartikel and vac objects + return res.status(200).json({ ...pagination, results: [...kennisartikel, ...vacObjects] }); + } catch (error) { + // Forward any errors to the error handler middleware + next(error); + return null; + } +}; + +export const getObjectByUUIDController: RequestHandler = async (req, res, next) => { + try { + const locale = req.query?.locale || 'nl'; + const uuid = req.params?.uuid; + const graphqlURL = new URL('/graphql', process.env.STRAPI_PRIVATE_URL); + const serverURL = getTheServerURL(req); + const tokenAuth = req.headers?.authorization?.split(' ')[1]; + const kennisartikelSchemaURL = new URL('api/v1/objecttypes/kennisartikel', serverURL).href; + const vacSchemaURL = new URL('api/v1/objecttypes/vac', serverURL).href; + + // Fetch vac object based on UUID + const vac = vacData({ url: vacSchemaURL }).find((item) => item.uuid === uuid); + + // Fetch product data from GraphQL + const { data } = await fetchData({ + url: graphqlURL.href, + query: GET_PRODUCT_BY_UUID, + variables: { locale, uuid }, + headers: { + Authorization: tokenAuth?.startsWith('Token') ? tokenAuth : `Token ${tokenAuth}`, + }, + }); + + res.set('Content-Type', 'application/json'); + + // Handle the case for a knowledge article (kennisartikel) + const products = data?.products?.data || []; + const kennisartikel = products + .map(({ attributes }) => generateKennisartikelObject({ attributes, url: kennisartikelSchemaURL })) + .find((item: { uuid: string }) => item.uuid === uuid); + + if (kennisartikel) return res.status(200).json(kennisartikel); + + // Handle the case for vac data + if (vac) return res.status(200).json(vac); + + // If no matching object found, return 404 + return res.status(404).json({ message: 'Object not found' }); + } catch (error) { + // Forward any errors to the error handler middleware + next(error); + return null; + // Ensure function returns after calling next() + } +}; diff --git a/apps/overige-objecten-api/src/controllers/objecttypes/index.test.ts b/apps/overige-objecten-api/src/controllers/objecttypes/index.test.ts new file mode 100644 index 000000000..45e73c3de --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/objecttypes/index.test.ts @@ -0,0 +1,61 @@ +import fetchMock from 'jest-fetch-mock'; +import request from 'supertest'; +import kennisartikelObjectTypes from '../../docs/kennisartikel.json'; +import vacObjectTypes from '../../docs/vac.json'; +import app from '../../server'; + +jest.mock('../../utils/getTheServerURL.ts', () => ({ + getTheServerURL: () => 'http://localhost:3000', +})); +fetchMock.enableMocks(); +describe('objecttypesController', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + beforeEach(() => { + fetchMock.resetMocks(); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('GET /api/v1/objecttypes/:type should return 200 and kennisartikel.json', async () => { + const response = await request(app).get('/api/v1/objecttypes/kennisartikel'); + expect(response.status).toBe(200); + expect(response.body).toEqual(kennisartikelObjectTypes); + }); + it('GET /api/v1/objecttypes/:type should return 200 and VAC.json', async () => { + const response = await request(app).get('/api/v1/objecttypes/vac'); + expect(response.status).toBe(200); + expect(response.body).toEqual(vacObjectTypes); + }); + it('GET /api/v1/objecttypes/:type should return 400 when type is invalid', async () => { + const response = await request(app).get('/api/v1/objecttypes/invalid-type'); + expect(response.status).toBe(400); + expect(response.body).toEqual({ error: 'Invalid type parameter' }); + }); + it('GET /api/v1/objecttypes/:type should return 404 when type is not found', async () => { + const response = await request(app).get('/api/v1/objecttypes'); + expect(response.status).toBe(404); + }); + it('GET /api/v1/objecttypes/:type should return 500 when an error occurs', async () => { + const spy = jest.spyOn(require('../../utils/readFile'), 'readFile').mockImplementation(() => 'invalid-path'); + const response = await request(app).get('/api/v1/objecttypes/kennisartikel'); + expect(response.status).toBe(500); + spy.mockRestore(); + }); + + it('GET /api/v1/objecttypes/:type should return 500 when kennisartikel.json is not found', async () => { + const spy = jest.spyOn(require('../../utils/readFile'), 'readFile').mockImplementation(() => false); + const response = await request(app).get('/api/v1/objecttypes/kennisartikel'); + expect(response.status).toBe(500); + expect(response.body).toEqual({ message: 'An unexpected error occurred.' }); + spy.mockRestore(); + }); + it('GET /api/v1/objecttypes/:type should return 500 when VAC.json is not found', async () => { + const spy = jest.spyOn(require('../../utils/readFile'), 'readFile').mockImplementation(() => false); + const response = await request(app).get('/api/v1/objecttypes/vac'); + expect(response.status).toBe(500); + expect(response.body).toEqual({ message: 'An unexpected error occurred.' }); + spy.mockRestore(); + }); +}); diff --git a/apps/overige-objecten-api/src/controllers/objecttypes/index.ts b/apps/overige-objecten-api/src/controllers/objecttypes/index.ts new file mode 100644 index 000000000..ed0ce9873 --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/objecttypes/index.ts @@ -0,0 +1,29 @@ +/* eslint-disable no-undef */ +import type { RequestHandler } from 'express'; +import path from 'node:path'; +import { readFile } from '../../utils'; +export const objecttypesController: RequestHandler = async (req, res, next) => { + try { + const kennisartikelJSON = readFile(path.join(__dirname, '../../docs/kennisartikel.json')); + const vacJSON = readFile(path.join(__dirname, '../../docs/vac.json')); + const type = req.params.type as string; + + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.status(200); + + if (!kennisartikelJSON || !vacJSON) { + throw new Error('JSON file not found'); + } + if (type === 'kennisartikel') { + return res.json(JSON.parse(kennisartikelJSON)); + } else if (type === 'vac') { + return res.json(JSON.parse(vacJSON)); + } else { + return res.status(400).json({ error: 'Invalid type parameter' }); + } + } catch (error) { + next(error); + return null; + } +}; diff --git a/apps/overige-objecten-api/src/controllers/openapi/index.test.ts b/apps/overige-objecten-api/src/controllers/openapi/index.test.ts new file mode 100644 index 000000000..597bc8ba8 --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/openapi/index.test.ts @@ -0,0 +1,18 @@ +import request from 'supertest'; +import app from '../../server'; + +describe('openAPIController', () => { + it('GET /api/v1/openapi.json return 200 and json', async () => { + const response = await request(app).get('/api/v1/openapi.json'); + expect(response.status).toBe(200); + expect(response.headers['content-type']).toMatch(/json/); + }); + it('GET /api/v1/openapi.yaml return 500 when an error occurs', async () => { + const spy = jest.spyOn(require('../../utils/readFile'), 'readFile').mockImplementation(() => undefined); + const response = await request(app).get('/api/v1/openapi.json'); + expect(response.status).toBe(500); + expect(response.body).toEqual({ message: 'An unexpected error occurred.' }); + expect(response.text).toEqual(JSON.stringify({ message: 'An unexpected error occurred.' })); + spy.mockRestore(); + }); +}); diff --git a/apps/overige-objecten-api/src/controllers/openapi/index.ts b/apps/overige-objecten-api/src/controllers/openapi/index.ts new file mode 100644 index 000000000..b133e67a6 --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/openapi/index.ts @@ -0,0 +1,31 @@ +import type { RequestHandler } from 'express'; +import yaml from 'js-yaml'; +import path from 'node:path'; +import { getTheServerURL, readFile } from '../../utils'; + +type Server = { + url: string; + description: string; +}; + +export const openAPIController: RequestHandler = async (req, res, next) => { + try { + // eslint-disable-next-line no-undef + const openapiYAML = readFile(path.join(__dirname, '../../docs/openapi.yaml')); + + if (!openapiYAML) throw new Error('openapi.yaml file not found'); + + const openAPIDocument: any = yaml.load(openapiYAML); + const openapiServers = openAPIDocument.servers.map((server: Server) => ({ + url: new URL('api/v1', getTheServerURL(req)), + description: server.description, + })); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.status(200); + return res.json({ ...openAPIDocument, servers: openapiServers }); + } catch (error) { + next(error); + return null; + } +}; diff --git a/apps/overige-objecten-api/src/docs/kennisartikel.json b/apps/overige-objecten-api/src/docs/kennisartikel.json new file mode 100644 index 000000000..2b68b581d --- /dev/null +++ b/apps/overige-objecten-api/src/docs/kennisartikel.json @@ -0,0 +1,230 @@ +{ + "type": "object", + "title": "SDG Kennisartikel", + "required": [ + "url", + "uuid", + "upnUri", + "publicatieDatum", + "productValtOnder", + "productAanwezig", + "verantwoordelijkeOrganisatie", + "locaties", + "doelgroep", + "vertalingen", + "beschikbareTalen" + ], + "properties": { + "url": { + "type": "string", + "title": "URL", + "description": "De unieke URL van dit object binnen deze API.", + "format": "uri" + }, + "uuid": { + "type": "string", + "title": "UUID", + "description": "De identificatie die binnen deze API gebruikt wordt voor de resource.", + "format": "uuid" + }, + "upnUri": { + "type": "string", + "title": "UPN URI", + "description": "De UPN URI van het specifieke product.", + "format": "uri" + }, + "publicatieDatum": { + "type": "string", + "format": "date", + "title": "Publicatiedatum", + "description": "De datum die aangeeft wanneer het product gepubliceerd is/wordt.", + "nullable": true + }, + "productAanwezig": { + "type": "boolean", + "title": "Product Aanwezig", + "description": "Een boolean die aangeeft of de organisatie dit product levert of niet.", + "nullable": true + }, + "productValtOnder": { + "type": "string", + "title": "Product Valt Onder", + "description": "Als een product valt onder een ander product, dan staat deze hier vermeld.", + "nullable": true + }, + "verantwoordelijkeOrganisatie": { + "type": "object", + "title": "De Leverende Organisatie", + "description": "De organisatie die dit product levert en de teksten hiervan beheert.", + "required": ["url", "owmsIdentifier", "owmsEndDate"], + "properties": { + "url": { + "type": "string", + "title": "URL van object", + "description": "De unieke URL van dit object binnen deze API.", + "format": "uri" + }, + "owmsIdentifier": { + "type": "string", + "title": "OWMS Identifier", + "description": "De OWMS Identifier van de hoofdorganisatie van deze lokale overheid.", + "format": "uri" + }, + "owmsPrefLabel": { + "type": "string", + "title": "OWMS label", + "description": "OWMS label van de hoofdorganisatie van deze lokale overheid." + }, + "owmsEndDate": { + "type": "string", + "title": "Einddatum", + "description": "De einddatum, zoals gevonden in het OWMS-model.", + "format": "date-time" + } + } + }, + "locaties": { + "type": "array", + "title": "Locaties waar Beschikbaar", + "description": "Een lijst met locaties waarop dit product beschikbaar is. Deze is nog niet nodig voor KISS en mag null zijn.", + "items": { + "type": "string" + }, + "nullable": true + }, + "doelgroep": { + "type": "string", + "title": "Doelgroep", + "description": "De doelgroep van dit product.", + "enum": ["eu-burger", "eu-bedrijf"] + }, + "afdelingen": { + "type": "array", + "title": "Afdeling of Afdelingen", + "description": "De afdeling of afdelingen die de teksten van dit product beheert.", + "items": { + "type": "object", + "required": ["afdelingnaam"], + "properties": { + "afdelingId": { + "type": "string", + "title": "Identifier Afdeling", + "description": "De unieke identifier van de afdeling." + }, + "afdelingnaam": { + "type": "string", + "title": "Naam Afdeling", + "description": "De naam van de afdeling." + } + } + } + }, + "vertalingen": { + "type": "array", + "title": "Vertalingen", + "description": "Een lijst met specifieke teksten op basis van taal.", + "items": { + "type": "object", + "required": ["taal", "datumWijziging"], + "properties": { + "taal": { + "type": "string", + "enum": ["nl", "en"], + "title": "Weergavetaal", + "description": "De taal waarin het kennisartikel is geschreven." + }, + "titel": { + "type": "string", + "title": "Titel van het Kennisartikel", + "description": "Een korte herkenbare titel van het kennisartikel." + }, + "tekst": { + "type": "string", + "title": "Inleiding", + "description": "Inleidende tekst voor het product." + }, + "procedureBeschrijving": { + "type": "string", + "title": "Aanvraag", + "description": "De beschrijving van hoe het product wordt aangevraagd. Aanpak." + }, + "bewijs": { + "type": "string", + "title": "Bewijs", + "description": "Dit bevat de bewijsstukken die de burger of ondernemer nodig heeft om dit product aan te vragen." + }, + "vereisten": { + "type": "string", + "title": "Eisen", + "description": "Dit beschrijft de vereisten waar een burger of ondernemer aan moet voldoen om gebruik te kunnen maken van het product." + }, + "bezwaarEnBeroep": { + "type": "string", + "title": "Bezwaar", + "description": "Beschrijft hoe de burger of ondernemer bezwaar kan maken, tegen een besluit over dit product." + }, + "kostenEnBetaalmethoden": { + "type": "string", + "title": "Kosten", + "description": "Beschrijft hoe de burger of ondernemer kan betalen en wat de kosten zijn." + }, + "uitersteTermijn": { + "type": "string", + "title": "Termijn", + "description": "De informatie over hoe hoelang het duurt voor het aanvragen van dit product, de termijn." + }, + "wtdBijGeenReactie": { + "type": "string", + "title": "Wat te doen bij geen reactie", + "description": "Beschrijft wat de aanvrager moet doen bij geen reactie." + }, + "notice": { + "type": "string", + "title": "Bijzonderheden", + "description": "Bijzonderheden rondom het product, die nog niet is vermeld bij één van de andere onderdelen." + }, + "contact": { + "type": "string", + "title": "Contact", + "description": "Beschrijving van hoe en waar de burger of ondernemer contact op kan nemen over dit product." + }, + "deskMemo": { + "type": "string", + "title": "Interne Informatie", + "description": "Interne informatie, alleen bestemd voor de Balie- of KCC-medewerker." + }, + "trefwoorden": { + "type": "array", + "title": "Trefwoorden", + "description": "Trefwoorden die passen bij het Kennisartikel/product.", + "items": { + "type": "object", + "required": ["trefwoord"], + "properties": { + "trefwoord": { + "type": "string", + "title": "Trefwoord", + "description": "Een trefwoord." + } + } + } + }, + "datumWijziging": { + "type": "string", + "title": "Datum Laatste Wijziging", + "description": "Datum wanneer dit product voor het laatst is gewijzigd.", + "format": "date-time" + } + } + } + }, + "beschikbareTalen": { + "type": "array", + "title": "Beschikbare Talen", + "description": "Alle beschikbare talen.", + "items": { + "type": "string" + } + } + } +} diff --git a/apps/overige-objecten-api/src/docs/openapi.yaml b/apps/overige-objecten-api/src/docs/openapi.yaml new file mode 100644 index 000000000..a8e963df7 --- /dev/null +++ b/apps/overige-objecten-api/src/docs/openapi.yaml @@ -0,0 +1,550 @@ +openapi: "3.0.0" +info: + title: Overige Objecten API + description: API to manage Overige Objecten resources + version: 1.0.0 +servers: + - url: http://localhost:4001/api/v1 + description: Example server + +paths: + /objects: + get: + security: + - TokenAuth: [] + summary: List all objects (Kennisartikelen and VAC) or filter by type + operationId: getObjects + parameters: + - in: query + name: type + required: false # Make 'type' optional + schema: + type: string + format: uri + enum: + - "http://localhost:4001/api/v1/objecttypes/kennisartikel" + - "http://localhost:4001/api/v1/objecttypes/vac" + description: Optional parameter to filter by object type. If not specified, both types will be returned. + - in: query + name: page + required: false + schema: + type: integer + default: null + description: Page number for pagination. + - in: query + name: pageSize + required: false + schema: + type: integer + default: null + description: Number of items per page for pagination. + + responses: + "200": + description: A list of objects (Kennisartikelen and/or VAC) + content: + application/json: + schema: + oneOf: # Use oneOf to allow either kennisartikel or vac + - type: array + items: + $ref: "#/components/schemas/kennisartikel" + - type: array + items: + $ref: "#/components/schemas/vac" + - type: array + items: + type: object + additionalProperties: false + - type: object + properties: + page: + type: integer + example: 1 + pageSize: + type: integer + example: 1 + count: + type: integer + example: 284 + total: + type: integer + example: 284 + next: + type: string + format: uri + nullable: true + example: "http://localhost:4001/api/v1/objects?type=http%3A%2F%2Flocalhost%3A4001%2Fapi%2Fv1%2Fobjecttypes%2Fkennisartikel&page=2&pageSize=1" + previous: + type: string + format: uri + nullable: true + example: null + results: + type: array + items: + oneOf: + - $ref: "#/components/schemas/kennisartikel" + - $ref: "#/components/schemas/vac" + example: + page: 1 + pageSize: 10 + count: 29 + total: 284 + next: "http://localhost:4001/api/v1/objects?page=2&pageSize=10" + previous: null + results: + - url: "http://localhost:4001/api/v1/objecttypes/kennisartikel" + uuid: "123e4567-e89b-12d3-a456-426614174000" + upnUri: "http://example.com/upn/1" + publicatieDatum: "2023-01-01" + productAanwezig: true + verantwoordelijkeOrganisatie: + url: "http://example.com/organisatie/1" + owmsIdentifier: "http://example.com/owms/1" + owmsEndDate: "2024-01-01T00:00:00Z" + locaties: ["Locatie 1", "Locatie 2"] + doelgroep: "eu-burger" + afdelingen: + - afdelingId: "1" + afdelingNaam: "Burgerzaken" + vertalingen: + - taal: "nl" + titel: "Kennisartikel Titel" + tekst: "Dit is de inleiding van het kennisartikel." + datumWijziging: "2023-02-01T00:00:00Z" + - vraag: "Wat is het proces om een paspoort aan te vragen?" + status: "actief" + antwoord: "U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren." + doelgroep: "eu-burger" + afdelingen: + - afdelingId: "1" + afdelingNaam: "Burgerzaken" + toelichting: "Deze VAC biedt informatie over het aanvragen van een paspoort in Nederland." + trefwoorden: + - trefwoord: "paspoort" + - trefwoord: "aanvragen" + gerelateerdeVACs: [] + gerelateerdeProducten: [] + "401": + description: Unauthorized + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Unauthorized" + "403": + description: Forbidden + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Forbidden" + "500": + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Internal Server Error" + /objects/{uuid}: + get: + security: + - TokenAuth: [] + summary: Get a specific object by ID + operationId: getObjectById + parameters: + - in: path + name: uuid + required: true + description: The unique identifier of the object + schema: + type: string + responses: + "200": + description: Object found + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/kennisartikel" + - $ref: "#/components/schemas/vac" + example: + url: "http://localhost:4001/api/v1/objecttypes/vac" + vraag: "Wat is het proces om een paspoort aan te vragen?" + status: "actief" + antwoord: "U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren." + doelgroep: "eu-burger" + afdelingen: + - afdelingId: "1" + afdelingNaam: "Burgerzaken" + toelichting: "Deze VAC biedt informatie over het aanvragen van een paspoort in Nederland." + trefwoorden: + - trefwoord: "paspoort" + - trefwoord: "aanvragen" + gerelateerdeVACs: [] + gerelateerdeProducten: [] + "404": + description: Object not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Object not found" + "403": + description: Forbidden + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Forbidden" + "500": + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Internal Server Error" +components: + securitySchemes: + TokenAuth: + type: apiKey + in: header + name: Authorization + schemas: + kennisartikel: + type: object + title: SDG Kennisartikel + required: + - url + - uuid + - upnUri + - publicatieDatum + - productValtOnder + - productAanwezig + - verantwoordelijkeOrganisatie + - locaties + - doelgroep + - vertalingen + - beschikbareTalen + properties: + url: + type: string + title: URL + description: De unieke URL van dit object binnen deze API. + format: uri + uuid: + type: string + title: UUID + description: De identificatie die binnen deze API gebruikt wordt voor de resource. + format: uuid + upnUri: + type: string + title: UPN URI + description: De UPN URI van het specifieke product. + format: uri + publicatieDatum: + type: string + format: date + title: Publicatiedatum + description: De datum die aangeeft wanneer het product gepubliceerd is/wordt. + nullable: true + productAanwezig: + type: boolean + title: Product Aanwezig + description: + Een boolean die aangeeft of de organisatie dit product levert of + niet. + nullable: true + productValtOnder: + type: string + title: Product Valt Onder + description: + Als een product valt onder een ander product, dan staat deze hier + vermeld. + nullable: true + verantwoordelijkeOrganisatie: + type: object + title: De Leverende Organisatie + description: De organisatie die dit product levert en de teksten hiervan beheert. + required: + - url + - owmsIdentifier + - owmsEndDate + properties: + url: + type: string + title: URL van object + description: De unieke URL van dit object binnen deze API. + format: uri + owmsIdentifier: + type: string + title: OWMS Identifier + description: De OWMS Identifier van de hoofdorganisatie van deze lokale overheid. + format: uri + owmsPrefLabel: + type: string + title: OWMS label + description: OWMS label van de hoofdorganisatie van deze lokale overheid. + owmsEndDate: + type: string + title: Einddatum + description: De einddatum, zoals gevonden in het OWMS-model. + format: date-time + locaties: + type: array + title: Locaties waar Beschikbaar + description: + Een lijst met locaties waarop dit product beschikbaar is. Deze is + nog niet nodig voor KISS en mag null zijn. + items: + type: string + nullable: true + doelgroep: + type: string + title: Doelgroep + description: De doelgroep van dit product. + enum: + - eu-burger + - eu-bedrijf + afdelingen: + type: array + title: Afdeling of Afdelingen + description: De afdeling of afdelingen die de teksten van dit product beheert. + items: + type: object + required: + - afdelingnaam + properties: + afdelingId: + type: string + title: Identifier Afdeling + description: De unieke identifier van de afdeling. + afdelingnaam: + type: string + title: Naam Afdeling + description: De naam van de afdeling. + vertalingen: + type: array + title: Vertalingen + description: Een lijst met specifieke teksten op basis van taal. + items: + type: object + required: + - taal + - datumWijziging + properties: + taal: + type: string + enum: + - nl + - en + title: Weergavetaal + description: De taal waarin het kennisartikel is geschreven. + titel: + type: string + title: Titel van het Kennisartikel + description: Een korte herkenbare titel van het kennisartikel. + tekst: + type: string + title: Inleiding + description: Inleidende tekst voor het product. + procedureBeschrijving: + type: string + title: Aanvraag + description: De beschrijving van hoe het product wordt aangevraagd. Aanpak. + bewijs: + type: string + title: Bewijs + description: + Dit bevat de bewijsstukken die de burger of ondernemer nodig + heeft om dit product aan te vragen. + vereisten: + type: string + title: Eisen + description: + Dit beschrijft de vereisten waar een burger of ondernemer aan + moet voldoen om gebruik te kunnen maken van het product. + bezwaarEnBeroep: + type: string + title: Bezwaar + description: + Beschrijft hoe de burger of ondernemer bezwaar kan maken, tegen + een besluit over dit product. + kostenEnBetaalmethoden: + type: string + title: Kosten + description: + Beschrijft hoe de burger of ondernemer kan betalen en wat de + kosten zijn. + uitersteTermijn: + type: string + title: Termijn + description: + De informatie over hoe hoelang het duurt voor het aanvragen + van dit product, de termijn. + wtdBijGeenReactie: + type: string + title: Wat te doen bij geen reactie + description: Beschrijft wat de aanvrager moet doen bij geen reactie. + notice: + type: string + title: Bijzonderheden + description: + Bijzonderheden rondom het product, die nog niet is vermeld + bij één van de andere onderdelen. + contact: + type: string + title: Contact + description: + Beschrijving van hoe en waar de burger of ondernemer contact + op kan nemen over dit product. + deskMemo: + type: string + title: Interne Informatie + description: Interne informatie, alleen bestemd voor de Balie- of KCC-medewerker. + trefwoorden: + type: array + title: Trefwoorden + description: Trefwoorden die passen bij het Kennisartikel/product. + items: + type: object + required: + - trefwoord + properties: + trefwoord: + type: string + title: Trefwoord + description: Een trefwoord. + datumWijziging: + type: string + title: Datum Laatste Wijziging + description: Datum wanneer dit product voor het laatst is gewijzigd. + format: date-time + beschikbareTalen: + type: array + title: Beschikbare Talen + description: Alle beschikbare talen. + items: + type: string + + vac: + type: object + title: VAC + required: + - vraag + - status + - antwoord + - doelgroep + - url + properties: + url: + type: string + title: JSON Schema + description: De URI van het JSON Schema van dit object. + format: uri + nullable: true + vraag: + type: string + title: De vraag + description: De vraag waar de VAC antwoord op geeft. + status: + enum: + - actief + - non-actief + - te-verwijderen + type: string + title: Status van de VAC + description: De actuele status van de VAC, is deze nog actueel of niet. + antwoord: + type: string + description: Het antwoord op de bij 'vraag' gestelde vraag. + doelgroep: + type: string + enum: + - eu-burger + - eu-bedrijf + - eu-burger-bedrijf + description: De doelgroep van de VAC. + afdelingen: + type: array + title: Gerelateerde Afdelingen + description: Afdelingen aan wie de VAC gerelateerd is. + items: + type: object + required: + - afdelingId + - afdelingNaam + properties: + afdelingId: + type: string + description: De unieke identifier van de afdeling. + afdelingNaam: + type: string + description: De naam van de afdeling. + toelichting: + type: string + description: + Een toelichting bij de VAC ten behoeve van de medewerker van het + contactcentrum. + trefwoorden: + type: array + title: Trefwoorden + description: Trefwoorden die passen bij de VAC. + items: + type: object + required: + - trefwoord + properties: + trefwoord: + type: string + description: Een trefwoord. + gerelateerdeVACs: + type: array + title: Gerelateerde VACs + description: VACs die gerelateerd zijn aan de VAC. + items: + type: object + required: + - VAC + properties: + VAC: + type: string + format: uri + description: De URI van de gerelateerde VAC. + gerelateerdeProducten: + type: array + items: + type: object + required: + - product + - productNaam + properties: + product: + type: string + format: uri + description: De URI van het gerelateerde product. + productNaam: + type: string + description: De naam van het gerelateerde product. + title: Gerelateerde Producten + description: Producten die gerelateerd zijn aan de VAC. diff --git a/apps/overige-objecten-api/src/docs/vac.json b/apps/overige-objecten-api/src/docs/vac.json new file mode 100644 index 000000000..2458ab416 --- /dev/null +++ b/apps/overige-objecten-api/src/docs/vac.json @@ -0,0 +1,108 @@ +{ + "type": "object", + "title": "VAC", + "required": ["vraag", "status", "antwoord", "doelgroep", "url"], + "properties": { + "url": { + "type": "string", + "title": "JSON Schema", + "description": "De URI van het JSON Schema van dit object.", + "format": "uri", + "nullable": true + }, + "vraag": { + "type": "string", + "title": "De vraag", + "description": "De vraag waar de VAC antwoord op geeft." + }, + "status": { + "enum": ["actief", "non-actief", "te-verwijderen"], + "type": "string", + "title": "Status van de VAC", + "description": "De actuele status van de VAC, is deze nog actueel of niet." + }, + "antwoord": { + "type": "string", + "description": "Het antwoord op de bij 'vraag' gestelde vraag." + }, + "doelgroep": { + "type": "string", + "enum": ["eu-burger", "eu-bedrijf", "eu-burger-bedrijf"], + "description": "De doelgroep van de VAC." + }, + "afdelingen": { + "type": "array", + "title": "Gerelateerde Afdelingen", + "description": "Afdelingen aan wie de VAC gerelateerd is.", + "items": { + "type": "object", + "required": ["afdelingId", "afdelingNaam"], + "properties": { + "afdelingId": { + "type": "string", + "description": "De unieke identifier van de afdeling." + }, + "afdelingNaam": { + "type": "string", + "description": "De naam van de afdeling." + } + } + } + }, + "toelichting": { + "type": "string", + "description": "Een toelichting bij de VAC ten behoeve van de medewerker van het contactcentrum." + }, + "trefwoorden": { + "type": "array", + "title": "Trefwoorden", + "description": "Trefwoorden die passen bij de VAC.", + "items": { + "type": "object", + "required": ["trefwoord"], + "properties": { + "trefwoord": { + "type": "string", + "description": "Een trefwoord." + } + } + } + }, + "gerelateerdeVACs": { + "type": "array", + "title": "Gerelateerde VACs", + "description": "VACs die gerelateerd zijn aan de VAC.", + "items": { + "type": "object", + "required": ["VAC"], + "properties": { + "VAC": { + "type": "string", + "format": "uri", + "description": "De URI van de gerelateerde VAC." + } + } + } + }, + "gerelateerdeProducten": { + "type": "array", + "items": { + "type": "object", + "required": ["product", "productNaam"], + "properties": { + "product": { + "type": "string", + "format": "uri", + "description": "De URI van het gerelateerde product." + }, + "productNaam": { + "type": "string", + "description": "De naam van het gerelateerde product." + } + } + }, + "title": "Gerelateerde Producten", + "description": "Producten die gerelateerd zijn aan de VAC." + } + } +} diff --git a/apps/overige-objecten-api/src/queries/index.ts b/apps/overige-objecten-api/src/queries/index.ts new file mode 100644 index 000000000..6a628ca31 --- /dev/null +++ b/apps/overige-objecten-api/src/queries/index.ts @@ -0,0 +1,107 @@ +const gql = (query: any) => query; +export const GET_ALL_PRODUCTS = gql(` +query getAllProducts($locale: I18NLocaleCode, $page: Int, $pageSize: Int, $start: Int, $limit: Int) { + products(locale: $locale, pagination: { start: $start, limit: $limit, page: $page, pageSize: $pageSize }) { + meta { + pagination { + total + page + pageSize + pageCount + } + } + data { + id + attributes { + title + slug + uuid + locale + updatedAt + createdAt + locale + metaTags { + keymatch + title + description + } + sections { + ... on ComponentComponentsUtrechtRichText { + id + content + kennisartikelCategorie + component: __typename + } + } + kennisartikelMetadata { + uuid + doelgroep + productAanwezig + productValtOnder + afdelingen { + afdelingId + afdelingnaam + } + verantwoordelijkeOrganisatie { + owmsIdentifier + owmsPrefLabel + owmsEndDate + } + upnUri + } + } + } + } +} +`); + +export const GET_PRODUCT_BY_UUID = gql(` + query getProductByUUID($locale: I18NLocaleCode, $uuid: String) { + products( + locale: $locale, + filters: { uuid: { eq: $uuid }} + ) { + data { + id + attributes { + title + slug + uuid + locale + updatedAt + createdAt + locale + metaTags { + keymatch + title + description + } + sections { + ... on ComponentComponentsUtrechtRichText { + id + content + kennisartikelCategorie + component:__typename + } + } + kennisartikelMetadata { + uuid + doelgroep + productAanwezig + productValtOnder + afdelingen { + afdelingId + afdelingnaam + } + verantwoordelijkeOrganisatie { + owmsIdentifier + owmsPrefLabel + owmsEndDate + } + upnUri + } + } + } + } + } +`); diff --git a/apps/overige-objecten-api/src/routers/index.ts b/apps/overige-objecten-api/src/routers/index.ts new file mode 100644 index 000000000..36857cf7b --- /dev/null +++ b/apps/overige-objecten-api/src/routers/index.ts @@ -0,0 +1,3 @@ +export { default as objects } from './objects'; +export { default as openapi } from './openapi'; +export { default as objecttypes } from './objecttypes'; diff --git a/apps/overige-objecten-api/src/routers/objects/index.ts b/apps/overige-objecten-api/src/routers/objects/index.ts new file mode 100644 index 000000000..1d7167a9c --- /dev/null +++ b/apps/overige-objecten-api/src/routers/objects/index.ts @@ -0,0 +1,9 @@ +import express from 'express'; +import { getAllObjectsController, getObjectByUUIDController } from '../../controllers'; + +const router = express.Router({ mergeParams: true }); + +router.get('/objects', getAllObjectsController); +router.get('/objects/:uuid', getObjectByUUIDController); + +export default router; diff --git a/apps/overige-objecten-api/src/routers/objecttypes/index.ts b/apps/overige-objecten-api/src/routers/objecttypes/index.ts new file mode 100644 index 000000000..faf1be029 --- /dev/null +++ b/apps/overige-objecten-api/src/routers/objecttypes/index.ts @@ -0,0 +1,8 @@ +import express from 'express'; +import { objecttypesController } from '../../controllers'; + +const router = express.Router({ mergeParams: true }); + +router.get('/objecttypes/:type', objecttypesController); + +export default router; diff --git a/apps/overige-objecten-api/src/routers/openapi/index.ts b/apps/overige-objecten-api/src/routers/openapi/index.ts new file mode 100644 index 000000000..77a95d65a --- /dev/null +++ b/apps/overige-objecten-api/src/routers/openapi/index.ts @@ -0,0 +1,8 @@ +import express from 'express'; +import { openAPIController } from '../../controllers'; + +const router = express.Router({ mergeParams: true }); + +router.get('/openapi.json', openAPIController); + +export default router; diff --git a/apps/overige-objecten-api/src/server.ts b/apps/overige-objecten-api/src/server.ts new file mode 100644 index 000000000..af6be3893 --- /dev/null +++ b/apps/overige-objecten-api/src/server.ts @@ -0,0 +1,134 @@ +/* eslint-disable no-undef */ +import type { CorsOptions } from 'cors'; +import cors from 'cors'; +import { config } from 'dotenv'; +import express from 'express'; +import { NextFunction, Request, Response } from 'express'; +import * as OpenApiValidator from 'express-openapi-validator'; +import yaml from 'js-yaml'; +import fs from 'node:fs'; +import path from 'node:path'; +import swaggerUi from 'swagger-ui-express'; +import { objects, objecttypes, openapi } from './routers'; +import { envAvailability, ErrorHandler } from './utils'; +config(); + +type OpenOpenApiValidationError = { + path: string; + message: string; + errorCode: string; +}; +interface OpenApiValidationErrorTypes { + errors: OpenOpenApiValidationError[]; +} + +// Validate environment variables +envAvailability({ + env: process.env, + keys: ['STRAPI_PRIVATE_URL', 'OVERIGE_OBJECTEN_API_PORT'], +}); + +const swaggerDocument: any = yaml.load(fs.readFileSync(path.join(__dirname, './docs/openapi.yaml'), 'utf8')); +const whitelist = process.env.OVERIGE_OBJECTEN_API_CORS?.split(', ') || []; +const corsOption: CorsOptions = { + origin: (origin, callback) => { + if (!origin || whitelist.indexOf(origin) !== -1) { + callback(null, true); + } else { + callback( + new ErrorHandler('Not allowed by CORS', { + statusCode: 403, + }), + ); + } + }, + optionsSuccessStatus: 200, +}; +const apiSpec = path.join(__dirname, './docs/openapi.yaml'); +const app = express(); +app.use(express.json()); + +const port = process.env.OVERIGE_OBJECTEN_API_PORT; +// Centralized error handler middleware +const globalErrorHandler = (err: ErrorHandler, _req: Request, res: Response, _next: NextFunction) => { + if (err instanceof ErrorHandler || (err as ErrorHandler)?.isOperational) { + // Send the proper error response with status code and message + return res.status(err?.options?.statusCode || 500).json({ + message: err.message, + }); + } + + const isOpenApiValidatorError = (err as OpenApiValidationErrorTypes)?.errors; + if (isOpenApiValidatorError) { + return res.status((err as any)?.status || 400).json((err as OpenApiValidationErrorTypes).errors); + } + // If it's an unknown error (not an operational error), log it and send a generic response + // eslint-disable-next-line no-console + console.error('Unexpected error:', err); + return res.status(500).json({ + message: 'An unexpected error occurred.', + }); +}; +/** + * Swagger + * Serve the Swagger UI for testing and documentation + * This is only available in development mode + */ +if (process.env.NODE_ENV === 'development') { + app.use('/api/v1/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +} + +/** + * Objecttypes + * /api/v1/objecttypes/:type + */ +app.use('/api/v1', objecttypes); +/** + * OpenAPI + * Serve the OpenAPI documentation + */ +app.use('/api/v1', openapi); +/** + * OpenAPI Validator + * Validate requests and responses according to the OpenAPI specification + */ +app.use( + OpenApiValidator.middleware({ + apiSpec, + validateResponses: { + onError: (error, _body, _req) => { + // Log the error from express-openapi-validator instead of returning the error and blocking the response. + // eslint-disable-next-line no-console + console.log(`Response body fails validation: `, error.errors); + }, + }, + }), +); +/** + * CORS + * Enable CORS with a whitelist of allowed origins + */ +app.use(cors(corsOption)); +/** + * Routes + * /api/v1/objects + * /api/v1/objects/:uuid + */ +app.use('/api/v1', objects); +// handle non existing routes +app.use((_req, res) => { + res.status(404).send('Route not found'); +}); +// Use global error handler middleware +app.use(globalErrorHandler); +/** + * Start the server + */ +if (process.env.NODE_ENV !== 'test') { + app.listen(port, () => { + // eslint-disable-next-line no-console + console.log(`Overige Objecten app listening on port ${port}!`); + }); +} + +export default app; diff --git a/apps/overige-objecten-api/src/strapi-product-type.ts b/apps/overige-objecten-api/src/strapi-product-type.ts new file mode 100644 index 000000000..514fe1a77 --- /dev/null +++ b/apps/overige-objecten-api/src/strapi-product-type.ts @@ -0,0 +1,71 @@ +export interface StrapiProductType { + data: Data; +} +export interface Meta { + pagination: Pagination; +} +export interface Pagination { + total: number; + page: number; + pageSize: number; + pageCount: number; +} +export interface Data { + products: Products; +} + +export interface Products { + data: Datum[]; + meta: Meta; +} + +export interface Datum { + id: string; + attributes: Attributes; +} + +export interface Attributes { + title: string; + slug: string; + uuid: string; + locale: 'nl' | 'en'; + updatedAt: string; + createdAt: string; + metaTags: MetaTags; + sections: Section[]; + kennisartikelMetadata: KennisartikelMetadata; +} + +export interface KennisartikelMetadata { + uuid: string; + doelgroep: 'eu-burger' | 'eu-bedrijf'; + productAanwezig: boolean; + productValtOnder: null; + afdelingen: Afdelingen[]; + verantwoordelijkeOrganisatie: VerantwoordelijkeOrganisatie; + upnUri: string; +} + +export interface Afdelingen { + afdelingId: string; + afdelingnaam: string; +} + +export interface VerantwoordelijkeOrganisatie { + owmsIdentifier: string; + owmsPrefLabel: string; + owmsEndDate: string; +} + +export interface MetaTags { + keymatch: string; + title: string; + description: string; +} + +export interface Section { + id?: string; + content?: string; + kennisartikelCategorie?: null | string; + component?: string; +} diff --git a/apps/overige-objecten-api/src/tests/jest.setup.ts b/apps/overige-objecten-api/src/tests/jest.setup.ts new file mode 100644 index 000000000..9e92f7c1b --- /dev/null +++ b/apps/overige-objecten-api/src/tests/jest.setup.ts @@ -0,0 +1,3 @@ +import fetchMock from 'jest-fetch-mock'; + +fetchMock.enableMocks(); diff --git a/apps/overige-objecten-api/src/utils/envAvailability.ts b/apps/overige-objecten-api/src/utils/envAvailability.ts new file mode 100644 index 000000000..41e5f2c64 --- /dev/null +++ b/apps/overige-objecten-api/src/utils/envAvailability.ts @@ -0,0 +1,11 @@ +interface EnvValidator { + env: any; + keys: string[]; +} +export const envAvailability = ({ env, keys }: EnvValidator) => { + keys?.forEach((key: string) => { + if (!env[key]) { + throw new Error(`Missing required environment variable: ${key}`); + } + }); +}; diff --git a/apps/overige-objecten-api/src/utils/errorHandler.ts b/apps/overige-objecten-api/src/utils/errorHandler.ts new file mode 100644 index 000000000..800d096b9 --- /dev/null +++ b/apps/overige-objecten-api/src/utils/errorHandler.ts @@ -0,0 +1,16 @@ +export type Options = { + statusCode: number; +}; +export class ErrorHandler extends Error { + isOperational: boolean; // this flag for custom error identification + + constructor( + message?: string, + public options?: Options, + ) { + super(message); + this.name = 'ErrorHandler'; + this.options = options; + this.isOperational = true; // Operational errors should be marked + } +} diff --git a/apps/overige-objecten-api/src/utils/fetchData.ts b/apps/overige-objecten-api/src/utils/fetchData.ts new file mode 100644 index 000000000..cffebbc4c --- /dev/null +++ b/apps/overige-objecten-api/src/utils/fetchData.ts @@ -0,0 +1,146 @@ +import { ErrorHandler } from './errorHandler'; + +interface HandleGraphqlRequestProps { + query?: string; + variables: any; + headers?: HeadersInit; +} + +const handleGraphqlRequest = ({ query, variables, headers }: HandleGraphqlRequestProps) => + ({ + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...headers, // Merge custom headers (including the ability to overwrite 'Content-Type') + }, + body: JSON.stringify({ query, variables }), + cache: 'no-store', + }) as RequestInit; + +export interface FetchDataProps { + url: string; + query?: string; + variables?: any; + method?: string; + headers?: HeadersInit; // Allow custom headers to be passed +} + +/** + * @description Fetches data from the server (GraphQL or REST). + * @param {string} url - The URL to fetch data from. + * @param {string} query - The GraphQL query (if applicable). + * @param {any} variables - The variables to pass to the GraphQL queries. + * @param {string} method - The HTTP method, default is POST for GraphQL. + * @param {HeadersInit} headers - Custom headers to pass to the request. + * @returns {Promise} - The fetched data. + */ +export const fetchData = async ({ + url, + query, + variables, + method = 'POST', + headers = {}, // Default to an empty object if no headers are provided +}: FetchDataProps): Promise => { + // Default headers, which can be overwritten by custom headers (e.g., Content-Type) + const defaultHeaders: HeadersInit = { + 'Content-Type': 'application/json', + }; + + const requestOptions: RequestInit = query + ? handleGraphqlRequest({ query, variables, headers: { ...defaultHeaders, ...headers } }) + : { + method, + cache: 'no-store', + headers: { + ...defaultHeaders, + ...headers, // Merge custom headers with default ones (overwriting defaults if needed) + }, + }; + + try { + const response = await fetch(url, requestOptions); + + // Check for non-successful responses (status not in the 2xx range) + if (!response.ok) { + handleHttpError(response); + } + + const data = await response.json(); + + // Handle GraphQL-specific errors + if (data.errors && data.errors.length > 0) { + data.errors.forEach(handleGraphqlError); // Process each error + } + + return data; + } catch (error: any) { + // Handle and log client-side or unexpected errors + throw new ErrorHandler(error.message || 'Unknown error occurred', { + statusCode: error?.options?.statusCode || 500, + }); + } +}; + +/** + * Handle common HTTP errors and throw the appropriate ErrorHandler + * @param response - Fetch API Response object + */ +const handleHttpError = (response: Response) => { + const status = response.status; + + let errorMessage = response.statusText || 'Unknown error'; + + // Specific error messages based on status codes + switch (status) { + case 400: + errorMessage = 'Bad Request'; + break; + case 401: + errorMessage = 'Unauthorized'; + break; + case 403: + errorMessage = 'Forbidden'; + break; + case 404: + errorMessage = 'Resource Not Found'; + break; + case 422: + errorMessage = 'Unprocessable Entity'; + break; + case 500: + errorMessage = 'Internal Server Error'; + break; + case 503: + errorMessage = 'Service Unavailable'; + break; + case 504: + errorMessage = 'Gateway Timeout'; + break; + case 505: + errorMessage = 'HTTP Version Not Supported'; + break; + default: + errorMessage = `Unexpected error: ${status}`; + break; + } + throw new ErrorHandler(errorMessage, { statusCode: status }); +}; + +/** + * Handle GraphQL-specific errors like 'Forbidden access' + * @param error - The error object returned by GraphQL + */ +const handleGraphqlError = (error: any) => { + const errorMessage = error?.message || 'GraphQL error'; + const errorCode = error?.extensions?.code || 400; // Handle extensions (specific to GraphQL) + // Handle known GraphQL error messages + if (errorCode === 'FORBIDDEN') { + throw new ErrorHandler('Forbidden access: You do not have the required permissions.', { + statusCode: 403, + }); + } + + throw new ErrorHandler(errorMessage, { + statusCode: errorCode, + }); +}; diff --git a/apps/overige-objecten-api/src/utils/generateKennisartikelObject.ts b/apps/overige-objecten-api/src/utils/generateKennisartikelObject.ts new file mode 100644 index 000000000..14349df37 --- /dev/null +++ b/apps/overige-objecten-api/src/utils/generateKennisartikelObject.ts @@ -0,0 +1,52 @@ +import { Attributes } from '../strapi-product-type'; +import { components } from '../types/openapi'; +interface GenerateKennisartikelObjectTypes { + attributes: Attributes; + url: string; +} +export const generateKennisartikelObject = ({ attributes, url }: GenerateKennisartikelObjectTypes) => { + const contentBlock = attributes?.sections.find(({ component }) => component === 'ComponentComponentsUtrechtRichText'); + const metaTags = attributes?.metaTags; + const trefwoorden = metaTags?.keymatch?.split(', ').map((trefwoord: string) => ({ trefwoord })) || []; + const kennisartikelMetadata = attributes.kennisartikelMetadata; + const publicatieDatum = new Date(attributes.createdAt).toISOString().split('T')[0]; + const data: components['schemas']['kennisartikel'] = { + url, + uuid: attributes.uuid, + upnUri: kennisartikelMetadata?.upnUri, + publicatieDatum, + productAanwezig: kennisartikelMetadata?.productAanwezig, + productValtOnder: kennisartikelMetadata?.productValtOnder, // we need an extra field for this + verantwoordelijkeOrganisatie: { + url: `${url}#verantwoordelijkeOrganisatie`, + owmsIdentifier: kennisartikelMetadata?.verantwoordelijkeOrganisatie?.owmsIdentifier, + owmsPrefLabel: kennisartikelMetadata?.verantwoordelijkeOrganisatie?.owmsPrefLabel, + owmsEndDate: kennisartikelMetadata?.verantwoordelijkeOrganisatie?.owmsEndDate, + }, + locaties: null, //Een lijst met locaties waarop dit product beschikbaar is. Deze is nog niet nodig voor KISS en mag null zijn. Dit obecjt is dus nog niet opgenomen in dit schema + doelgroep: kennisartikelMetadata?.doelgroep?.replace('_', '-') as 'eu-burger' | 'eu-bedrijf', + afdelingen: kennisartikelMetadata?.afdelingen, + beschikbareTalen: [attributes?.locale], + vertalingen: [ + { + bewijs: contentBlock?.kennisartikelCategorie === 'bewijs' ? contentBlock.content : undefined, + bezwaarEnBeroep: contentBlock?.kennisartikelCategorie === 'bezwaar' ? contentBlock.content : undefined, + contact: contentBlock?.kennisartikelCategorie === 'contact' ? contentBlock.content : undefined, + datumWijziging: attributes.updatedAt, + deskMemo: contentBlock?.kennisartikelCategorie === 'deskMemo' ? contentBlock.content : undefined, + kostenEnBetaalmethoden: contentBlock?.kennisartikelCategorie === 'kosten' ? contentBlock.content : undefined, + notice: contentBlock?.kennisartikelCategorie === 'bijzonderheden' ? contentBlock.content : undefined, + procedureBeschrijving: contentBlock?.kennisartikelCategorie === 'aanvraag' ? contentBlock.content : undefined, + taal: attributes?.locale, + tekst: contentBlock?.kennisartikelCategorie === 'inleiding' ? contentBlock.content : undefined, + titel: attributes?.title, + trefwoorden, + uitersteTermijn: contentBlock?.kennisartikelCategorie === 'termijn' ? contentBlock.content : undefined, + vereisten: contentBlock?.kennisartikelCategorie === 'voorwaarden' ? contentBlock.content : undefined, + wtdBijGeenReactie: + contentBlock?.kennisartikelCategorie === 'wat_te_doen_bij_geen_reactie' ? contentBlock.content : undefined, + }, + ], + }; + return data; +}; diff --git a/apps/overige-objecten-api/src/utils/getPaginatedResponse.ts b/apps/overige-objecten-api/src/utils/getPaginatedResponse.ts new file mode 100644 index 000000000..f610929b2 --- /dev/null +++ b/apps/overige-objecten-api/src/utils/getPaginatedResponse.ts @@ -0,0 +1,35 @@ +import type { Request } from 'express'; +import type { Products } from '../strapi-product-type'; + +export const getPaginatedResponse = async (req: Request, strapiResponse: Products) => { + if (!strapiResponse?.meta?.pagination) return {}; + // Get pagination info from Strapi response + const { page, pageSize, pageCount, total } = strapiResponse.meta.pagination; + + // Get current URL and base path + const baseUrl = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`; + + // Get existing query parameters + const queryParams = new URLSearchParams(req.query as any); + + // Generate URL with updated page + const generatePageUrl = (pageNum: number) => { + queryParams.set('page', pageNum.toString()); + queryParams.set('pageSize', pageSize.toString()); + return `${baseUrl}?${queryParams.toString()}`; + }; + + // Build pagination object + const pagination = { + page, + pageSize, + count: pageCount, + total, + next: page < pageCount ? generatePageUrl(page + 1) : null, + previous: page > 1 ? generatePageUrl(page - 1) : null, + }; + + return { + ...pagination, + }; +}; diff --git a/apps/overige-objecten-api/src/utils/getTheServerURL.ts b/apps/overige-objecten-api/src/utils/getTheServerURL.ts new file mode 100644 index 000000000..b30d199cf --- /dev/null +++ b/apps/overige-objecten-api/src/utils/getTheServerURL.ts @@ -0,0 +1,2 @@ +import type { Request } from 'express'; +export const getTheServerURL = (request: Request) => `${request.protocol}://${request.get('host')}`; diff --git a/apps/overige-objecten-api/src/utils/index.ts b/apps/overige-objecten-api/src/utils/index.ts new file mode 100644 index 000000000..90f3f58ff --- /dev/null +++ b/apps/overige-objecten-api/src/utils/index.ts @@ -0,0 +1,9 @@ +export { envAvailability } from './envAvailability'; +export { ErrorHandler } from './errorHandler'; +export { fetchData } from './fetchData'; +export { getTheServerURL } from './getTheServerURL'; +export { vacData } from './vacData'; +export type { VACType } from './vacData'; +export { generateKennisartikelObject } from './generateKennisartikelObject'; +export { readFile } from './readFile'; +export { getPaginatedResponse } from './getPaginatedResponse'; diff --git a/apps/overige-objecten-api/src/utils/readFile.ts b/apps/overige-objecten-api/src/utils/readFile.ts new file mode 100644 index 000000000..f353f92bb --- /dev/null +++ b/apps/overige-objecten-api/src/utils/readFile.ts @@ -0,0 +1,11 @@ +import fs from 'node:fs'; + +export const readFile = (filePath: string) => { + try { + return fs.readFileSync(filePath, 'utf8'); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + return undefined; + } +}; diff --git a/apps/overige-objecten-api/src/utils/vacData.ts b/apps/overige-objecten-api/src/utils/vacData.ts new file mode 100644 index 000000000..b3992b1f6 --- /dev/null +++ b/apps/overige-objecten-api/src/utils/vacData.ts @@ -0,0 +1,31 @@ +import { components } from '../types/openapi'; + +export type VACType = components['schemas']['vac'] & { uuid: string }; +export const vacData = ({ url }: any): VACType[] => [ + { + url, + uuid: 'a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6', + vraag: 'Wat is het proces om een paspoort aan te vragen?', + status: 'actief', + antwoord: + 'U moet een afspraak maken bij de gemeente, uw identiteitsbewijs meenemen en een recente pasfoto aanleveren.', + doelgroep: 'eu-burger', + }, + { + url, + uuid: 'b2c3d4e5-f6g7-8h9i-0j1k-l2m3n4o5p6q7', + vraag: 'Hoe kan ik een rijbewijs aanvragen?', + status: 'actief', + antwoord: + 'Voor het aanvragen van een rijbewijs moet u een aanvraagformulier invullen en uw identiteitsbewijs meenemen naar het gemeentehuis.', + doelgroep: 'eu-burger', + }, + { + url, + uuid: 'c3d4e5f6-g7h8-9i0j-k1l2-m3n4o5p6q7r8', + vraag: 'Wat moet ik doen bij verhuizing?', + status: 'actief', + antwoord: 'Bij verhuizing moet u zich binnen 5 dagen inschrijven op uw nieuwe adres bij de gemeente.', + doelgroep: 'eu-burger', + }, +]; diff --git a/apps/overige-objecten-api/tsconfig.json b/apps/overige-objecten-api/tsconfig.json new file mode 100644 index 000000000..555a5868c --- /dev/null +++ b/apps/overige-objecten-api/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "declaration": true, + "module": "commonjs", + "outDir": "dist", + "lib": ["ES2021", "DOM"] + }, + "include": ["src/**/*", "./jest.config.ts"], + "exclude": ["node_modules", "**/*.test.ts", "./src/types/openapi.ts"] +} diff --git a/apps/overige-objecten-api/tsconfig.test.json b/apps/overige-objecten-api/tsconfig.test.json new file mode 100644 index 000000000..810d15c1b --- /dev/null +++ b/apps/overige-objecten-api/tsconfig.test.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2016", + "module": "ES6", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "strict": true, + "types": ["jest"], + "resolveJsonModule": true + }, + "include": ["**/*.test.tsx", "**/*.test.ts", "tests"] +} diff --git a/apps/pdc-dashboard/config/sync/admin-role.strapi-super-admin.json b/apps/pdc-dashboard/config/sync/admin-role.strapi-super-admin.json index cdb2a7b91..eed5b02fb 100644 --- a/apps/pdc-dashboard/config/sync/admin-role.strapi-super-admin.json +++ b/apps/pdc-dashboard/config/sync/admin-role.strapi-super-admin.json @@ -366,7 +366,17 @@ "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.onderwerp", "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.uitleg", "productencatalogus", - "uuid" + "uuid", + "kennisartikelMetadata.doelgroep", + "kennisartikelMetadata.productAanwezig", + "kennisartikelMetadata.productValtOnder", + "kennisartikelMetadata.afdelingen.afdelingId", + "kennisartikelMetadata.afdelingen.afdelingnaam", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsIdentifier", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsPrefLabel", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsEndDate", + "kennisartikelMetadata.uuid", + "kennisartikelMetadata.upnUri" ], "locales": ["en", "nl"] }, @@ -438,7 +448,17 @@ "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.onderwerp", "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.uitleg", "productencatalogus", - "uuid" + "uuid", + "kennisartikelMetadata.doelgroep", + "kennisartikelMetadata.productAanwezig", + "kennisartikelMetadata.productValtOnder", + "kennisartikelMetadata.afdelingen.afdelingId", + "kennisartikelMetadata.afdelingen.afdelingnaam", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsIdentifier", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsPrefLabel", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsEndDate", + "kennisartikelMetadata.uuid", + "kennisartikelMetadata.upnUri" ], "locales": ["en", "nl"] }, @@ -492,7 +512,17 @@ "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.onderwerp", "pdc_metadata.cimPdcProductBeschrijving.cimPdcProductAspectBeschrijving.uitleg", "productencatalogus", - "uuid" + "uuid", + "kennisartikelMetadata.doelgroep", + "kennisartikelMetadata.productAanwezig", + "kennisartikelMetadata.productValtOnder", + "kennisartikelMetadata.afdelingen.afdelingId", + "kennisartikelMetadata.afdelingen.afdelingnaam", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsIdentifier", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsPrefLabel", + "kennisartikelMetadata.verantwoordelijkeOrganisatie.owmsEndDate", + "kennisartikelMetadata.uuid", + "kennisartikelMetadata.upnUri" ], "locales": ["en", "nl"] }, diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.afdelingen.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.afdelingen.json new file mode 100644 index 000000000..45d85940c --- /dev/null +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.afdelingen.json @@ -0,0 +1,72 @@ +{ + "key": "plugin_content_manager_configuration_components::components.afdelingen", + "value": { + "settings": { + "bulkable": true, + "filterable": true, + "searchable": true, + "pageSize": 10, + "mainField": "afdelingnaam", + "defaultSortBy": "afdelingId", + "defaultSortOrder": "ASC" + }, + "metadatas": { + "id": { + "edit": {}, + "list": { + "label": "id", + "searchable": false, + "sortable": false + } + }, + "afdelingId": { + "edit": { + "label": "Identifier Afdeling", + "description": "De unieke identifier van de afdeling.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "afdelingId", + "searchable": true, + "sortable": true + } + }, + "afdelingnaam": { + "edit": { + "label": "Naam Afdeling", + "description": "De naam van de afdeling.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "afdelingnaam", + "searchable": true, + "sortable": true + } + } + }, + "layouts": { + "list": ["id", "afdelingId", "afdelingnaam"], + "edit": [ + [ + { + "name": "afdelingId", + "size": 6 + }, + { + "name": "afdelingnaam", + "size": 6 + } + ] + ] + }, + "uid": "components.afdelingen", + "isComponent": true + }, + "type": "object", + "environment": null, + "tag": null +} diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.kennisartikel.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.kennisartikel.json new file mode 100644 index 000000000..9bb1f8a3d --- /dev/null +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.kennisartikel.json @@ -0,0 +1,170 @@ +{ + "key": "plugin_content_manager_configuration_components::components.kennisartikel", + "value": { + "settings": { + "bulkable": true, + "filterable": true, + "searchable": true, + "pageSize": 10, + "mainField": "productValtOnder", + "defaultSortBy": "productValtOnder", + "defaultSortOrder": "ASC" + }, + "metadatas": { + "id": { + "edit": {}, + "list": { + "label": "id", + "searchable": false, + "sortable": false + } + }, + "doelgroep": { + "edit": { + "label": "Doelgroep", + "description": "De doelgroep van dit product.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "doelgroep", + "searchable": true, + "sortable": true + } + }, + "productAanwezig": { + "edit": { + "label": "Product Aanwezig", + "description": "Een boolean die aangeeft of de organisatie dit product levert of niet.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "productAanwezig", + "searchable": true, + "sortable": true + } + }, + "productValtOnder": { + "edit": { + "label": "Product Valt Onder", + "description": "Als een product valt onder een ander product, dan staat deze hier vermeld.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "productValtOnder", + "searchable": true, + "sortable": true + } + }, + "afdelingen": { + "edit": { + "label": "Afdeling of Afdelingen", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "afdelingen", + "searchable": false, + "sortable": false + } + }, + "verantwoordelijkeOrganisatie": { + "edit": { + "label": "De Leverende Organisatie", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "verantwoordelijkeOrganisatie", + "searchable": false, + "sortable": false + } + }, + "uuid": { + "edit": { + "label": "UUID", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "uuid", + "searchable": true, + "sortable": true + } + }, + "upnUri": { + "edit": { + "label": "UPN URI", + "description": "De UPN URI van het specifieke product.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "upnUri", + "searchable": true, + "sortable": true + } + } + }, + "layouts": { + "list": ["id", "doelgroep", "productAanwezig", "productValtOnder"], + "edit": [ + [ + { + "name": "doelgroep", + "size": 6 + }, + { + "name": "productAanwezig", + "size": 6 + } + ], + [ + { + "name": "productValtOnder", + "size": 6 + }, + { + "name": "upnUri", + "size": 6 + } + ], + [ + { + "name": "afdelingen", + "size": 12 + } + ], + [ + { + "name": "verantwoordelijkeOrganisatie", + "size": 12 + } + ], + [ + { + "name": "uuid", + "size": 6 + } + ] + ] + }, + "uid": "components.kennisartikel", + "isComponent": true + }, + "type": "object", + "environment": null, + "tag": null +} diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.verantwoordelijke-organisatie.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.verantwoordelijke-organisatie.json new file mode 100644 index 000000000..3d4d450db --- /dev/null +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_components##components.verantwoordelijke-organisatie.json @@ -0,0 +1,92 @@ +{ + "key": "plugin_content_manager_configuration_components::components.verantwoordelijke-organisatie", + "value": { + "settings": { + "bulkable": true, + "filterable": true, + "searchable": true, + "pageSize": 10, + "mainField": "owmsIdentifier", + "defaultSortBy": "owmsIdentifier", + "defaultSortOrder": "ASC" + }, + "metadatas": { + "id": { + "edit": {}, + "list": { + "label": "id", + "searchable": false, + "sortable": false + } + }, + "owmsIdentifier": { + "edit": { + "label": "OWMS Identifier", + "description": "De OWMS Identifier van de hoofdorganisatie van deze lokale overheid.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "owmsIdentifier", + "searchable": true, + "sortable": true + } + }, + "owmsPrefLabel": { + "edit": { + "label": "OWMS label", + "description": "OWMS label van de hoofdorganisatie van deze lokale overheid.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "owmsPrefLabel", + "searchable": true, + "sortable": true + } + }, + "owmsEndDate": { + "edit": { + "label": "Einddatum", + "description": "De einddatum, zoals gevonden in het OWMS-model.", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "owmsEndDate", + "searchable": true, + "sortable": true + } + } + }, + "layouts": { + "list": ["id", "owmsIdentifier", "owmsPrefLabel", "owmsEndDate"], + "edit": [ + [ + { + "name": "owmsIdentifier", + "size": 6 + }, + { + "name": "owmsPrefLabel", + "size": 6 + } + ], + [ + { + "name": "owmsEndDate", + "size": 6 + } + ] + ] + }, + "uid": "components.verantwoordelijke-organisatie", + "isComponent": true + }, + "type": "object", + "environment": null, + "tag": null +} diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##api##product.product.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##api##product.product.json index 06e42d686..7f6af1670 100644 --- a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##api##product.product.json +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##api##product.product.json @@ -191,6 +191,20 @@ "sortable": true } }, + "kennisartikelMetadata": { + "edit": { + "label": "Kennisartikel Metadata", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "kennisartikelMetadata", + "searchable": false, + "sortable": false + } + }, "createdAt": { "edit": { "label": "createdAt", @@ -251,7 +265,6 @@ } }, "layouts": { - "list": ["title", "metaTags", "price", "uuid"], "edit": [ [ { @@ -301,6 +314,12 @@ "size": 6 } ], + [ + { + "name": "kennisartikelMetadata", + "size": 12 + } + ], [ { "name": "pdc_subcategories", @@ -325,7 +344,8 @@ "size": 6 } ] - ] + ], + "list": ["title", "metaTags", "price", "uuid"] } }, "type": "object", diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release-action.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release-action.json new file mode 100644 index 000000000..e9b2ca03b --- /dev/null +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release-action.json @@ -0,0 +1,188 @@ +{ + "key": "plugin_content_manager_configuration_content_types::plugin::content-releases.release-action", + "value": { + "settings": { + "bulkable": true, + "filterable": true, + "searchable": true, + "pageSize": 10, + "mainField": "contentType", + "defaultSortBy": "contentType", + "defaultSortOrder": "ASC" + }, + "metadatas": { + "id": { + "edit": {}, + "list": { + "label": "id", + "searchable": true, + "sortable": true + } + }, + "type": { + "edit": { + "label": "type", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "type", + "searchable": true, + "sortable": true + } + }, + "contentType": { + "edit": { + "label": "contentType", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "contentType", + "searchable": true, + "sortable": true + } + }, + "locale": { + "edit": { + "label": "locale", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "locale", + "searchable": true, + "sortable": true + } + }, + "release": { + "edit": { + "label": "release", + "description": "", + "placeholder": "", + "visible": true, + "editable": true, + "mainField": "name" + }, + "list": { + "label": "release", + "searchable": true, + "sortable": true + } + }, + "isEntryValid": { + "edit": { + "label": "isEntryValid", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "isEntryValid", + "searchable": true, + "sortable": true + } + }, + "createdAt": { + "edit": { + "label": "createdAt", + "description": "", + "placeholder": "", + "visible": false, + "editable": true + }, + "list": { + "label": "createdAt", + "searchable": true, + "sortable": true + } + }, + "updatedAt": { + "edit": { + "label": "updatedAt", + "description": "", + "placeholder": "", + "visible": false, + "editable": true + }, + "list": { + "label": "updatedAt", + "searchable": true, + "sortable": true + } + }, + "createdBy": { + "edit": { + "label": "createdBy", + "description": "", + "placeholder": "", + "visible": false, + "editable": true, + "mainField": "firstname" + }, + "list": { + "label": "createdBy", + "searchable": true, + "sortable": true + } + }, + "updatedBy": { + "edit": { + "label": "updatedBy", + "description": "", + "placeholder": "", + "visible": false, + "editable": true, + "mainField": "firstname" + }, + "list": { + "label": "updatedBy", + "searchable": true, + "sortable": true + } + } + }, + "layouts": { + "list": ["id", "type", "contentType", "locale"], + "edit": [ + [ + { + "name": "type", + "size": 6 + }, + { + "name": "contentType", + "size": 6 + } + ], + [ + { + "name": "locale", + "size": 6 + }, + { + "name": "release", + "size": 6 + } + ], + [ + { + "name": "isEntryValid", + "size": 4 + } + ] + ] + }, + "uid": "plugin::content-releases.release-action" + }, + "type": "object", + "environment": null, + "tag": null +} diff --git a/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release.json b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release.json new file mode 100644 index 000000000..ac0ba087f --- /dev/null +++ b/apps/pdc-dashboard/config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##content-releases.release.json @@ -0,0 +1,206 @@ +{ + "key": "plugin_content_manager_configuration_content_types::plugin::content-releases.release", + "value": { + "settings": { + "bulkable": true, + "filterable": true, + "searchable": true, + "pageSize": 10, + "mainField": "name", + "defaultSortBy": "name", + "defaultSortOrder": "ASC" + }, + "metadatas": { + "id": { + "edit": {}, + "list": { + "label": "id", + "searchable": true, + "sortable": true + } + }, + "name": { + "edit": { + "label": "name", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "name", + "searchable": true, + "sortable": true + } + }, + "releasedAt": { + "edit": { + "label": "releasedAt", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "releasedAt", + "searchable": true, + "sortable": true + } + }, + "scheduledAt": { + "edit": { + "label": "scheduledAt", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "scheduledAt", + "searchable": true, + "sortable": true + } + }, + "timezone": { + "edit": { + "label": "timezone", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "timezone", + "searchable": true, + "sortable": true + } + }, + "status": { + "edit": { + "label": "status", + "description": "", + "placeholder": "", + "visible": true, + "editable": true + }, + "list": { + "label": "status", + "searchable": true, + "sortable": true + } + }, + "actions": { + "edit": { + "label": "actions", + "description": "", + "placeholder": "", + "visible": true, + "editable": true, + "mainField": "contentType" + }, + "list": { + "label": "actions", + "searchable": false, + "sortable": false + } + }, + "createdAt": { + "edit": { + "label": "createdAt", + "description": "", + "placeholder": "", + "visible": false, + "editable": true + }, + "list": { + "label": "createdAt", + "searchable": true, + "sortable": true + } + }, + "updatedAt": { + "edit": { + "label": "updatedAt", + "description": "", + "placeholder": "", + "visible": false, + "editable": true + }, + "list": { + "label": "updatedAt", + "searchable": true, + "sortable": true + } + }, + "createdBy": { + "edit": { + "label": "createdBy", + "description": "", + "placeholder": "", + "visible": false, + "editable": true, + "mainField": "firstname" + }, + "list": { + "label": "createdBy", + "searchable": true, + "sortable": true + } + }, + "updatedBy": { + "edit": { + "label": "updatedBy", + "description": "", + "placeholder": "", + "visible": false, + "editable": true, + "mainField": "firstname" + }, + "list": { + "label": "updatedBy", + "searchable": true, + "sortable": true + } + } + }, + "layouts": { + "list": ["id", "name", "releasedAt", "scheduledAt"], + "edit": [ + [ + { + "name": "name", + "size": 6 + }, + { + "name": "releasedAt", + "size": 6 + } + ], + [ + { + "name": "scheduledAt", + "size": 6 + }, + { + "name": "timezone", + "size": 6 + } + ], + [ + { + "name": "status", + "size": 6 + }, + { + "name": "actions", + "size": 6 + } + ] + ] + }, + "uid": "plugin::content-releases.release" + }, + "type": "object", + "environment": null, + "tag": null +} diff --git a/apps/pdc-dashboard/src/api/product/content-types/product/schema.json b/apps/pdc-dashboard/src/api/product/content-types/product/schema.json index 8ec0f4226..06080bff0 100644 --- a/apps/pdc-dashboard/src/api/product/content-types/product/schema.json +++ b/apps/pdc-dashboard/src/api/product/content-types/product/schema.json @@ -131,6 +131,16 @@ }, "type": "customField", "customField": "plugin::uuid-field.uuid-field" + }, + "kennisartikelMetadata": { + "type": "component", + "repeatable": false, + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "component": "components.kennisartikel" } } } diff --git a/apps/pdc-dashboard/src/components/components/afdelingen.json b/apps/pdc-dashboard/src/components/components/afdelingen.json new file mode 100644 index 000000000..01695b6e1 --- /dev/null +++ b/apps/pdc-dashboard/src/components/components/afdelingen.json @@ -0,0 +1,19 @@ +{ + "collectionName": "components_components_afdelingens", + "info": { + "displayName": "afdelingen", + "description": "" + }, + "options": {}, + "attributes": { + "afdelingId": { + "type": "customField", + "customField": "plugin::uuid-field.uuid-field", + "required": true + }, + "afdelingnaam": { + "type": "string", + "required": true + } + } +} diff --git a/apps/pdc-dashboard/src/components/components/kennisartikel.json b/apps/pdc-dashboard/src/components/components/kennisartikel.json new file mode 100644 index 000000000..a1806f8f5 --- /dev/null +++ b/apps/pdc-dashboard/src/components/components/kennisartikel.json @@ -0,0 +1,41 @@ +{ + "collectionName": "components_components_kennisartikels", + "info": { + "displayName": "Kennisartikel", + "description": "" + }, + "options": {}, + "attributes": { + "doelgroep": { + "type": "enumeration", + "enum": ["eu-burger", "eu-bedrijf"], + "required": true + }, + "productAanwezig": { + "type": "boolean", + "required": true + }, + "productValtOnder": { + "type": "string" + }, + "afdelingen": { + "type": "component", + "repeatable": true, + "component": "components.afdelingen" + }, + "verantwoordelijkeOrganisatie": { + "type": "component", + "repeatable": false, + "component": "components.verantwoordelijke-organisatie" + }, + "uuid": { + "type": "customField", + "customField": "plugin::uuid-field.uuid-field" + }, + "upnUri": { + "type": "customField", + "customField": "plugin::uniform-product-name.uniform-product-name", + "required": true + } + } +} diff --git a/apps/pdc-dashboard/src/components/components/verantwoordelijke-organisatie.json b/apps/pdc-dashboard/src/components/components/verantwoordelijke-organisatie.json new file mode 100644 index 000000000..8ca4ab08c --- /dev/null +++ b/apps/pdc-dashboard/src/components/components/verantwoordelijke-organisatie.json @@ -0,0 +1,24 @@ +{ + "collectionName": "components_components_verantwoordelijke_organisaties", + "info": { + "displayName": "Verantwoordelijke Organisatie", + "description": "" + }, + "options": {}, + "attributes": { + "owmsIdentifier": { + "type": "customField", + "customField": "plugin::gemeente-select.gemeente", + "default": "http://standaarden.overheid.nl/owms/terms/Utrecht_(gemeente)", + "required": true + }, + "owmsPrefLabel": { + "type": "string", + "default": "Gemeente Utrecht" + }, + "owmsEndDate": { + "type": "datetime", + "required": true + } + } +} diff --git a/apps/strapi.frameless.io/docs/developers/overige-objecten-api.md b/apps/strapi.frameless.io/docs/developers/overige-objecten-api.md new file mode 100644 index 000000000..5749568dc --- /dev/null +++ b/apps/strapi.frameless.io/docs/developers/overige-objecten-api.md @@ -0,0 +1,80 @@ +# overige-objecten-api + +The **overige-objecten-api** is a microservice built with Node.js that maps certain PDC product fields to the **kennisartikel** and **VAC** fields. + +## Features + +- OpenAPI documentation. +- Request validation against **kennisartikel** and **VAC** schemas, ensuring that each field has the correct type. +- Automatic generation of TypeScript interfaces for **kennisartikel** and **VAC** schemas. +- Provides clear and descriptive error messages in JSON format when validation fails. + +## Getting Started + +You can run the server locally in two ways: + +> Note: Before proceeding, ensure you generate an API token from the Strapi dashboard by following these steps: + +1. Go to the PDC Strapi dashboard. +2. Navigate to Settings > API Tokens and select Create New API Token. +3. Allow access only to the product API by checking the boxes for find and findOne. +4. Once you've configured the permissions, click Save. + +After generating the token, include it in your request headers as a Bearer token. Here's an example: + +```ts +Authorization: Bearer API_TOKEN +``` + +### 1. Start with Docker + +Ensure that you have the following environment variables in the `.pdc.prod.env` file before starting: + +```shell +OVERIGE_OBJECTEN_API_PORT=4001 +OVERIGE_OBJECTEN_API_CORS=http://localhost:3000 # If using multiple domains, separate them with a comma (e.g., 'http://localhost:3000, http://localhost:3001'). +``` + +To start the service, open a terminal in the project root and run: + +```shell +cd bin && bash ./deploy.sh pdc-dashboard prod up --build +``` + +### 2. Start without Docker + +Create an .env file in the apps/overige-objecten-api directory with the following environment variables: + +```shell +STRAPI_PRIVATE_URL=http://127.0.0.1:1337/ +OVERIGE_OBJECTEN_API_PORT=4001 +OVERIGE_OBJECTEN_API_CORS='' # Required for client-side application +``` + +Then, follow these steps: + +1. Build the project: + + ```shell + yarn build + + ``` + +2. Start the PDC-dashboard server: + + ```shell + yarn workspace @frameless/pdc-dashboard dev + + ``` + +3. Start the overige-objecten-api server: + + ```shell + yarn workspace @frameless/overige-objecten-api dev + + ``` + +## API Endpoints + +- ObjectsAPI: [http://localhost:4001/api/v1/objects](http://localhost:4001/api/v1/objects) +- OpenAPI specification: [http://localhost:4001/api/v1/openapi.json](http://localhost:4001/api/v1/openapi.json) diff --git a/apps/strapi.frameless.io/docs/developers/readme.md b/apps/strapi.frameless.io/docs/developers/readme.md index 823afd326..e60c2e50e 100644 --- a/apps/strapi.frameless.io/docs/developers/readme.md +++ b/apps/strapi.frameless.io/docs/developers/readme.md @@ -135,49 +135,51 @@ To run the application as a Docker container on your Mac, follow these steps: -| Variable name | Description | Type | Default Value | Application | Note | -| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `ADMIN_JWT_SECRET` | Secret for signing JWTs for the Admin panel | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `APP_KEYS` | The secret key for session cookie signing | `String` | | strapi-dashboard | [Server configuration](https://docs.strapi.io/dev-docs/configurations/server#available-options) | -| `API_TOKEN_SALT` | Salt for generating API tokens | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `CSP_CONNECT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `connect-src` | | | | | -| `CSP_FONT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `font-src` | | | | | -| `CSP_FORM_ACTION_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `form-action` | | | | | -| `CSP_FRAME_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `frame-src` | | | | | -| `CSP_IMG_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `img-src` | | | | | -| `CSP_SCRIPT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `script-src` | | | | | -| `CSP_STYLE_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `style-src` | | | | | -| `CSP_WORKER_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `worker-src` | | | | | -| `DATABASE_CLIENT` | Database client to use | `String` | sqlite | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_HOST` | Database host | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_NAME` | Database name | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_PASSWORD` | Database password | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_PORT` | Database port | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_SSL` | For SSL database connection. Use an object to pass certificate files as strings. | `Boolean` or `Object` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `DATABASE_USERNAME` | Database username | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | -| `FRONTEND_PUBLIC_URL` | | `String` | | strapi-dashboard | | -| `HOST` | | `Number` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `I18N_DEBUG` | When `I18N_DEBUG` is set, the `i18next` package will log debug information. | `String` | | frontend | | -| `JWT_SECRET` | Secret for signing JWTs for the Users-Permissions plugin | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | -| `MATOMO_HOST` | PDC ONLY / Optional: URL for Matomo | `URL` | | FRONTEND | | -| `MATOMO_SITE_ID` | PDC ONLY / Optional: container ID for Matomo | `String` | | FRONTEND | | -| `NODE_ENV` | | `production` \| `development` | | | | -| `OPEN_FORMS_API_TOKEN` | PDC ONLY: A Token for Open Forms API | `String` | | strapi-dashboard / frontend | | -| `OPEN_FORMS_API_URL` | PDC ONLY: URL for Open Forms API (usually the origin + /api/v2) | `String` | | frontend / strapi-dashboard | | -| `OPEN_FORMS_CSS_URL` | PDC ONLY / Optional: URL for Open Forms CSS | `String` | | frontend | | -| `OPEN_FORMS_SDK_URL` | PDC ONLY / Optional : URL for Open Forms SDK | `String` | | frontend | | -| `PANDOSEARCH_API_URL` | PDC ONLY | `String` | | frontend | | -| `PGADMIN_DEFAULT_EMAIL` | | `String` | | Database | | -| `PGADMIN_DEFAULT_PASSWORD` | | `String` | | Database | | -| `PORT` | Port on which the server should be running. | `Number` | 1337 | strapi-dashboard | | -| `PREVIEW_SECRET_TOKEN` | The secret used for the Strapi preview plugin should have the same value for both the frontend and the Strapi dashboard. | `String` | | strapi-dashboard frontend | | -| `STRAPI_PRIVATE_URL` | | `String` | | frontend | | -| `STRAPI_PUBLIC_URL` | | `URL` | | Frontend | The Strapi dashboard URL, e.g.,`http://localhost:1337/` | -| `TRANSFER_TOKEN_SALT` | Salt for generating Transfer tokens. If no transfer token salt is defined, transfer features will be disabled. | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) Secrets can be generated manually by running `node -p "require('crypto').randomBytes(48).toString('base64');"` | -| `OGONE_PAYMENT_SERVICE_URL` | PDC ONLY: URL for Open Forms/Payment | `URL` | | Frontend | | -| `SURVEY_RUN_GUID` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | -| `SURVEY_RUN_APIKEY` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | -| `SURVEY_RUN_URL` | PDC ONLY: Used for the Survey service | `URL` | | Fronend | | +| Variable name | Description | Type | Default Value | Application | Note | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `ADMIN_JWT_SECRET` | Secret for signing JWTs for the Admin panel | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `APP_KEYS` | The secret key for session cookie signing | `String` | | strapi-dashboard | [Server configuration](https://docs.strapi.io/dev-docs/configurations/server#available-options) | +| `API_TOKEN_SALT` | Salt for generating API tokens | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `CSP_CONNECT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `connect-src` | | | | | +| `CSP_FONT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `font-src` | | | | | +| `CSP_FORM_ACTION_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `form-action` | | | | | +| `CSP_FRAME_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `frame-src` | | | | | +| `CSP_IMG_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `img-src` | | | | | +| `CSP_SCRIPT_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `script-src` | | | | | +| `CSP_STYLE_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `style-src` | | | | | +| `CSP_WORKER_SRC_URLS` | Space-separated list of URLs to allow in `Content-Security-Policy` for `worker-src` | | | | | +| `DATABASE_CLIENT` | Database client to use | `String` | sqlite | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_HOST` | Database host | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_NAME` | Database name | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_PASSWORD` | Database password | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_PORT` | Database port | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_SSL` | For SSL database connection. Use an object to pass certificate files as strings. | `Boolean` or `Object` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `DATABASE_USERNAME` | Database username | `String` | | strapi-dashboard | [Reference to Strapi Database docs](https://docs.strapi.io/cloud/advanced/database) | +| `FRONTEND_PUBLIC_URL` | | `String` | | strapi-dashboard | | +| `HOST` | | `Number` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `I18N_DEBUG` | When `I18N_DEBUG` is set, the `i18next` package will log debug information. | `String` | | frontend | | +| `JWT_SECRET` | Secret for signing JWTs for the Users-Permissions plugin | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) | +| `MATOMO_HOST` | PDC ONLY / Optional: URL for Matomo | `URL` | | FRONTEND | | +| `MATOMO_SITE_ID` | PDC ONLY / Optional: container ID for Matomo | `String` | | FRONTEND | | +| `NODE_ENV` | | `production` \| `development` | | | | +| `OPEN_FORMS_API_TOKEN` | PDC ONLY: A Token for Open Forms API | `String` | | strapi-dashboard / frontend | | +| `OPEN_FORMS_API_URL` | PDC ONLY: URL for Open Forms API (usually the origin + /api/v2) | `String` | | frontend / strapi-dashboard | | +| `OPEN_FORMS_CSS_URL` | PDC ONLY / Optional: URL for Open Forms CSS | `String` | | frontend | | +| `OPEN_FORMS_SDK_URL` | PDC ONLY / Optional : URL for Open Forms SDK | `String` | | frontend | | +| `PANDOSEARCH_API_URL` | PDC ONLY | `String` | | frontend | | +| `PGADMIN_DEFAULT_EMAIL` | | `String` | | Database | | +| `PGADMIN_DEFAULT_PASSWORD` | | `String` | | Database | | +| `PORT` | Port on which the server should be running. | `Number` | 1337 | strapi-dashboard | | +| `PREVIEW_SECRET_TOKEN` | The secret used for the Strapi preview plugin should have the same value for both the frontend and the Strapi dashboard. | `String` | | strapi-dashboard frontend | | +| `STRAPI_PRIVATE_URL` | | `String` | | frontend | | +| `STRAPI_PUBLIC_URL` | | `URL` | | Frontend | The Strapi dashboard URL, e.g.,`http://localhost:1337/` | +| `TRANSFER_TOKEN_SALT` | Salt for generating Transfer tokens. If no transfer token salt is defined, transfer features will be disabled. | `String` | | strapi-dashboard | [Admin panel configuration](https://docs.strapi.io/dev-docs/configurations/admin-panel#available-options) Secrets can be generated manually by running `node -p "require('crypto').randomBytes(48).toString('base64');"` | +| `OGONE_PAYMENT_SERVICE_URL` | PDC ONLY: URL for Open Forms/Payment | `URL` | | Frontend | | +| `SURVEY_RUN_GUID` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | +| `SURVEY_RUN_APIKEY` | PDC ONLY: Used for the Survey service | `String` | | Fronend | | +| `SURVEY_RUN_URL` | PDC ONLY: Used for the Survey service | `URL` | | Fronend | | +| OVERIGE_OBJECTEN_API_PORT | | Number | | Overige-objecten-api | | +| OVERIGE_OBJECTEN_API_CORS | This environment variable manages CORS (Cross-Origin Resource Sharing), allowing the server to specify permitted external origins for resource loading. For multiple domains, separate them with a comma (e.g., '[http://localhost:3000](http://localhost:3000), [http://localhost:3001](http://localhost:3001)'). | URL | | Overige-objecten-api | | ## Start the server without Docker diff --git a/docker-compose.pdc.dev.yml b/docker-compose.pdc.dev.yml index 6089d4480..a69f8ab73 100644 --- a/docker-compose.pdc.dev.yml +++ b/docker-compose.pdc.dev.yml @@ -91,7 +91,7 @@ services: container_name: pdc_samenwerkende-catalogi build: context: . - dockerfile: Dockerfile.prod + dockerfile: Dockerfile.dev restart: unless-stopped command: npm run start:pdc-sc environment: @@ -104,6 +104,26 @@ services: depends_on: - pdc_strapi - pdc_strapi_db + overige-objecten-api: + container_name: overige-objecten-api + build: + context: . + dockerfile: Dockerfile.dev + restart: unless-stopped + volumes: + - ./apps/overige-objecten-api/src/docs:/opt/app/apps/overige-objecten-api/dist/src/docs + command: yarn workspace @frameless/overige-objecten-api start + environment: + STRAPI_PRIVATE_URL: ${STRAPI_PRIVATE_URL} + OVERIGE_OBJECTEN_API_PORT: ${OVERIGE_OBJECTEN_API_PORT} + OVERIGE_OBJECTEN_API_CORS: ${OVERIGE_OBJECTEN_API_CORS} + ports: + - "4001:4001" + networks: + - pdc_strapi_network + depends_on: + - pdc_strapi + - pdc_strapi_db pdc_strapi_db: container_name: pdc_strapi_db platform: linux/amd64 #for platform error on Apple M1 chips diff --git a/docker-compose.pdc.prod.yml b/docker-compose.pdc.prod.yml index f2a0f0115..2330512cd 100644 --- a/docker-compose.pdc.prod.yml +++ b/docker-compose.pdc.prod.yml @@ -91,6 +91,26 @@ services: depends_on: - pdc_strapi - pdc_strapi_db + overige-objecten-api: + container_name: overige-objecten-api + build: + context: . + dockerfile: Dockerfile.prod + restart: unless-stopped + volumes: + - ./apps/overige-objecten-api/src/docs:/opt/app/apps/overige-objecten-api/dist/src/docs + command: yarn start:overige-objecten-api + environment: + STRAPI_PRIVATE_URL: ${STRAPI_PRIVATE_URL} + OVERIGE_OBJECTEN_API_PORT: ${OVERIGE_OBJECTEN_API_PORT} + OVERIGE_OBJECTEN_API_CORS: ${OVERIGE_OBJECTEN_API_CORS} + ports: + - "4001:4001" + networks: + - pdc_strapi_network + depends_on: + - pdc_strapi + - pdc_strapi_db pdc_strapi_db: container_name: pdc_strapi_db platform: linux/amd64 #for platform error on Apple M1 chips diff --git a/package.json b/package.json index 446ff576c..ca98bb79d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "start:kennisbank-frontend": "npm run --workspace @frameless/kennisbank-frontend start", "start:kennisbank-dashboard": "npm run --workspace @frameless/kennisbank-dashboard start", "start:pdc-sc": "npm run --workspace @frameless/pdc-sc start", + "start:overige-objecten-api": "yarn workspace @frameless/overige-objecten-api start", "build": "npm run --workspaces build --if-present", "build:frontend": "npm run build --workspace @frameless/${APP}", "build:strapi": "npm run build --workspace @frameless/${APP}", diff --git a/yarn.lock b/yarn.lock index af20220fa..a5bd9c6a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,6 +168,15 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@apidevtools/json-schema-ref-parser@^11.7.0": + version "11.7.0" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz#228d72018a0e7cbee744b677eaa01a8968f302d9" + integrity sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.15" + js-yaml "^4.1.0" + "@apollo/protobufjs@1.2.6": version "1.2.6" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" @@ -314,6 +323,14 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.22.13": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" @@ -491,6 +508,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + "@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" @@ -523,6 +545,16 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" @@ -3997,6 +4029,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsdevtools/ono@7.1.3", "@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + "@juggle/resize-observer@^3.4.0": version "3.4.0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" @@ -5218,6 +5255,38 @@ resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-2.1.0.tgz#383acd31259d7c9ae8fb1b02d5e18fe613c2a13d" integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg== +"@redocly/ajv@^8.11.2": + version "8.11.2" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.2.tgz#46e1bf321ec0ac1e0fd31dea41a3d1fcbdcda0b5" + integrity sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js-replace "^1.0.1" + +"@redocly/config@^0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.12.1.tgz#7b905a17d710244550ef826542d0db164d5ace02" + integrity sha512-RW3rSirfsPdr0uvATijRDU3f55SuZV3m7/ppdTDvGw4IB0cmeZRkFmqTrchxMqWP50Gfg1tpHnjdxUCNo0E2qg== + +"@redocly/openapi-core@^1.25.3": + version "1.25.4" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.25.4.tgz#99e2535a55f6fa186bab0b3fe28603da1d1f4d38" + integrity sha512-qnpr4Z1rzfXdtxQxt/lfGD0wW3UVrm3qhrTpzLG5R/Ze+z+1u8sSRiQHp9N+RT3IuMjh00wq59nop9x9PPa1jQ== + dependencies: + "@redocly/ajv" "^8.11.2" + "@redocly/config" "^0.12.1" + colorette "^1.2.0" + https-proxy-agent "^7.0.4" + js-levenshtein "^1.1.6" + js-yaml "^4.1.0" + lodash.isequal "^4.5.0" + minimatch "^5.0.1" + node-fetch "^2.6.1" + pluralize "^8.0.0" + yaml-ast-parser "0.0.43" + "@reduxjs/toolkit@1.9.7": version "1.9.7" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.7.tgz#7fc07c0b0ebec52043f8cb43510cf346405f78a6" @@ -7146,6 +7215,11 @@ resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537" integrity sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg== +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + "@types/cookies@*": version "0.9.0" resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.9.0.tgz#a2290cfb325f75f0f28720939bee854d4142aee2" @@ -7156,6 +7230,13 @@ "@types/keygrip" "*" "@types/node" "*" +"@types/cors@2.8.17": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -7362,7 +7443,7 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -7478,6 +7559,11 @@ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -7498,6 +7584,13 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== +"@types/multer@^1.4.12": + version "1.4.12" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.12.tgz#da67bd0c809f3a63fe097c458c0d4af1fea50ab7" + integrity sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg== + dependencies: + "@types/express" "*" + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -7703,6 +7796,32 @@ "@types/react" "*" csstype "^3.0.2" +"@types/superagent@^8.1.0": + version "8.1.9" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.9.tgz#28bfe4658e469838ed0bf66d898354bcab21f49f" + integrity sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + form-data "^4.0.0" + +"@types/supertest@6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.2.tgz#2af1c466456aaf82c7c6106c6b5cbd73a5e86588" + integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + +"@types/swagger-ui-express@4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz#d0929e3fabac1a96a8a9c6c7ee8d42362c5cdf48" + integrity sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg== + dependencies: + "@types/express" "*" + "@types/serve-static" "*" + "@types/through@*": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.33.tgz#14ebf599320e1c7851e7d598149af183c6b9ea56" @@ -7747,6 +7866,11 @@ dependencies: "@types/node" "*" +"@types/yamljs@0.2.34": + version "0.2.34" + resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.34.tgz#c10b1f31b173f2cc93342f27b0796c2eb5b3ae84" + integrity sha512-gJvfRlv9ErxdOv7ux7UsJVePtX54NAvQyd8ncoiFqK8G5aeHIfQfGH2fbruvjAQ9657HwAaO54waS+Dsk2QTUQ== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -8372,7 +8496,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-draft-04@~1.0.0: +ajv-draft-04@^1.0.0, ajv-draft-04@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== @@ -8418,7 +8542,7 @@ ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.17.1, ajv@^8.9.0: version "8.17.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== @@ -8686,6 +8810,11 @@ apollo-server-types@^3.6.2, apollo-server-types@^3.8.0: apollo-reporting-protobuf "^3.4.0" apollo-server-env "^4.2.1" +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + "aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -9461,6 +9590,24 @@ body-parser@1.20.2: type-is "~1.6.18" unpipe "1.0.0" +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + bonjour-service@^1.0.11: version "1.2.1" resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" @@ -9614,6 +9761,13 @@ browserslist@^4.0.0, browserslist@^4.17.3, browserslist@^4.18.1, browserslist@^4 node-releases "^2.0.18" update-browserslist-db "^1.1.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -9675,7 +9829,7 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" -busboy@1.6.0, busboy@^1.6.0: +busboy@1.6.0, busboy@^1.0.0, busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== @@ -9937,7 +10091,7 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -10041,6 +10195,11 @@ change-case@^4.1.2: snake-case "^3.0.4" tslib "^2.0.3" +change-case@^5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" + integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -10484,7 +10643,7 @@ colorette@2.0.19: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== -colorette@^1.1.0: +colorette@^1.1.0, colorette@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== @@ -10595,7 +10754,7 @@ compare-versions@^5.0.1: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.3.tgz#a9b34fea217472650ef4a2651d905f42c28ebfd7" integrity sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A== -component-emitter@^1.2.1: +component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== @@ -10640,6 +10799,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" @@ -10752,7 +10921,7 @@ content-disposition@0.5.4, content-disposition@~0.5.2: dependencies: safe-buffer "5.2.1" -content-type@^1.0.4, content-type@~1.0.4, content-type@~1.0.5: +content-type@^1.0.4, content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -10869,6 +11038,11 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + cookies@~0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" @@ -10951,6 +11125,14 @@ core-util-is@^1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig-typescript-loader@^4.0.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz#f3feae459ea090f131df5474ce4b1222912319f9" @@ -11054,7 +11236,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-fetch@^3.1.5: +cross-fetch@^3.0.4, cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== @@ -11842,7 +12024,7 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -dezalgo@^1.0.0: +dezalgo@^1.0.0, dezalgo@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== @@ -12100,7 +12282,7 @@ dotenv@16.3.2: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f" integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== -dotenv@^16.0.0: +dotenv@16.4.5, dotenv@^16.0.0: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -12167,6 +12349,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.5.4: version "1.5.13" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" @@ -12230,6 +12419,11 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding-sniffer@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz#799569d66d443babe82af18c9f403498365ef1d5" @@ -13186,6 +13380,25 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +express-openapi-validator@5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/express-openapi-validator/-/express-openapi-validator-5.3.7.tgz#e93cabd9a3b4be4f62e748d239128a217118c00c" + integrity sha512-AFlIXvICrPWJvWtrsfpL212kyvmyzThcZ1ASnnsRqmzxSo/3SV+J7M1oEsFYbqo7AYQPGtSBKy7eheGM6wPwag== + dependencies: + "@apidevtools/json-schema-ref-parser" "^11.7.0" + "@types/multer" "^1.4.12" + ajv "^8.17.1" + ajv-draft-04 "^1.0.0" + ajv-formats "^2.1.1" + content-type "^1.0.5" + json-schema-traverse "^1.0.0" + lodash.clonedeep "^4.5.0" + lodash.get "^4.4.2" + media-typer "^1.1.0" + multer "^1.4.5-lts.1" + ono "^7.1.3" + path-to-regexp "^8.1.0" + express@4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -13223,6 +13436,43 @@ express@4.18.2: utils-merge "1.0.1" vary "~1.1.2" +express@4.21.0: + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.10" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + express@^4.17.3: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" @@ -13349,7 +13599,7 @@ fast-json-patch@^3.1.0, fast-json-patch@^3.1.1: resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947" integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ== -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -13371,7 +13621,7 @@ fast-querystring@^1.1.1: dependencies: fast-decode-uri-component "^1.0.1" -fast-safe-stringify@2.1.1: +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -13502,6 +13752,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + filesize@^8.0.6: version "8.0.7" resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" @@ -13537,6 +13794,19 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-cache-dir@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" @@ -13767,6 +14037,15 @@ formidable@^1.1.1: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== +formidable@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a" + integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og== + dependencies: + dezalgo "^1.0.4" + hexoid "^1.0.0" + once "^1.4.0" + formik@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.0.tgz#8243e42a89e1c9fbe9aefbd48bc8d1f10ae2950d" @@ -14320,6 +14599,18 @@ glob@^10.2.2, glob@^10.3.7: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" + integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -14949,6 +15240,11 @@ helmet@^6.0.1: resolved "https://registry.yarnpkg.com/helmet/-/helmet-6.2.0.tgz#c29d62014be4c70b8ef092c9c5e54c8c26b8e16e" integrity sha512-DWlwuXLLqbrIOltR6tFQXShj/+7Cyp0gLi6uAb8qMdFh/YBBFbKSgQ6nbXmScYd8emMctuthmgIa7tUfo9Rtyg== +hexoid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + highlight.js@^10.4.1: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" @@ -15261,7 +15557,7 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -https-proxy-agent@^7.0.0: +https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.4: version "7.0.5" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== @@ -15523,6 +15819,11 @@ indent-string@^5.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== +index-to-position@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-0.1.2.tgz#e11bfe995ca4d8eddb1ec43274488f3c201a7f09" + integrity sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g== + infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -16507,6 +16808,23 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" + integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw== + dependencies: + "@isaacs/cliui" "^8.0.2" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + java-properties@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" @@ -16646,6 +16964,14 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" +jest-fetch-mock@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" @@ -16826,7 +17152,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -16965,6 +17291,11 @@ js-cookie@2.2.1, js-cookie@^2.2.1: dependencies: acorn "^8.0.5" +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + js-sdsl@^4.1.4: version "4.4.2" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847" @@ -16988,6 +17319,13 @@ js-yaml@3.14.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -16996,13 +17334,6 @@ js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsbn@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -17974,7 +18305,7 @@ lodash.kebabcase@4.1.1, lodash.kebabcase@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== -lodash.memoize@^4.1.2: +lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -18146,6 +18477,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.1.tgz#3a732fbfedb82c5ba7bca6564ad3f42afcb6e147" + integrity sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ== + lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -18218,7 +18554,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@^1.1.1: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -18718,6 +19054,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + memfs@^3.1.2, memfs@^3.4.1, memfs@^3.4.12, memfs@^3.4.3: version "3.6.0" resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" @@ -18798,6 +19139,11 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -19310,6 +19656,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mime@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" @@ -19389,6 +19740,13 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== + dependencies: + brace-expansion "^2.0.1" + minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" @@ -19550,7 +19908,7 @@ mkdirp-infer-owner@^2.0.0: infer-owner "^1.0.4" mkdirp "^1.0.3" -mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -19616,6 +19974,19 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multer@^1.4.5-lts.1: + version "1.4.5-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.5-lts.1.tgz#803e24ad1984f58edffbc79f56e305aec5cfd1ac" + integrity sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + multi-semantic-release@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/multi-semantic-release/-/multi-semantic-release-3.0.2.tgz#a714bb66a30a669d64386f566f728416ff59cae8" @@ -20012,6 +20383,22 @@ nodemon@3.0.3: touch "^3.1.0" undefsafe "^2.0.5" +nodemon@3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.7.tgz#07cb1f455f8bece6a499e0d72b5e029485521a54" + integrity sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + noms@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" @@ -20470,7 +20857,7 @@ oauth-sign@^0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -20662,6 +21049,13 @@ only@~0.0.2: resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== +ono@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/ono/-/ono-7.1.3.tgz#a054e96a388f566a6c4c95e1e92b9b253722d286" + integrity sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ== + dependencies: + "@jsdevtools/ono" "7.1.3" + open@8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" @@ -20698,6 +21092,18 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" +openapi-typescript@7.4.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-7.4.1.tgz#1e42ecbd286ea62f6f8dc994b9aeccaa8b31c537" + integrity sha512-HrRoWveViADezHCNgQqZmPKmQ74q7nuH/yg9ursFucZaYQNUqsX38fE/V2sKBHVM+pws4tAHpuh/ext2UJ/AoQ== + dependencies: + "@redocly/openapi-core" "^1.25.3" + ansi-colors "^4.1.3" + change-case "^5.4.4" + parse-json "^8.1.0" + supports-color "^9.4.0" + yargs-parser "^21.1.1" + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -21084,6 +21490,15 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-json@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.1.0.tgz#91cdc7728004e955af9cb734de5684733b24a717" + integrity sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA== + dependencies: + "@babel/code-frame" "^7.22.13" + index-to-position "^0.1.2" + type-fest "^4.7.1" + parse-numeric-range@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" @@ -21283,6 +21698,19 @@ path-scurry@^1.11.1, path-scurry@^1.7.0: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -21305,6 +21733,11 @@ path-to-regexp@^6.1.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== +path-to-regexp@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -21498,7 +21931,7 @@ plur@^4.0.0: dependencies: irregular-plurals "^3.2.0" -pluralize@8.0.0: +pluralize@8.0.0, pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== @@ -22272,6 +22705,11 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== +promise-polyfill@^8.1.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63" + integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== + promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" @@ -22607,7 +23045,7 @@ qs@6.11.1: dependencies: side-channel "^1.0.4" -qs@^6.10.3, qs@^6.11.2, qs@^6.4.0, qs@^6.5.2, qs@^6.9.6: +qs@6.13.0, qs@^6.10.3, qs@^6.11.0, qs@^6.11.2, qs@^6.4.0, qs@^6.5.2, qs@^6.9.6: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== @@ -23306,7 +23744,7 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -23896,6 +24334,14 @@ rimraf@5.0.5: dependencies: glob "^10.3.7" +rimraf@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e" + integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A== + dependencies: + glob "^11.0.0" + package-json-from-dist "^1.0.0" + rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -24371,6 +24817,25 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + sendmail@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/sendmail/-/sendmail-1.6.1.tgz#6be92fb4be70d1d9ad102030aeb1e737bd512159" @@ -24447,6 +24912,16 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -25780,6 +26255,29 @@ stylis@^4.3.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4" integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== +superagent@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1" + integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.4" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^3.5.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.0" + +supertest@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634" + integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA== + dependencies: + methods "^1.1.2" + superagent "^9.0.1" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -25806,6 +26304,11 @@ supports-color@^8.0.0, supports-color@^8.1.1, 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.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" @@ -25863,6 +26366,18 @@ svgo@^3.0.2, svgo@^3.2.0: csso "^5.0.5" picocolors "^1.0.0" +swagger-ui-dist@>=5.0.0: + version "5.17.14" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz#e2c222e5bf9e15ccf80ec4bc08b4aaac09792fd6" + integrity sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw== + +swagger-ui-express@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz#fb8c1b781d2793a6bd2f8a205a3f4bd6fa020dd8" + integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== + dependencies: + swagger-ui-dist ">=5.0.0" + swap-case@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" @@ -26339,6 +26854,21 @@ ts-easing@^0.2.0: resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec" integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ== +ts-jest@29.2.3: + version "29.2.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.3.tgz#3d226ac36b8b820151a38f164414f9f6b412131f" + integrity sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ== + dependencies: + bs-logger "0.x" + ejs "^3.1.10" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + ts-log@^2.2.3: version "2.2.5" resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.5.tgz#aef3252f1143d11047e2cb6f7cfaac7408d96623" @@ -26473,12 +27003,17 @@ type-fest@^3.12.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== +type-fest@^4.7.1: + version "4.26.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.26.1.tgz#a4a17fa314f976dd3e6d6675ef6c775c16d7955e" + integrity sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg== + type-func@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/type-func/-/type-func-1.0.3.tgz#ab184234ae80d8d50057cefeff3b2d97d08ae9b0" integrity sha512-YA90CUk+i00tWESPNRMahywXhAz+12NLJLKlOWrgHIbqaFXjdZrWstRghaibOW/IxhPjui4SmXxO/03XSGRIjA== -type-is@^1.6.14, type-is@^1.6.16, type-is@^1.6.18, type-is@~1.6.18: +type-is@^1.6.14, type-is@^1.6.16, type-is@^1.6.18, type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -26537,6 +27072,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + typescript@5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" @@ -26888,6 +27428,11 @@ upper-case@^2.0.2: dependencies: tslib "^2.0.3" +uri-js-replace@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uri-js-replace/-/uri-js-replace-1.0.1.tgz#c285bb352b701c9dfdaeffc4da5be77f936c9048" + integrity sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g== + uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -27076,7 +27621,7 @@ vanilla-picker@^2.11.2, vanilla-picker@^2.12.1: dependencies: "@sphinxxxx/color-conversion" "^2.2.2" -vary@^1.1.2, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -27793,7 +28338,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml-ast-parser@^0.0.43: +yaml-ast-parser@0.0.43, yaml-ast-parser@^0.0.43: version "0.0.43" resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== @@ -27821,7 +28366,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.1.1: +yargs-parser@^21.0.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==