From bbeadfbaf703b13f5c6d8f339c5964d088715464 Mon Sep 17 00:00:00 2001 From: Ali Amori Kadhim Date: Thu, 14 Nov 2024 12:11:17 +0100 Subject: [PATCH] fix(overige-objecten-api): fix type parameter value --- .../src/controllers/openapi/index.test.ts | 18 ++ .../src/controllers/openapi/index.ts | 29 ++- .../src/controllers/openapi/types.ts | 240 ++++++++++++++++++ 3 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 apps/overige-objecten-api/src/controllers/openapi/types.ts diff --git a/apps/overige-objecten-api/src/controllers/openapi/index.test.ts b/apps/overige-objecten-api/src/controllers/openapi/index.test.ts index 0f048ecdf..919a783df 100644 --- a/apps/overige-objecten-api/src/controllers/openapi/index.test.ts +++ b/apps/overige-objecten-api/src/controllers/openapi/index.test.ts @@ -1,4 +1,5 @@ import request from 'supertest'; +import { OpenAPI } from './types'; import app from '../../server'; describe('openAPIController', () => { @@ -15,4 +16,21 @@ describe('openAPIController', () => { expect(response.text).toEqual(JSON.stringify({ message: 'An unexpected error occurred.' })); spy.mockRestore(); }); + it('GET api/v2/openapi.json return 200 and json with updated type parameter schema enum contains the correct URLs', async () => { + const spy = jest + .spyOn(require('../../utils/getTheServerURL'), 'getTheServerURL') + .mockImplementation(() => 'https://example.com/'); + const response = await request(app).get('/api/v2/openapi.json'); + const body = response.body as OpenAPI; + expect(response.status).toBe(200); + expect(body).toBeDefined(); + expect(body.paths['/objects'].get.parameters).toBeDefined(); + const typeParameter = body.paths['/objects'].get.parameters.find((parameter) => parameter.name === 'type'); + expect(typeParameter).toBeDefined(); + expect(typeParameter?.schema.enum).toEqual([ + 'https://example.com/api/v2/objecttypes/kennisartikel', + 'https://example.com/api/v2/objecttypes/vac', + ]); + spy.mockRestore(); + }); }); diff --git a/apps/overige-objecten-api/src/controllers/openapi/index.ts b/apps/overige-objecten-api/src/controllers/openapi/index.ts index 055ddc68a..b203dbd1f 100644 --- a/apps/overige-objecten-api/src/controllers/openapi/index.ts +++ b/apps/overige-objecten-api/src/controllers/openapi/index.ts @@ -1,25 +1,32 @@ +/* eslint-disable no-undef */ import type { RequestHandler } from 'express'; import yaml from 'js-yaml'; import path from 'node:path'; +import type { OpenAPI } from './types'; 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')); + const url = new URL('api/v2', getTheServerURL(req)).href; + const OBJECT_TYPE_ENUM = [`${url}/objecttypes/kennisartikel`, `${url}/objecttypes/vac`]; + const OPEN_API_YAML = readFile(path.join(__dirname, '../../docs/openapi.yaml')); - if (!openapiYAML) throw new Error('openapi.yaml file not found'); + if (!OPEN_API_YAML) 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/v2', getTheServerURL(req)), + const openAPIDocument = yaml.load(OPEN_API_YAML) as OpenAPI; + // Update server URLs + const openapiServers = (openAPIDocument.servers || []).map((server) => ({ + url, description: server.description, })); + // Update enum for specific endpoint parameter + const typeParameter = openAPIDocument.paths['/objects']?.get?.parameters?.find( + (parameter) => parameter.name === 'type', + ); + if (typeParameter?.schema) { + typeParameter.schema.enum = OBJECT_TYPE_ENUM; + } + res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); res.status(200); diff --git a/apps/overige-objecten-api/src/controllers/openapi/types.ts b/apps/overige-objecten-api/src/controllers/openapi/types.ts new file mode 100644 index 000000000..795d4107c --- /dev/null +++ b/apps/overige-objecten-api/src/controllers/openapi/types.ts @@ -0,0 +1,240 @@ +export interface APIInfo { + title: string; + description: string; + version: string; +} + +export interface Server { + url: string; + description: string; +} + +export interface OpenAPI { + openapi: string; + info: APIInfo; + servers: Server[]; + paths: Paths; + components: Components; +} +export interface Paths { + '/objects': ObjectsEndpoint; + '/objects/{uuid}': ObjectByIdEndpoint; +} + +export interface ObjectsEndpoint { + get: GetObjectsOperation; +} + +export interface ObjectByIdEndpoint { + get: GetObjectByIdOperation; +} + +export interface GetObjectsOperation { + security: Array<{ TokenAuth: any[] }>; + summary: string; + operationId: string; + parameters: Array; + responses: { + 200: ResponseListObjects; + }; +} + +export interface GetObjectByIdOperation { + security: Array<{ TokenAuth: any[] }>; + summary: string; + operationId: string; + parameters: Array; + responses: { + 200: ResponseObjectById; + 403: ErrorResponse; + 404: ErrorResponse; + 500: ErrorResponse; + }; +} +export interface ParameterQuery { + in: 'query'; + name: string; + required: boolean; + schema: { type: string; format?: string; enum?: string[] }; + description: string; +} + +export interface ParameterPath { + in: 'path'; + name: string; + required: boolean; + schema: { type: string }; + description: string; +} + +export interface ResponseListObjects { + description: string; + content: { + 'application/json': { + schema: ListObjectsSchema; + example: ListObjectsExample; + }; + }; +} + +export interface ResponseObjectById { + description: string; + content: { + 'application/json': { + schema: { oneOf: [{ $ref: string }, { $ref: string }] }; + example: ObjectExample; + }; + }; +} + +export interface ErrorResponse { + description: string; + content: { + 'application/json': { + schema: ErrorSchema; + }; + }; +} + +export interface ListObjectsSchema { + type: 'object'; + properties: { + count: { type: 'integer'; example: number }; + next: { type: 'string'; format: 'uri'; nullable: true; example: string }; + previous: { type: 'string'; format: 'uri'; nullable: true; example: string | null }; + results: { type: 'array'; items: { $ref: string } }; + }; +} + +export interface ListObjectsExample { + count: number; + next: string | null; + previous: string | null; + results: ObjectData[]; +} + +export interface ObjectExample { + url: string; + uuid: string; + type: string; + record: ObjectRecord; +} + +export interface ErrorSchema { + type: 'object'; + properties: { + error: { type: 'string'; example: string }; + }; +} +export interface Components { + securitySchemes: { + TokenAuth: SecurityScheme; + }; + schemas: { + ObjectData: ObjectData; + kennisartikel: KennisArtikel; + vac: VAC; + GeoJSONGeometry: GeoJSONGeometry; + }; +} + +export interface SecurityScheme { + type: 'apiKey'; + in: 'header'; + name: string; +} + +export interface ObjectData { + url: string; + uuid: string; + type: string; + record: ObjectRecord; +} + +export interface ObjectRecord { + index: number; + typeVersion: number; + data: KennisArtikel | VAC; + geometry: GeoJSONGeometry | null; + startAt: string; + endAt: string | null; + registrationAt: string; + correctionFor?: number; + correctedBy?: number; +} +export interface KennisArtikel { + url: string; + uuid: string; + upnUri: string; + publicatieDatum: string | null; + productAanwezig: boolean | null; + productValtOnder: string | null; + verantwoordelijkeOrganisatie: VerantwoordelijkeOrganisatie; + locaties?: string[] | null; + doelgroep: 'eu-burger' | 'eu-bedrijf'; + afdelingen: Afdeling[]; + vertalingen: Vertaling[]; + beschikbareTalen: string[]; +} + +export interface VerantwoordelijkeOrganisatie { + url: string; + owmsIdentifier: string; + owmsPrefLabel?: string; + owmsEndDate: string; +} + +export interface Afdeling { + afdelingId?: string; + afdelingNaam: string; +} + +export interface Vertaling { + taal: 'nl' | 'en'; + titel?: string; + tekst?: string; + procedureBeschrijving?: string; + vereisten?: string; + bezwaarEnBeroep?: string; + kostenEnBetaalmethoden?: string; + uitersteTermijn?: string; + wtdBijGeenReactie?: string; + notice?: string; + contact?: string; + deskMemo?: string; + trefwoorden?: Trefwoord[]; + datumWijziging: string; +} + +export interface Trefwoord { + trefwoord: string; +} + +export interface VAC { + url?: string; + vraag: string; + status: 'actief' | 'non-actief' | 'te-verwijderen'; + antwoord: string; + doelgroep: 'eu-burger' | 'eu-bedrijf' | 'eu-burger-bedrijf'; + afdelingen: Afdeling[]; + toelichting?: string; + trefwoorden?: Trefwoord[]; + gerelateerdeVACs?: RelatedVAC[]; + gerelateerdeProducten?: RelatedProduct[]; +} + +export interface RelatedVAC { + VAC: string; +} + +export interface RelatedProduct { + product: string; + productNaam: string; +} + +export interface GeoJSONGeometry { + type: string; + coordinates: Point2D[] | Point2D[][] | Point2D[][][]; +} + +export type Point2D = [number, number];