From b526d19014797287ef9de4c1927e14557224b80d Mon Sep 17 00:00:00 2001 From: Roman Kalyakin Date: Thu, 25 Apr 2024 14:03:30 +0200 Subject: [PATCH] Reorganising schemas (#382) * referencing schemas * updated search schema * articles updated * collections * refactored schemas used in public services, added validation * deleted unused files * updated collectable items * removed unused schemas * added still used schema * generated types --- README.md | 8 + package-lock.json | 94 +++- package.json | 1 + scripts/generate-types.js | 17 +- src/app.ts | 4 + src/authentication.js | 4 +- src/middleware/openApiValidator.ts | 38 ++ src/middleware/swagger.ts | 34 +- src/models/generated/parameters.ts | 6 + src/models/generated/requestBodies.ts | 15 + src/models/generated/responses.ts | 522 ++++++++++++++++++ src/models/generated/schemas.ts | 419 ++++++++++++++ src/schema/README.md | 4 + src/schema/collectable-items/response.json | 36 -- src/schema/collections/findResponse.json | 33 -- .../parameters/textReusePassagesAddOns.json} | 3 +- .../authenticationCreate.json} | 4 +- .../collectionsCreate.json} | 1 - src/schema/responses/articlesFind.json | 3 + src/schema/responses/articlesGet.json | 3 + .../authenticationCreate.json} | 13 +- .../baseFind.json} | 9 +- .../responses/collectableItemsFind.json | 18 + src/schema/responses/collectionsCreate.json | 3 + src/schema/responses/collectionsFind.json | 18 + src/schema/responses/collectionsGet.json | 3 + .../collectionsRemove.json} | 2 +- src/schema/responses/pagination.json | 35 ++ src/schema/responses/searchFind.json | 18 + .../responses/textReuseClusterFind.json | 18 + src/schema/responses/textReuseClusterGet.json | 18 + .../responses/textReusePassagesFind.json | 18 + .../responses/textReusePassagesGet.json | 3 + .../versionFind.json} | 2 +- .../article.json => schemas/Article.json} | 15 +- src/schema/schemas/BaseUser.json | 25 + .../CollectableItemGroup.json | 5 +- src/schema/schemas/Collection.json | 52 ++ .../entity.json => schemas/Entity.json} | 3 +- .../Error.json} | 3 +- src/schema/schemas/Filter.json | 56 ++ .../{search/page.json => schemas/Page.json} | 4 +- src/schema/schemas/TextReuseCluster.json | 48 ++ .../schemas/TextReuseClusterDetails.json | 44 ++ .../schemas/TextReusePassage.json} | 0 .../user.json => schemas/User.json} | 4 +- src/schema/search/response.json | 33 -- src/services/articles/articles.schema.ts | 4 +- .../authentication/authentication.schema.ts | 33 +- .../collectable-items.schema.ts | 28 +- .../collectable-items.service.js | 24 +- .../collections/collections.schema.ts | 40 +- .../collections/schema/post/payload.json | 21 - src/services/schemas.ts | 23 + src/services/search/search.schema.ts | 32 +- .../text-reuse-clusters.schema.ts | 27 +- .../schema/findResponse.json | 32 -- .../text-reuse-passages.hooks.js | 8 +- .../text-reuse-passages.schema.ts | 16 +- src/services/version/version.schema.ts | 5 +- src/util/openapi.ts | 145 ++--- yarn.lock | 80 ++- 62 files changed, 1745 insertions(+), 492 deletions(-) create mode 100644 src/middleware/openApiValidator.ts create mode 100644 src/models/generated/parameters.ts create mode 100644 src/models/generated/requestBodies.ts create mode 100644 src/models/generated/responses.ts create mode 100644 src/models/generated/schemas.ts create mode 100644 src/schema/README.md delete mode 100644 src/schema/collectable-items/response.json delete mode 100644 src/schema/collections/findResponse.json rename src/{services/text-reuse-passages/schema/addons.json => schema/parameters/textReusePassagesAddOns.json} (61%) rename src/schema/{authentication/request.json => requestBodies/authenticationCreate.json} (62%) rename src/schema/{collections/newCollection.json => requestBodies/collectionsCreate.json} (84%) create mode 100644 src/schema/responses/articlesFind.json create mode 100644 src/schema/responses/articlesGet.json rename src/schema/{authentication/response.json => responses/authenticationCreate.json} (55%) rename src/schema/{common/findResponse.json => responses/baseFind.json} (69%) create mode 100644 src/schema/responses/collectableItemsFind.json create mode 100644 src/schema/responses/collectionsCreate.json create mode 100644 src/schema/responses/collectionsFind.json create mode 100644 src/schema/responses/collectionsGet.json rename src/schema/{collections/removeResponse.json => responses/collectionsRemove.json} (88%) create mode 100644 src/schema/responses/pagination.json create mode 100644 src/schema/responses/searchFind.json create mode 100644 src/schema/responses/textReuseClusterFind.json create mode 100644 src/schema/responses/textReuseClusterGet.json create mode 100644 src/schema/responses/textReusePassagesFind.json create mode 100644 src/schema/responses/textReusePassagesGet.json rename src/schema/{version/response.json => responses/versionFind.json} (93%) rename src/schema/{search/article.json => schemas/Article.json} (66%) create mode 100644 src/schema/schemas/BaseUser.json rename src/schema/{collectable-items => schemas}/CollectableItemGroup.json (78%) create mode 100644 src/schema/schemas/Collection.json rename src/schema/{search/entity.json => schemas/Entity.json} (81%) rename src/schema/{common/defaultErrorResponse.json => schemas/Error.json} (62%) create mode 100644 src/schema/schemas/Filter.json rename src/schema/{search/page.json => schemas/Page.json} (94%) create mode 100644 src/schema/schemas/TextReuseCluster.json create mode 100644 src/schema/schemas/TextReuseClusterDetails.json rename src/{services/text-reuse-passages/schema/passage.json => schema/schemas/TextReusePassage.json} (100%) rename src/schema/{authentication/user.json => schemas/User.json} (84%) delete mode 100644 src/schema/search/response.json delete mode 100644 src/services/collections/schema/post/payload.json create mode 100644 src/services/schemas.ts delete mode 100644 src/services/text-reuse-passages/schema/findResponse.json diff --git a/README.md b/README.md index e745077e..298e1ee1 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,14 @@ $ feathers generate model # Generate a new Model $ feathers help # Show all commands ``` +## Generating Typescipt types from JSON schemas + +When a schema is updated, the typescript types should be regenerated. This can be done by running the following command: + +``` +npm run generate-types +``` + ## Help For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). diff --git a/package-lock.json b/package-lock.json index e05c1176..1174e4e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "debug": "^4.3.4", "decypher": "^0.13.0", "dotenv": "^16.0.1", + "express-openapi-validator": "5.1.6", "fast-ratelimit": "^3.0.0", "fast-xml-parser": "^4.3.4", "feathers-authentication-hooks": "^1.0.2", @@ -133,6 +134,17 @@ "node": ">=12.17" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -836,6 +848,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@koa/cors": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", @@ -1325,6 +1342,14 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "16.18.96", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", @@ -1466,6 +1491,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -2078,6 +2116,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4044,6 +4087,39 @@ "node": ">= 0.10.0" } }, + "node_modules/express-openapi-validator": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.1.6.tgz", + "integrity": "sha512-CF24Pef5uThjdsCbjo1UP2mYx2YCkQl1HFoikCFFafFpZBCZ0YErD/RbqlcnKbKM9tMwXZsjAuuO84b2hmdF4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.1.2", + "@types/multer": "^1.4.7", + "ajv": "^8.11.2", + "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": "^6.2.0" + } + }, + "node_modules/express-openapi-validator/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5881,7 +5957,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -5892,8 +5967,7 @@ "node_modules/js-yaml/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -6535,9 +6609,9 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7265,6 +7339,14 @@ "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" }, + "node_modules/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ==", + "dependencies": { + "@jsdevtools/ono": "7.1.3" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", diff --git a/package.json b/package.json index 5f01e3d9..f9027c30 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "node-fetch": "2.7.0", "node-http-proxy-json": "^0.1.6", "passport-github": "^1.1.0", + "express-openapi-validator": "5.1.6", "pg": "^8.11.3", "pg-hstore": "^2.3.2", "redis": "4.6.13", diff --git a/scripts/generate-types.js b/scripts/generate-types.js index c32f125b..b0b5f714 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -1,23 +1,22 @@ const { execSync } = require('node:child_process') const fs = require('node:fs') -const basePath = './src/services' +const basePath = './src/schema' -// for now support only the followin services until we migrate everything: -const supportedServices = ['text-reuse-clusters'] +const schemaBits = ['schemas', 'parameters', 'requestBodies', 'responses'] const directories = fs - .readdirSync('./src/services') + .readdirSync(basePath) .filter(item => { return fs.statSync(`${basePath}/${item}`).isDirectory() }) - .filter(item => supportedServices.includes(item)) + .filter(item => schemaBits.includes(item)) -directories.forEach(service => { +directories.forEach(dir => { // eslint-disable-next-line no-console - console.log(`Generating types for service ${service}...`) + console.log(`Generating types for service ${dir}...`) const command = - `quicktype --src-lang schema --src ${basePath}/${service}/schema/**/*.json` + - ` --out ${basePath}/${service}/models/generated.ts --lang ts --just-types` + `quicktype --src-lang schema --src ${basePath}/${dir}/*.json` + + ` --out ./src/models/generated/${dir}.ts --lang ts --just-types` execSync(command) }) diff --git a/src/app.ts b/src/app.ts index e42ff3a0..b4c0cb26 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,7 +10,9 @@ import sequelize from './sequelize' import solr from './solr' import media from './services/media' import proxy from './services/proxy' +import schemas from './services/schemas' import { ensureServiceIsFeathersCompatible } from './util/feathers' +import openApiValidator from './middleware/openApiValidator' const path = require('path') const compress = require('compression') @@ -87,5 +89,7 @@ app.configure(appHooks) // configure express services app.configure(media) app.configure(proxy) +app.configure(schemas) +app.configure(openApiValidator) module.exports = app diff --git a/src/authentication.js b/src/authentication.js index e60ae989..e0689289 100644 --- a/src/authentication.js +++ b/src/authentication.js @@ -46,13 +46,15 @@ class HashedPasswordVerifier extends LocalStrategy { } module.exports = app => { + const isPublicApi = app.get('isPublicApi') const authentication = new CustomisedAuthenticationService(app) authentication.register('jwt', new JWTStrategy()) authentication.register('local', new HashedPasswordVerifier()) app.use('/authentication', authentication, { - methods: ['create', 'remove'], + methods: isPublicApi ? ['create'] : undefined, + events: [], docs: createSwaggerServiceOptions({ schemas: {}, docs }), }) } diff --git a/src/middleware/openApiValidator.ts b/src/middleware/openApiValidator.ts new file mode 100644 index 00000000..95a9d578 --- /dev/null +++ b/src/middleware/openApiValidator.ts @@ -0,0 +1,38 @@ +import type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types' +import RefParser, { FileInfo } from '@apidevtools/json-schema-ref-parser' +import type { ImpressoApplication } from '../types' +import type { Application } from '@feathersjs/express' +import * as OpenApiValidator from 'express-openapi-validator' +import fs from 'fs' + +export default async (app: ImpressoApplication & Application) => { + const isPublicApi = app.get('isPublicApi') + if (!isPublicApi) return + + if (!('docs' in app)) throw new Error('`docs` property not found in app object. Is swagger initialized?') + const spec = (app as any)['docs'] as unknown as OpenAPIV3.Document + + const dereferencedOpenApiSpec = await RefParser.dereference(spec, { + resolve: { + file: { + /** + * All JSON schema files are relative to the + * `src` / `dist` directory. Adding it here. + */ + read: (file: FileInfo) => { + const cwd = process.cwd() + const filePath = file.url.replace(cwd, `${cwd}/dist`) + return fs.readFileSync(filePath, 'utf-8') + }, + }, + }, + }) + + const middlewares = OpenApiValidator.middleware({ + apiSpec: dereferencedOpenApiSpec as unknown as OpenAPIV3.Document, + validateRequests: true, // (default) + validateResponses: true, // false by default + validateApiSpec: false, + }) + middlewares.forEach(middleware => app.use(middleware)) +} diff --git a/src/middleware/swagger.ts b/src/middleware/swagger.ts index f7be49da..8e73c5a4 100644 --- a/src/middleware/swagger.ts +++ b/src/middleware/swagger.ts @@ -1,6 +1,32 @@ import swagger, { swaggerUI } from 'feathers-swagger' import { logger } from '../logger' import { ImpressoApplication } from '../types' +import fs from 'fs' +import path from 'path' +import { Application } from '@feathersjs/express' + +const schemaBaseDir = path.join(__dirname, '../schema') + +interface SchemaRef { + $ref: string +} + +const getFilesAsSchemaRefs = (dir: string, prefix: string): Record => { + const allFiles = fs.readdirSync(dir) + + return allFiles + .filter(f => f.endsWith('.json')) + .reduce( + (acc, f) => { + const key = path.basename(f, '.json') + acc[key] = { + $ref: `${prefix}/${key}.json`, + } + return acc + }, + {} as Record + ) +} function getRedirectPrefix({ req, ctx }: any) { const headers = (req && req.headers) || (ctx && ctx.headers) || {} @@ -33,7 +59,7 @@ function generateSwaggerUIInitializerScript({ docsJsonPath, ctx, req }: any) { ` } -export default (app: ImpressoApplication) => { +export default (app: ImpressoApplication & Application) => { if (!app.get('isPublicApi')) { logger.info('Internal API - swagger middleware is disabled') return @@ -51,6 +77,10 @@ export default (app: ImpressoApplication) => { version: require('../../package.json').version, }, components: { + schemas: getFilesAsSchemaRefs(`${schemaBaseDir}/schemas`, './schema/schemas'), + requestBodies: getFilesAsSchemaRefs(`${schemaBaseDir}/requestBodies`, './schema/requestBodies'), + responses: getFilesAsSchemaRefs(`${schemaBaseDir}/responses`, './schema/responses'), + parameters: getFilesAsSchemaRefs(`${schemaBaseDir}/parameters`, './schema/parameters'), securitySchemes: { BearerAuth: { type: 'http', @@ -73,5 +103,5 @@ export default (app: ImpressoApplication) => { getSwaggerInitializerScript: generateSwaggerUIInitializerScript, }), }) - return app.configure(swaggerItem) + app.configure(swaggerItem) } diff --git a/src/models/generated/parameters.ts b/src/models/generated/parameters.ts new file mode 100644 index 00000000..d60d508f --- /dev/null +++ b/src/models/generated/parameters.ts @@ -0,0 +1,6 @@ +/** + * Add-ons for text reuse passages find method. + */ +export interface Parameters { + newspaper?: any; +} diff --git a/src/models/generated/requestBodies.ts b/src/models/generated/requestBodies.ts new file mode 100644 index 00000000..df4a928e --- /dev/null +++ b/src/models/generated/requestBodies.ts @@ -0,0 +1,15 @@ +export interface AuthenticationCreateRequest { + email: string; + password: string; + strategy: Strategy; +} + +export enum Strategy { + Local = "local", +} + +export interface NewCollection { + description?: string; + name: string; + status?: string; +} diff --git a/src/models/generated/responses.ts b/src/models/generated/responses.ts new file mode 100644 index 00000000..d79d6f17 --- /dev/null +++ b/src/models/generated/responses.ts @@ -0,0 +1,522 @@ +/** + * Search find response (articles) + */ +export interface SearchFindResponse { + data: any[]; +} + +/** + * A journal/magazine article + */ +export interface ArticlesGet { + /** + * The excerpt of the article + */ + excerpt: string; + /** + * TODO + */ + isCC: boolean; + locations?: Entity[]; + /** + * The number of pages in this article + */ + nbPages: number; + pages: Page[]; + persons?: Entity[]; + /** + * The size of the article in characters + */ + size: number; + /** + * The title of the article + */ + title: string; + /** + * The type of the article. NOTE: may be empty. + */ + type: string; + /** + * The unique identifier of the article + */ + uid: string; +} + +/** + * An entity like location, person, etc + */ +export interface Entity { + /** + * Relevance of the entity in the document + */ + relevance: number; + /** + * Unique identifier of the entity + */ + uid: string; +} + +/** + * A page of an article + */ +export interface Page { + /** + * The access rights code + */ + accessRights: string; + /** + * Whether the page has coordinates + */ + hasCoords: boolean; + /** + * Whether the page has errors + */ + hasErrors: boolean; + /** + * The IIF image file name of the page + */ + iiif: string; + /** + * The IIIF fragment of the page, image file name + */ + iiifFragment?: string; + /** + * The IIF image thumbnail file name of the page + */ + iiifThumbnail: string; + /** + * Reference to the article + */ + issueUid: string; + /** + * Page labels + */ + labels: string[]; + /** + * Unique ID of the newspaper + */ + newspaperUid: string; + /** + * The number of the page + */ + num: number; + /** + * Whether the page image has been obfuscated because the user is not authorised to access it + */ + obfuscated?: boolean; + /** + * Regions of the page + */ + regions: { [key: string]: any }[]; + /** + * The unique identifier of the page + */ + uid: string; +} + +export interface AuthenticationResponse { + accessToken: string; + authentication: Authentication; + user: User; +} + +export interface Authentication { + payload?: { [key: string]: any }; + strategy?: string; + [property: string]: any; +} + +/** + * User details + */ +export interface User { + firstname: string; + id: number; + isActive: boolean; + isStaff: boolean; + isSuperuser: boolean; + lastname: string; + uid: string; + username: string; +} + +/** + * Search find response (articles) + */ +export interface BaseFindResponse { + data: any[]; + /** + * Additional information about the response. + */ + info: { [key: string]: any }; + /** + * The number of items returned in this response + */ + limit: number; + /** + * The number of items skipped in this response + */ + skip: number; + /** + * The total number of items matching the query + */ + total: number; +} + +/** + * Collectable Item find response + * + * Search find response (articles) + */ +export interface CollectableItemFindResponse { + data: any[]; +} + +/** + * Description of the collection object (Collection class) + */ +export interface CollectionsGet { + countItems: number | string; + creationDate: string; + creator: BaseUser; + description: string; + labels?: string[]; + lastModifiedDate: string; + name: string; + status: string; + uid: string; +} + +export interface BaseUser { + uid: string; + username: string; + [property: string]: any; +} + +/** + * Collections find response + * + * Search find response (articles) + */ +export interface CollectionsFindResponse { + data: any[]; +} + +/** + * Remove collection response + */ +export interface RemoveCollectionResponse { + params: Params; + /** + * Deletion task details + */ + task: Task; +} + +export interface Params { + /** + * The collection id + */ + id?: string; + /** + * The status of the operation + */ + status?: Status; +} + +/** + * The status of the operation + */ +export enum Status { + Del = "DEL", +} + +/** + * Deletion task details + */ +export interface Task { + /** + * When task was created + */ + creationDate?: string; + /** + * The ID of the task + */ + task_id?: string; +} + +export interface FindTextReuseClustersResponse { + clusters: GetTextReuseClusterResponse[]; + info: Pagination; +} + +export interface GetTextReuseClusterResponse { + cluster: TextReuseCluster; + details?: TextReuseClusterDetails; + textSample: string; +} + +/** + * Represents a cluster of text reuse passages + */ +export interface TextReuseCluster { + /** + * Number of passages in cluster + */ + clusterSize?: number; + /** + * Number of connected clusters + */ + connectedClustersCount?: number; + /** + * ID of the text reuse passage + */ + id: string; + /** + * Percentage of overlap between passages in the cluster + */ + lexicalOverlap?: number; + /** + * Time window covered by documents in the cluster + */ + timeCoverage?: TimeCoverage; +} + +/** + * Time window covered by documents in the cluster + */ +export interface TimeCoverage { + from?: Date; + to?: Date; +} + +/** + * Extra details of the cluster + */ +export interface TextReuseClusterDetails { + facets: Facet[]; + /** + * Resolution for the 'date' facet + */ + resolution?: Resolution; +} + +export interface Facet { + buckets?: { [key: string]: any }[]; + /** + * Number of buckets + */ + numBuckets?: number; + /** + * Facet type + */ + type?: string; +} + +/** + * Resolution for the 'date' facet + */ +export enum Resolution { + Day = "day", + Month = "month", + Year = "year", +} + +/** + * TODO: review this schema + */ +export interface Pagination { + /** + * Limit to this many items + */ + limit?: number; + /** + * Skip this many items + */ + offset?: number; + /** + * Display N-th page (using 'limit' as the number of items in the page) + */ + page?: number; + /** + * Skip this many items + */ + skip?: number; + /** + * Total items available + */ + total?: number; + [property: string]: any; +} + +/** + * Collections find response + * + * Search find response (articles) + */ +export interface TextReusePassageFindResponse { + data: any[]; +} + +/** + * Represents a passage of text that was identified as a part of a text reuse cluster + */ +export interface TextReusePassagesGet { + /** + * Details of the article the passage belongs to + */ + article: ArticleDetails; + /** + * Collection IDs the passage belongs to + */ + collections: string[]; + /** + * Details of the connected clusters + */ + connectedClusters?: ConnectedClusters; + /** + * Textual content of the passage + */ + content: string; + /** + * Date of the item (article) where this passage was found + */ + date?: Date; + /** + * ID of the text reuse passage + */ + id: string; + /** + * TBD + */ + isFront?: boolean; + /** + * Issue details + */ + issue?: Issue; + /** + * Newspaper details + */ + newspaper?: Newspaper; + offsetEnd: number; + offsetStart: number; + /** + * Numbers of the pages where the passage was found + */ + pageNumbers: number[]; + /** + * Bounding box of the passage in the page + */ + pageRegions: string[]; + /** + * Size of the passage + */ + size?: number; + /** + * Details of the cluster the passage belongs to + */ + textReuseCluster: ClusterDetails; + /** + * Title of the content item (article) where this passage was found + */ + title: string; +} + +/** + * Details of the article the passage belongs to + */ +export interface ArticleDetails { + /** + * ID of the article + */ + id: string; +} + +/** + * Details of the connected clusters + */ +export interface ConnectedClusters { + /** + * ID of the connected cluster + */ + id: string; +} + +/** + * Issue details + */ +export interface Issue { + /** + * ID of the issue + */ + id: string; +} + +/** + * Newspaper details + */ +export interface Newspaper { + /** + * ID of the newspaper + */ + id: string; +} + +/** + * Details of the cluster the passage belongs to + */ +export interface ClusterDetails { + /** + * The size of the cluster + */ + clusterSize: number; + /** + * ID of the cluster + */ + id: string; + /** + * The lexical overlap between the two articles + */ + lexicalOverlap?: number; + /** + * The time difference in days between the two articles + */ + timeDifferenceDay?: number; +} + +/** + * Version of the API. Contains information about the current version of the API, features, + * etc. + */ +export interface APIVersion { + apiVersion: APIVersionObject; + documentsDateSpan: DocumentsDateSpan; + features: { [key: string]: any }; + mysql: Mysql; + newspapers: { [key: string]: any }; + solr: Solr; + version: string; +} + +export interface APIVersionObject { + branch?: string; + revision?: string; + version?: string; + [property: string]: any; +} + +export interface DocumentsDateSpan { + end?: any; + start?: any; + [property: string]: any; +} + +export interface Mysql { + endpoint?: string; + [property: string]: any; +} + +export interface Solr { + endpoints?: { [key: string]: any }; + [property: string]: any; +} diff --git a/src/models/generated/schemas.ts b/src/models/generated/schemas.ts new file mode 100644 index 00000000..0dcbf58b --- /dev/null +++ b/src/models/generated/schemas.ts @@ -0,0 +1,419 @@ +/** + * A journal/magazine article + */ +export interface Article { + /** + * The excerpt of the article + */ + excerpt: string; + /** + * TODO + */ + isCC: boolean; + locations?: Entity[]; + /** + * The number of pages in this article + */ + nbPages: number; + pages: Page[]; + persons?: Entity[]; + /** + * The size of the article in characters + */ + size: number; + /** + * The title of the article + */ + title: string; + /** + * The type of the article. NOTE: may be empty. + */ + type: string; + /** + * The unique identifier of the article + */ + uid: string; +} + +/** + * An entity like location, person, etc + */ +export interface Entity { + /** + * Relevance of the entity in the document + */ + relevance: number; + /** + * Unique identifier of the entity + */ + uid: string; +} + +/** + * A page of an article + */ +export interface Page { + /** + * The access rights code + */ + accessRights: string; + /** + * Whether the page has coordinates + */ + hasCoords: boolean; + /** + * Whether the page has errors + */ + hasErrors: boolean; + /** + * The IIF image file name of the page + */ + iiif: string; + /** + * The IIIF fragment of the page, image file name + */ + iiifFragment?: string; + /** + * The IIF image thumbnail file name of the page + */ + iiifThumbnail: string; + /** + * Reference to the article + */ + issueUid: string; + /** + * Page labels + */ + labels: string[]; + /** + * Unique ID of the newspaper + */ + newspaperUid: string; + /** + * The number of the page + */ + num: number; + /** + * Whether the page image has been obfuscated because the user is not authorised to access it + */ + obfuscated?: boolean; + /** + * Regions of the page + */ + regions: { [key: string]: any }[]; + /** + * The unique identifier of the page + */ + uid: string; +} + +/** + * Collectable item group object + */ +export interface CollectableItemGroup { + /** + * Ids of the collections + */ + collectionIds?: string[]; + /** + * Collection objects + */ + collections?: Collection[]; + /** + * Content type of the collectable item group: (A)rticle, (E)ntities, (P)ages, (I)ssues + */ + contentType?: ContentType; + /** + * The id of the collectable item group + */ + itemId?: string; + /** + * The latest date added to the collectable item group + */ + latestDateAdded?: Date; + /** + * Search queries + */ + searchQueries?: string[]; + [property: string]: any; +} + +/** + * Description of the collection object (Collection class) + */ +export interface Collection { + countItems: number | string; + creationDate: string; + creator: BaseUser; + description: string; + labels?: string[]; + lastModifiedDate: string; + name: string; + status: string; + uid: string; +} + +export interface BaseUser { + uid: string; + username: string; + [property: string]: any; +} + +/** + * Content type of the collectable item group: (A)rticle, (E)ntities, (P)ages, (I)ssues + */ +export enum ContentType { + A = "A", + E = "E", + I = "I", + P = "P", +} + +/** + * Default error response. TODO: replace with https://datatracker.ietf.org/doc/html/rfc9457 + */ +export interface Error { + data?: any[] | { [key: string]: any }; + message: string; + [property: string]: any; +} + +/** + * A single filter criteria + */ +export interface Filter { + context?: Context; + daterange?: string; + op?: Op; + precision?: Precision; + q?: string[] | string; + /** + * Possible values are in 'search.validators:eachFilterValidator.type.choices' + */ + type: string; + uid?: string; + uids?: string; +} + +export enum Context { + Exclude = "exclude", + Include = "include", +} + +export enum Op { + And = "AND", + Or = "OR", +} + +export enum Precision { + Exact = "exact", + Fuzzy = "fuzzy", + Partial = "partial", + Soft = "soft", +} + +/** + * Represents a cluster of text reuse passages + */ +export interface TextReuseCluster { + /** + * Number of passages in cluster + */ + clusterSize?: number; + /** + * Number of connected clusters + */ + connectedClustersCount?: number; + /** + * ID of the text reuse passage + */ + id: string; + /** + * Percentage of overlap between passages in the cluster + */ + lexicalOverlap?: number; + /** + * Time window covered by documents in the cluster + */ + timeCoverage?: TimeCoverage; +} + +/** + * Time window covered by documents in the cluster + */ +export interface TimeCoverage { + from?: Date; + to?: Date; +} + +/** + * Extra details of the cluster + */ +export interface TextReuseClusterDetails { + facets: Facet[]; + /** + * Resolution for the 'date' facet + */ + resolution?: Resolution; +} + +export interface Facet { + buckets?: { [key: string]: any }[]; + /** + * Number of buckets + */ + numBuckets?: number; + /** + * Facet type + */ + type?: string; +} + +/** + * Resolution for the 'date' facet + */ +export enum Resolution { + Day = "day", + Month = "month", + Year = "year", +} + +/** + * Represents a passage of text that was identified as a part of a text reuse cluster + */ +export interface TextReusePassage { + /** + * Details of the article the passage belongs to + */ + article: ArticleDetails; + /** + * Collection IDs the passage belongs to + */ + collections: string[]; + /** + * Details of the connected clusters + */ + connectedClusters?: ConnectedClusters; + /** + * Textual content of the passage + */ + content: string; + /** + * Date of the item (article) where this passage was found + */ + date?: Date; + /** + * ID of the text reuse passage + */ + id: string; + /** + * TBD + */ + isFront?: boolean; + /** + * Issue details + */ + issue?: Issue; + /** + * Newspaper details + */ + newspaper?: Newspaper; + offsetEnd: number; + offsetStart: number; + /** + * Numbers of the pages where the passage was found + */ + pageNumbers: number[]; + /** + * Bounding box of the passage in the page + */ + pageRegions: string[]; + /** + * Size of the passage + */ + size?: number; + /** + * Details of the cluster the passage belongs to + */ + textReuseCluster: ClusterDetails; + /** + * Title of the content item (article) where this passage was found + */ + title: string; +} + +/** + * Details of the article the passage belongs to + */ +export interface ArticleDetails { + /** + * ID of the article + */ + id: string; +} + +/** + * Details of the connected clusters + */ +export interface ConnectedClusters { + /** + * ID of the connected cluster + */ + id: string; +} + +/** + * Issue details + */ +export interface Issue { + /** + * ID of the issue + */ + id: string; +} + +/** + * Newspaper details + */ +export interface Newspaper { + /** + * ID of the newspaper + */ + id: string; +} + +/** + * Details of the cluster the passage belongs to + */ +export interface ClusterDetails { + /** + * The size of the cluster + */ + clusterSize: number; + /** + * ID of the cluster + */ + id: string; + /** + * The lexical overlap between the two articles + */ + lexicalOverlap?: number; + /** + * The time difference in days between the two articles + */ + timeDifferenceDay?: number; +} + +/** + * User details + */ +export interface User { + firstname: string; + id: number; + isActive: boolean; + isStaff: boolean; + isSuperuser: boolean; + lastname: string; + uid: string; + username: string; +} diff --git a/src/schema/README.md b/src/schema/README.md new file mode 100644 index 00000000..f9911599 --- /dev/null +++ b/src/schema/README.md @@ -0,0 +1,4 @@ +# JSON schemas +This directory contains JSON schemas for the various types of data that are used in the project. +The directory is organised following OpenAPI 3.0 specification, with each schema belonging to a specific category. +See the supported categories here: https://spec.openapis.org/oas/v3.0.0.html#fixed-fields-5 \ No newline at end of file diff --git a/src/schema/collectable-items/response.json b/src/schema/collectable-items/response.json deleted file mode 100644 index 6f8e524a..00000000 --- a/src/schema/collectable-items/response.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/collectable-items/response.json", - "type": "object", - "title": "CollectableItemsSearchResponse", - "description": "Collectable Items Search Response", - "additionalProperties": false, - "required": ["data", "limit", "skip", "total", "toBeResolved", "resolved"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/article.json" - } - }, - "limit": { - "type": "integer", - "description": "The number of articles returned in this response" - }, - "skip": { - "type": "integer", - "description": "The number of articles skipped in this response" - }, - "total": { - "type": "integer", - "description": "The total number of articles matching the query" - }, - "toBeResolved": { - "type": "array", - "description": "TBD." - }, - "resolved": { - "type": "array", - "description": "TBD." - } - } -} diff --git a/src/schema/collections/findResponse.json b/src/schema/collections/findResponse.json deleted file mode 100644 index 0996d5f0..00000000 --- a/src/schema/collections/findResponse.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/collections/findResponse.json", - "type": "object", - "title": "Collections search response", - "description": "Collections search response", - "additionalProperties": false, - "required": ["data", "limit", "skip", "total", "info"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/models/collection.model.json" - } - }, - "limit": { - "type": "integer", - "description": "The number of items returned in this response" - }, - "skip": { - "type": "integer", - "description": "The number of items skipped in this response" - }, - "total": { - "type": "integer", - "description": "The total number of items matching the query" - }, - "info": { - "type": "object", - "description": "Additional information about the search response.", - "properties": {} - } - } -} diff --git a/src/services/text-reuse-passages/schema/addons.json b/src/schema/parameters/textReusePassagesAddOns.json similarity index 61% rename from src/services/text-reuse-passages/schema/addons.json rename to src/schema/parameters/textReusePassagesAddOns.json index 1da13ac2..bdbbb90d 100644 --- a/src/services/text-reuse-passages/schema/addons.json +++ b/src/schema/parameters/textReusePassagesAddOns.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Validate add-ons.", + "description": "Add-ons for text reuse passages find method.", + "title": "TextReusePassagesFindAddOns", "type": "object", "properties": { "newspaper": {} diff --git a/src/schema/authentication/request.json b/src/schema/requestBodies/authenticationCreate.json similarity index 62% rename from src/schema/authentication/request.json rename to src/schema/requestBodies/authenticationCreate.json index 0d34ea5a..b39e1993 100644 --- a/src/schema/authentication/request.json +++ b/src/schema/requestBodies/authenticationCreate.json @@ -1,8 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/authencation/request.json", - "title": "Authentication Request", - "description": "Authentication request", + "title": "Authentication Create Request", "type": "object", "additionalProperties": false, "properties": { diff --git a/src/schema/collections/newCollection.json b/src/schema/requestBodies/collectionsCreate.json similarity index 84% rename from src/schema/collections/newCollection.json rename to src/schema/requestBodies/collectionsCreate.json index b17053bc..0c48fa07 100644 --- a/src/schema/collections/newCollection.json +++ b/src/schema/requestBodies/collectionsCreate.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/collections/newCollection.json", "title": "New Collection", "type": "object", "additionalProperties": false, diff --git a/src/schema/responses/articlesFind.json b/src/schema/responses/articlesFind.json new file mode 100644 index 00000000..8ac59fff --- /dev/null +++ b/src/schema/responses/articlesFind.json @@ -0,0 +1,3 @@ +{ + "$ref": "searchFind.json" +} diff --git a/src/schema/responses/articlesGet.json b/src/schema/responses/articlesGet.json new file mode 100644 index 00000000..51a501f3 --- /dev/null +++ b/src/schema/responses/articlesGet.json @@ -0,0 +1,3 @@ +{ + "$ref": "../schemas/Article.json" +} diff --git a/src/schema/authentication/response.json b/src/schema/responses/authenticationCreate.json similarity index 55% rename from src/schema/authentication/response.json rename to src/schema/responses/authenticationCreate.json index 64d7896d..c1c4c0fc 100644 --- a/src/schema/authentication/response.json +++ b/src/schema/responses/authenticationCreate.json @@ -1,8 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/authencation/response.json", "title": "Authentication Response", - "description": "Authentication response", "type": "object", "additionalProperties": false, "properties": { @@ -21,13 +19,8 @@ } }, "user": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/authencation/user.json" + "$ref": "../schemas/User.json" } }, - "required": [ - "accessToken", - "authentication", - "payload", - "user" - ] -} \ No newline at end of file + "required": ["accessToken", "authentication", "user"] +} diff --git a/src/schema/common/findResponse.json b/src/schema/responses/baseFind.json similarity index 69% rename from src/schema/common/findResponse.json rename to src/schema/responses/baseFind.json index 2b34281f..96938e37 100644 --- a/src/schema/common/findResponse.json +++ b/src/schema/responses/baseFind.json @@ -1,13 +1,12 @@ { - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/common/findResponse.json", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "title": "Default Response", + "title": "Base find response", "additionalProperties": false, "required": ["data", "limit", "skip", "total", "info"], "properties": { "data": { - "type": "array", - "items": {} + "type": "array" }, "limit": { "type": "integer", @@ -23,7 +22,7 @@ }, "info": { "type": "object", - "description": "Additional information about the find response.", + "description": "Additional information about the response.", "properties": {} } } diff --git a/src/schema/responses/collectableItemsFind.json b/src/schema/responses/collectableItemsFind.json new file mode 100644 index 00000000..e74ed003 --- /dev/null +++ b/src/schema/responses/collectableItemsFind.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "baseFind.json", + "unevaluatedProperties": false, + "type": "object", + "title": "Collectable Item Find Response", + "description": "Collectable Item find response", + "additionalProperties": false, + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "../schemas/CollectableItemGroup.json" + } + } + } +} diff --git a/src/schema/responses/collectionsCreate.json b/src/schema/responses/collectionsCreate.json new file mode 100644 index 00000000..966b89a8 --- /dev/null +++ b/src/schema/responses/collectionsCreate.json @@ -0,0 +1,3 @@ +{ + "$ref": "../schemas/Collection.json" +} diff --git a/src/schema/responses/collectionsFind.json b/src/schema/responses/collectionsFind.json new file mode 100644 index 00000000..3dfbd09b --- /dev/null +++ b/src/schema/responses/collectionsFind.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "baseFind.json", + "unevaluatedProperties": false, + "type": "object", + "title": "Collections find response", + "description": "Collections find response", + "additionalProperties": false, + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "../schemas/Collection.json" + } + } + } +} diff --git a/src/schema/responses/collectionsGet.json b/src/schema/responses/collectionsGet.json new file mode 100644 index 00000000..966b89a8 --- /dev/null +++ b/src/schema/responses/collectionsGet.json @@ -0,0 +1,3 @@ +{ + "$ref": "../schemas/Collection.json" +} diff --git a/src/schema/collections/removeResponse.json b/src/schema/responses/collectionsRemove.json similarity index 88% rename from src/schema/collections/removeResponse.json rename to src/schema/responses/collectionsRemove.json index 22ce558f..befc74b8 100644 --- a/src/schema/collections/removeResponse.json +++ b/src/schema/responses/collectionsRemove.json @@ -1,5 +1,5 @@ { - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/collections/removeResponse.json", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Remove Collection Response", "description": "Remove collection response", diff --git a/src/schema/responses/pagination.json b/src/schema/responses/pagination.json new file mode 100644 index 00000000..44878eef --- /dev/null +++ b/src/schema/responses/pagination.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Pagination", + "description": "TODO: review this schema", + "type": "object", + "properties": { + "limit": { + "type": "integer", + "description": "Limit to this many items", + "default": 10, + "minimum": 0, + "maximum": 10000 + }, + "offset": { + "type": "integer", + "description": "Skip this many items", + "minimum": 0 + }, + "skip": { + "type": "integer", + "description": "Skip this many items", + "minimum": 0 + }, + "page": { + "type": "integer", + "description": "Display N-th page (using 'limit' as the number of items in the page)", + "minimum": 1 + }, + "total": { + "type": "integer", + "description": "Total items available", + "minimum": 0 + } + } +} diff --git a/src/schema/responses/searchFind.json b/src/schema/responses/searchFind.json new file mode 100644 index 00000000..709fdd8d --- /dev/null +++ b/src/schema/responses/searchFind.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "baseFind.json", + "unevaluatedProperties": false, + "type": "object", + "title": "Search Find Response", + "description": "Search find response (articles)", + "additionalProperties": false, + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "../schemas/Article.json" + } + } + } +} diff --git a/src/schema/responses/textReuseClusterFind.json b/src/schema/responses/textReuseClusterFind.json new file mode 100644 index 00000000..c10601b2 --- /dev/null +++ b/src/schema/responses/textReuseClusterFind.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Find TextReuseClusters Response", + "type": "object", + "additionalProperties": false, + "properties": { + "clusters": { + "type": "array", + "items": { + "$ref": "./textReuseClusterGet.json" + } + }, + "info": { + "$ref": "./pagination.json" + } + }, + "required": ["clusters", "info"] +} diff --git a/src/schema/responses/textReuseClusterGet.json b/src/schema/responses/textReuseClusterGet.json new file mode 100644 index 00000000..fd00953f --- /dev/null +++ b/src/schema/responses/textReuseClusterGet.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Get TextReuseCluster Response", + "type": "object", + "additionalProperties": false, + "properties": { + "cluster": { + "$ref": "../schemas/TextReuseCluster.json" + }, + "textSample": { + "type": "string" + }, + "details": { + "$ref": "../schemas/TextReuseClusterDetails.json" + } + }, + "required": ["cluster", "textSample"] +} diff --git a/src/schema/responses/textReusePassagesFind.json b/src/schema/responses/textReusePassagesFind.json new file mode 100644 index 00000000..13c55613 --- /dev/null +++ b/src/schema/responses/textReusePassagesFind.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "baseFind.json", + "unevaluatedProperties": false, + "type": "object", + "title": "Text Reuse Passage Find Response", + "description": "Collections find response", + "additionalProperties": false, + "required": ["data"], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "../schemas/TextReusePassage.json" + } + } + } +} diff --git a/src/schema/responses/textReusePassagesGet.json b/src/schema/responses/textReusePassagesGet.json new file mode 100644 index 00000000..29df90f4 --- /dev/null +++ b/src/schema/responses/textReusePassagesGet.json @@ -0,0 +1,3 @@ +{ + "$ref": "../schemas/TextReusePassage.json" +} diff --git a/src/schema/version/response.json b/src/schema/responses/versionFind.json similarity index 93% rename from src/schema/version/response.json rename to src/schema/responses/versionFind.json index bb08bc8d..05e68554 100644 --- a/src/schema/version/response.json +++ b/src/schema/responses/versionFind.json @@ -1,5 +1,5 @@ { - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/version/response.json", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "API Version", "description": "Version of the API. Contains information about the current version of the API, features, etc.", diff --git a/src/schema/search/article.json b/src/schema/schemas/Article.json similarity index 66% rename from src/schema/search/article.json rename to src/schema/schemas/Article.json index 10445a56..e4117f62 100644 --- a/src/schema/search/article.json +++ b/src/schema/schemas/Article.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/article.json", "title": "Article", "description": "A journal/magazine article", "type": "object", @@ -27,9 +26,9 @@ "description": "The number of pages in this article" }, "pages": { - "type":"array", + "type": "array", "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/page.json" + "$ref": "Page.json" } }, "isCC": { @@ -43,17 +42,15 @@ "locations": { "type": "array", "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/entity.json" + "$ref": "Entity.json" } }, "persons": { "type": "array", "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/entity.json" + "$ref": "Entity.json" } } }, - "required": [ - "uid", "type", "title", "size", "nbPages", "pages", "isCC", "excerpt" - ] -} \ No newline at end of file + "required": ["uid", "type", "title", "size", "nbPages", "pages", "isCC", "excerpt"] +} diff --git a/src/schema/schemas/BaseUser.json b/src/schema/schemas/BaseUser.json new file mode 100644 index 00000000..9fa9f7f2 --- /dev/null +++ b/src/schema/schemas/BaseUser.json @@ -0,0 +1,25 @@ +{ + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Base User", + "required": ["uid", "username"], + "properties": { + "uid": { + "$id": "#/properties/uid", + "type": "string", + "title": "unique identifier for the user", + "default": "", + "examples": ["local-dg"], + "pattern": "^([a-zA-Z-]+)$" + }, + "username": { + "$id": "#/properties/username", + "type": "string", + "title": "unique username for the user for other humans", + "default": "", + "examples": ["daniele.guido"], + "pattern": "^([a-z.]+)$" + } + } +} diff --git a/src/schema/collectable-items/CollectableItemGroup.json b/src/schema/schemas/CollectableItemGroup.json similarity index 78% rename from src/schema/collectable-items/CollectableItemGroup.json rename to src/schema/schemas/CollectableItemGroup.json index 5c65b2c7..0d6b5f66 100644 --- a/src/schema/collectable-items/CollectableItemGroup.json +++ b/src/schema/schemas/CollectableItemGroup.json @@ -1,8 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/collectable-items/CollectableItemGroup.json", "description": "Collectable item group object", - "title": "CollectableItemGroup", + "title": "Collectable Item Group", "type": "object", "properties": { "itemId": { @@ -31,7 +30,7 @@ "collections": { "type": "array", "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/models/collection.model.json" + "$ref": "./Collection.json" }, "description": "Collection objects" }, diff --git a/src/schema/schemas/Collection.json b/src/schema/schemas/Collection.json new file mode 100644 index 00000000..2dd15694 --- /dev/null +++ b/src/schema/schemas/Collection.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Collection", + "description": "Description of the collection object (Collection class)", + "type": "object", + "additionalProperties": false, + "required": ["uid", "name", "description", "status", "creationDate", "lastModifiedDate", "countItems", "creator"], + "properties": { + "uid": { + "type": "string", + "minLength": 2, + "maxLength": 50 + }, + "name": { + "type": "string", + "minLength": 2, + "maxLength": 50 + }, + "description": { + "type": "string", + "maxlength": 500 + }, + "status": { + "type": "string", + "minLength": 2, + "maxLength": 3, + "title": "Status of the commection", + "examples": ["PRI", "DEL"] + }, + "creationDate": { + "type": "string" + }, + "lastModifiedDate": { + "type": "string" + }, + "countItems": { + "type": ["integer", "string"], + "title": "Number of items in the collection", + "default": 0, + "examples": [3245] + }, + "creator": { + "$ref": "./BaseUser.json" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/src/schema/search/entity.json b/src/schema/schemas/Entity.json similarity index 81% rename from src/schema/search/entity.json rename to src/schema/schemas/Entity.json index d3287327..340724ca 100644 --- a/src/schema/search/entity.json +++ b/src/schema/schemas/Entity.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/entity.json", "description": "An entity like location, person, etc", "type": "object", "title": "Entity", @@ -16,4 +15,4 @@ } }, "required": ["uid", "relevance"] -} \ No newline at end of file +} diff --git a/src/schema/common/defaultErrorResponse.json b/src/schema/schemas/Error.json similarity index 62% rename from src/schema/common/defaultErrorResponse.json rename to src/schema/schemas/Error.json index ee6372da..f3a97995 100644 --- a/src/schema/common/defaultErrorResponse.json +++ b/src/schema/schemas/Error.json @@ -1,7 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/common/defaultErrorResponse.json", - "description": "Default error response", + "description": "Default error response. TODO: replace with https://datatracker.ietf.org/doc/html/rfc9457", "title": "Error", "type": "object", "required": ["message"], diff --git a/src/schema/schemas/Filter.json b/src/schema/schemas/Filter.json new file mode 100644 index 00000000..1e4d3bfb --- /dev/null +++ b/src/schema/schemas/Filter.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Filter", + "description": "A single filter criteria", + "type": "object", + "additionalProperties": false, + "properties": { + "context": { + "type": "string", + "enum": ["include", "exclude"], + "default": "include" + }, + "op": { + "type": "string", + "enum": ["AND", "OR"], + "default": "OR" + }, + "type": { + "type": "string", + "description": "Possible values are in 'search.validators:eachFilterValidator.type.choices'" + }, + "precision": { + "type": "string", + "enum": ["fuzzy", "soft", "exact", "partial"], + "default": "exact" + }, + "q": { + "anyOf": [ + { + "type": "string", + "minLength": 2, + "maxLength": 500 + }, + { + "type": "array", + "items": { + "type": "string", + "minLength": 2, + "maxLength": 500 + } + } + ] + }, + "daterange": { + "type": "string", + "pattern": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z TO \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z" + }, + "uids": { + "type": "string" + }, + "uid": { + "type": "string" + } + }, + "required": ["type"] +} diff --git a/src/schema/search/page.json b/src/schema/schemas/Page.json similarity index 94% rename from src/schema/search/page.json rename to src/schema/schemas/Page.json index 1f9f55e8..bddca8fb 100644 --- a/src/schema/search/page.json +++ b/src/schema/schemas/Page.json @@ -1,5 +1,5 @@ { - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/page.json", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "Page", "description": "A page of an article", @@ -9,7 +9,7 @@ "num", "issueUid", "newspaperUid", - "iif", + "iiif", "iiifThumbnail", "accessRights", "labels", diff --git a/src/schema/schemas/TextReuseCluster.json b/src/schema/schemas/TextReuseCluster.json new file mode 100644 index 00000000..bed10192 --- /dev/null +++ b/src/schema/schemas/TextReuseCluster.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Text Reuse Cluster", + "description": "Represents a cluster of text reuse passages", + "additionalProperties": false, + "required": ["id"], + "properties": { + "id": { + "type": "string", + "title": "Passage ID", + "description": "ID of the text reuse passage", + "examples": ["abc123"], + "pattern": "^[a-zA-Z0-9-_]+$" + }, + "lexicalOverlap": { + "type": "number", + "description": "Percentage of overlap between passages in the cluster", + "minimum": 0, + "maximum": 100 + }, + "clusterSize": { + "type": "number", + "description": "Number of passages in cluster", + "minimum": 0 + }, + "connectedClustersCount": { + "type": "number", + "description": "Number of connected clusters", + "minimum": 0 + }, + "timeCoverage": { + "type": "object", + "description": "Time window covered by documents in the cluster", + "additionalProperties": false, + "properties": { + "from": { + "type": "string", + "format": "date" + }, + "to": { + "type": "string", + "format": "date" + } + } + } + } +} diff --git a/src/schema/schemas/TextReuseClusterDetails.json b/src/schema/schemas/TextReuseClusterDetails.json new file mode 100644 index 00000000..9967a650 --- /dev/null +++ b/src/schema/schemas/TextReuseClusterDetails.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Text Reuse Cluster Details", + "description": "Extra details of the cluster", + "additionalProperties": false, + "required": ["facets"], + "properties": { + "facets": { + "type": "array", + "items": { + "$ref": "#/definitions/facet" + } + }, + "resolution": { + "type": "string", + "enum": ["year", "month", "day"], + "description": "Resolution for the 'date' facet" + } + }, + "definitions": { + "facet": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "description": "Facet type" + }, + "numBuckets": { + "type": "integer", + "description": "Number of buckets" + }, + "buckets": { + "type": "array", + "items": { + "type": "object", + "description": "TODO: define bucket fields" + } + } + } + } + } +} diff --git a/src/services/text-reuse-passages/schema/passage.json b/src/schema/schemas/TextReusePassage.json similarity index 100% rename from src/services/text-reuse-passages/schema/passage.json rename to src/schema/schemas/TextReusePassage.json diff --git a/src/schema/authentication/user.json b/src/schema/schemas/User.json similarity index 84% rename from src/schema/authentication/user.json rename to src/schema/schemas/User.json index 50e54cc9..632f223d 100644 --- a/src/schema/authentication/user.json +++ b/src/schema/schemas/User.json @@ -1,6 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/authencation/user.json", "title": "User", "description": "User details", "type": "object", @@ -14,7 +13,6 @@ "isActive": { "type": "boolean" }, "isSuperuser": { "type": "boolean" }, "uid": { "type": "string" } - }, "required": ["id", "username", "firstname", "lastname", "isStaff", "isActive", "isSuperuser", "uid"] -} \ No newline at end of file +} diff --git a/src/schema/search/response.json b/src/schema/search/response.json deleted file mode 100644 index b269418d..00000000 --- a/src/schema/search/response.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/response.json", - "type": "object", - "title": "Article Search Response", - "description": "Article search response", - "additionalProperties": false, - "required": ["data", "limit", "skip", "total", "info"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/search/article.json" - } - }, - "limit": { - "type": "integer", - "description": "The number of articles returned in this response" - }, - "skip": { - "type": "integer", - "description": "The number of articles skipped in this response" - }, - "total": { - "type": "integer", - "description": "The total number of articles matching the query" - }, - "info": { - "type": "object", - "description": "Additional information about the search response.", - "properties": {} - } - } -} diff --git a/src/services/articles/articles.schema.ts b/src/services/articles/articles.schema.ts index c2d6859d..a100e601 100644 --- a/src/services/articles/articles.schema.ts +++ b/src/services/articles/articles.schema.ts @@ -80,7 +80,7 @@ export const docs: ServiceSwaggerOptions = { parameters: findParameters, responses: getStandardResponses({ method: 'find', - schema: 'searchResponseSchema', + schema: 'articlesFind', }), }, get: { @@ -98,7 +98,7 @@ export const docs: ServiceSwaggerOptions = { ], responses: getStandardResponses({ method: 'get', - schema: 'article', + schema: 'articlesGet', }), }, }, diff --git a/src/services/authentication/authentication.schema.ts b/src/services/authentication/authentication.schema.ts index 29204e58..7af8f186 100644 --- a/src/services/authentication/authentication.schema.ts +++ b/src/services/authentication/authentication.schema.ts @@ -1,34 +1,21 @@ import { ServiceSwaggerOptions } from 'feathers-swagger' - -const authRequestSchema = require('../../schema/authentication/request.json') - -const authResponseSchema = require('../../schema/authentication/response.json') -authResponseSchema.properties.user.$ref = '#/components/schemas/user' - -const userSchema = require('../../schema/authentication/user.json') -userSchema.$id = 'user' - -const defaultErrorResponseSchema = require('../../schema/common/defaultErrorResponse.json') -defaultErrorResponseSchema.$id = '#/components/schemas/defaultErrorResponse' - -export { authRequestSchema, authResponseSchema, userSchema } +import { getRequestBodyContent, getResponseContent } from '../../util/openapi' export const docs: ServiceSwaggerOptions = { description: 'Issue a token for the user', securities: ['create'], - schemas: { - user: userSchema, - authRequestSchema, - authResponseSchema, - defaultErrorResponse: defaultErrorResponseSchema, - }, - refs: { - createRequest: 'authRequestSchema', - createResponse: 'authResponseSchema', - }, operations: { create: { description: 'Authenticate user', + requestBody: { + content: getRequestBodyContent('authenticationCreate'), + }, + responses: { + 201: { + description: 'Authentication successful', + content: getResponseContent('authenticationCreate'), + }, + }, }, }, } diff --git a/src/services/collectable-items/collectable-items.schema.ts b/src/services/collectable-items/collectable-items.schema.ts index 2e41b7d1..00b9a633 100644 --- a/src/services/collectable-items/collectable-items.schema.ts +++ b/src/services/collectable-items/collectable-items.schema.ts @@ -1,22 +1,7 @@ import { ServiceSwaggerOptions } from 'feathers-swagger' -import { QueryParameter } from '../../util/openapi' +import { QueryParameter, getStandardResponses } from '../../util/openapi' import { REGEX_UIDS } from '../../hooks/params' -const baseUserSchema = require('../../schema/models/base-user.model.json') -baseUserSchema.$id = '#/components/schemas/base-user' - -const collectionSchema = require('../../schema/models/collection.model.json') -collectionSchema.$id = '#/components/schemas/collection' -collectionSchema.properties.creator.$ref = '#/components/schemas/baseUser' - -const collectableItemGroupSchema = require('../../schema/collectable-items/CollectableItemGroup.json') -collectableItemGroupSchema.$id = '#/components/schemas/CollectableItemGroup' -collectableItemGroupSchema.properties.collections.items.$ref = '#/components/schemas/collection' - -const collectableItemsFindResponseSchema = require('../../schema/collectable-items/response.json') -collectableItemsFindResponseSchema.$id = '#/components/schemas/findResponse' -collectableItemsFindResponseSchema.properties.data.items.$ref = '#/components/schemas/CollectableItemGroup' - const findParameters: QueryParameter[] = [ { in: 'query', @@ -89,17 +74,14 @@ const findParameters: QueryParameter[] = [ export const docs: ServiceSwaggerOptions = { description: 'Collectable items', securities: ['find', 'create', 'remove'], - schemas: { - collectableItemsFindResponseSchema, - baseUser: baseUserSchema, - collection: collectionSchema, - CollectableItemGroup: collectableItemGroupSchema, - }, - refs: { findResponse: 'collectableItemsFindResponseSchema' }, operations: { find: { description: 'Find collectable items that match the given query', parameters: findParameters, + responses: getStandardResponses({ + method: 'find', + schema: 'collectableItemsFind', + }), }, }, } diff --git a/src/services/collectable-items/collectable-items.service.js b/src/services/collectable-items/collectable-items.service.js index 0beef06f..b35cf7ac 100644 --- a/src/services/collectable-items/collectable-items.service.js +++ b/src/services/collectable-items/collectable-items.service.js @@ -1,30 +1,30 @@ -// import { createSwaggerServiceOptions } from 'feathers-swagger'; -import { optionsDisabledInPublicApi } from '../../hooks/public-api'; -// import { docs } from './collectable-items.schema'; +import { createSwaggerServiceOptions } from 'feathers-swagger' +import { optionsDisabledInPublicApi } from '../../hooks/public-api' +import { docs } from './collectable-items.schema' // Initializes the `collectable-items` service on path `/collectable-items` -const createService = require('./collectable-items.class.js'); -const hooks = require('./collectable-items.hooks'); +const createService = require('./collectable-items.class.js') +const hooks = require('./collectable-items.hooks') module.exports = function (app) { - const paginate = app.get('paginate'); + const paginate = app.get('paginate') const options = { name: 'collectable-items', paginate, app, - }; + } // Initialize our service with any options it requires app.use('/collectable-items', createService(options), { ...optionsDisabledInPublicApi(app), events: [], // disabled for now while it's not public - // docs: createSwaggerServiceOptions({ schemas: {}, docs }), - }); + docs: createSwaggerServiceOptions({ schemas: {}, docs }), + }) // Get our initialized service so that we can register hooks - const service = app.service('collectable-items'); + const service = app.service('collectable-items') - service.hooks(hooks); -}; + service.hooks(hooks) +} diff --git a/src/services/collections/collections.schema.ts b/src/services/collections/collections.schema.ts index 367798fa..2702c943 100644 --- a/src/services/collections/collections.schema.ts +++ b/src/services/collections/collections.schema.ts @@ -1,25 +1,8 @@ import type { ServiceSwaggerOptions } from 'feathers-swagger' import type { QueryParameter } from '../../util/openapi' -import { getStandardResponses, jsonSchemaRef } from '../../util/openapi' +import { getRequestBodyContent, getStandardResponses } from '../../util/openapi' import { REGEX_UIDS } from '../../hooks/params' -const baseUserSchema = require('../../schema/models/base-user.model.json') -baseUserSchema.$id = 'baseUser' - -const collectionSchema = require('../../schema/models/collection.model.json') -collectionSchema.$id = 'collection' -collectionSchema.properties.creator.$ref = '#/components/schemas/baseUser' - -const collectionFindResponseSchema = require('../../schema/collections/findResponse.json') -collectionFindResponseSchema.$id = 'collectionFindResponse' -collectionFindResponseSchema.properties.data.items.$ref = '#/components/schemas/collection' - -const newCollectionSchema = require('../../schema/collections/newCollection.json') -newCollectionSchema.$id = 'newCollection' - -const collectionRemoveResponseSchema = require('../../schema/collections/removeResponse.json') -collectionRemoveResponseSchema.$id = 'collectionRemoveResponse' - const findParameters: QueryParameter[] = [ { in: 'query', @@ -57,20 +40,13 @@ const findParameters: QueryParameter[] = [ export const docs: ServiceSwaggerOptions = { description: 'Collections', securities: ['find', 'get', 'create', 'patch', 'remove'], - schemas: { - baseUser: baseUserSchema, - collection: collectionSchema, - collectionFindResponseSchema, - newCollection: newCollectionSchema, - collectionRemoveResponseSchema, - }, operations: { find: { description: 'Find collections', parameters: findParameters, responses: getStandardResponses({ method: 'find', - schema: 'collectionFindResponseSchema', + schema: 'collectionsFind', }), }, get: { @@ -88,17 +64,17 @@ export const docs: ServiceSwaggerOptions = { ], responses: getStandardResponses({ method: 'get', - schema: 'collection', + schema: 'collectionsGet', }), }, create: { description: 'Create a new collection', requestBody: { - content: jsonSchemaRef('newCollection'), + content: getRequestBodyContent('collectionsCreate'), }, responses: getStandardResponses({ method: 'create', - schema: 'collection', + schema: 'collectionsCreate', }), }, patch: { @@ -115,11 +91,11 @@ export const docs: ServiceSwaggerOptions = { }, ], requestBody: { - content: jsonSchemaRef('newCollection'), + content: getRequestBodyContent('collectionsCreate'), }, responses: getStandardResponses({ method: 'patch', - schema: 'collection', + schema: 'collectionsCreate', }), }, remove: { @@ -137,7 +113,7 @@ export const docs: ServiceSwaggerOptions = { ], responses: getStandardResponses({ method: 'remove', - schema: 'collectionRemoveResponseSchema', + schema: 'collectionsRemove', }), }, }, diff --git a/src/services/collections/schema/post/payload.json b/src/services/collections/schema/post/payload.json deleted file mode 100644 index 053ba2c5..00000000 --- a/src/services/collections/schema/post/payload.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/services/collections/schema/post/payload.json", - "description": "Request payload for POST /collections", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/models/collection.model.json#/properties/name" - }, - "description": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/models/collection.model.json#/properties/description" - }, - "status": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/models/collection.model.json#/properties/status" - } - }, - "required": [ - "name" - ] -} diff --git a/src/services/schemas.ts b/src/services/schemas.ts new file mode 100644 index 00000000..132efa0f --- /dev/null +++ b/src/services/schemas.ts @@ -0,0 +1,23 @@ +import { Application } from '@feathersjs/express' +import type { Request, Response } from 'express' +import { ImpressoApplication } from '../types' + +/** + * This service is only available in the public API. + * It serves the JSON schema files from the `schema` directory. + */ +export default (app: ImpressoApplication & Application) => { + // only in public API + if (!app.get('isPublicApi')) return + + app.use('/schema/', async (req: Request, res: Response) => { + const filename = req.path + + // send file from disk + res.sendFile(filename, { + root: `${__dirname}/../schema`, + dotfiles: 'deny', + headers: { 'Content-Type': 'application/json' }, + }) + }) +} diff --git a/src/services/search/search.schema.ts b/src/services/search/search.schema.ts index 5349a1ab..8e58f04c 100644 --- a/src/services/search/search.schema.ts +++ b/src/services/search/search.schema.ts @@ -1,33 +1,9 @@ import { ServiceSwaggerOptions } from 'feathers-swagger' import { SolrMappings } from '../../data/constants' import type { QueryParameter } from '../../util/openapi' -import { getStandardResponses } from '../../util/openapi' +import { getSchemaRef, getStandardResponses } from '../../util/openapi' import { paramsValidator } from './search.validators' -const filterSchema = require('../../schema/search/filter.json') -const articleSchema = require('../../schema/search/article.json') -articleSchema.$id = '#/components/schemas/article' -articleSchema.properties.pages.items.$ref = '#/components/schemas/page' -articleSchema.properties.locations.items.$ref = '#/components/schemas/entity' -articleSchema.properties.persons.items.$ref = '#/components/schemas/entity' - -const pageSchema = require('../../schema/search/page.json') -pageSchema.$id = '#/components/schemas/page' - -const entitySchema = require('../../schema/search/entity.json') -entitySchema.$id = '#/components/schemas/entity' - -const searchResponseSchema = require('../../schema/search/response.json') -searchResponseSchema.properties.data.items.$ref = '#/components/schemas/article' - -// const articleListSchema = { -// title: 'Article list', -// type: 'array', -// items: { $ref: '#/components/schemas/article' } -// }; - -export { articleSchema, entitySchema, pageSchema } - const findParameters: QueryParameter[] = [ { in: 'query', @@ -77,7 +53,7 @@ const findParameters: QueryParameter[] = [ required: false, schema: { type: 'array', - items: filterSchema, + items: getSchemaRef('Filter'), }, description: 'Filters to apply', }, @@ -110,15 +86,13 @@ const findParameters: QueryParameter[] = [ export const docs: ServiceSwaggerOptions = { description: 'Search articles', securities: ['find'], - schemas: { entity: entitySchema, page: pageSchema, article: articleSchema, searchResponseSchema }, - // refs: { findResponse: 'searchResponseSchema' }, operations: { find: { description: 'Find articles that match the given query', parameters: findParameters, responses: getStandardResponses({ method: 'find', - schema: 'searchResponseSchema', + schema: 'searchFind', }), }, }, diff --git a/src/services/text-reuse-clusters/text-reuse-clusters.schema.ts b/src/services/text-reuse-clusters/text-reuse-clusters.schema.ts index 387889f3..271934f2 100644 --- a/src/services/text-reuse-clusters/text-reuse-clusters.schema.ts +++ b/src/services/text-reuse-clusters/text-reuse-clusters.schema.ts @@ -1,24 +1,9 @@ import type { ServiceSwaggerOptions } from 'feathers-swagger' import type { MethodParameter } from '../../util/openapi' -import { getFindResponse, getStandardParameters, getStandardResponses } from '../../util/openapi' +import { getStandardParameters, getStandardResponses } from '../../util/openapi' import { OrderByKeyToField } from './text-reuse-clusters.class' import { Filter } from '../../models' -const cluster = require('../../schema/models/text-reuse/cluster.json') -cluster.$id = 'cluster' -const clusterDetails = require('../../schema/models/text-reuse/clusterDetails.json') -cluster.$id = 'clusterDetails' - -const clusterGetResponse = require('./schema/get/response.json') -clusterGetResponse.$id = 'clusterGetResponse' -clusterGetResponse.properties.cluster.$ref = '#/components/schemas/cluster' -clusterGetResponse.properties.details.$ref = '#/components/schemas/clusterDetails' - -const clusterFindResponse = require('./schema/find/response.json') -clusterFindResponse.$id = 'clusterFindResponse' -clusterFindResponse.properties.clusters.items.$ref = '#/components/schemas/clusterGetResponse' -clusterFindResponse.properties.info.$ref = '#/components/schemas/pagination' - export interface FindQueyParameters { text?: string skip?: number @@ -78,19 +63,13 @@ const getParameters: MethodParameter[] = [ export const docs: ServiceSwaggerOptions = { description: 'Text Reuse Clusters', securities: ['find', 'get'], - schemas: { - cluster, - clusterDetails, - clusterGetResponse, - clustersFindResponse: getFindResponse({ itemRef: 'cluster', title: cluster.title }), - }, operations: { find: { description: 'Find text reuse clusters', parameters: findParameters, responses: getStandardResponses({ method: 'find', - schema: 'clustersFindResponse', + schema: 'textReuseClustersFind', }), }, get: { @@ -98,7 +77,7 @@ export const docs: ServiceSwaggerOptions = { parameters: getParameters, responses: getStandardResponses({ method: 'get', - schema: 'clusterGetResponse', + schema: 'textReuseClustersGet', }), }, }, diff --git a/src/services/text-reuse-passages/schema/findResponse.json b/src/services/text-reuse-passages/schema/findResponse.json deleted file mode 100644 index 66b8f20e..00000000 --- a/src/services/text-reuse-passages/schema/findResponse.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$id": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/services/text-reuse-passages/findResponse.json", - "type": "object", - "title": "Text Reuse Passage search response", - "additionalProperties": false, - "required": ["data", "limit", "skip", "total", "info"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "https://github.com/impresso/impresso-middle-layer/tree/master/src/schema/services/text-reuse-passages/passage.model.json" - } - }, - "limit": { - "type": "integer", - "description": "The number of items returned in this response" - }, - "skip": { - "type": "integer", - "description": "The number of items skipped in this response" - }, - "total": { - "type": "integer", - "description": "The total number of items matching the query" - }, - "info": { - "type": "object", - "description": "Additional information about the search response.", - "properties": {} - } - } -} diff --git a/src/services/text-reuse-passages/text-reuse-passages.hooks.js b/src/services/text-reuse-passages/text-reuse-passages.hooks.js index 8d0dd785..b5cf8e12 100644 --- a/src/services/text-reuse-passages/text-reuse-passages.hooks.js +++ b/src/services/text-reuse-passages/text-reuse-passages.hooks.js @@ -1,8 +1,8 @@ import { authenticateAround as authenticate } from '../../hooks/authenticate' import { decodeJsonQueryParameters, decodePathParameters } from '../../hooks/parameters' import { rateLimit } from '../../hooks/rateLimiter' -import { validateParameters } from '../../util/openapi' -import { docs } from './text-reuse-passages.schema' +// import { validateParameters } from '../../util/openapi' +// import { docs } from './text-reuse-passages.schema' module.exports = { around: { @@ -11,11 +11,11 @@ module.exports = { before: { get: [ decodePathParameters(['id']), // - validateParameters(docs.operations.get.parameters), // + // validateParameters(docs.operations.get.parameters), // ], find: [ decodeJsonQueryParameters(['filters', 'addons']), // - validateParameters(docs.operations.find.parameters), // + // validateParameters(docs.operations.find.parameters), // ], }, } diff --git a/src/services/text-reuse-passages/text-reuse-passages.schema.ts b/src/services/text-reuse-passages/text-reuse-passages.schema.ts index 4325818a..51aa5aae 100644 --- a/src/services/text-reuse-passages/text-reuse-passages.schema.ts +++ b/src/services/text-reuse-passages/text-reuse-passages.schema.ts @@ -1,10 +1,8 @@ import type { ServiceSwaggerOptions } from 'feathers-swagger' import type { MethodParameter } from '../../util/openapi' -import { getFindResponse, getStandardParameters, getStandardResponses } from '../../util/openapi' +import { getParameterRef, getSchemaRef, getStandardParameters, getStandardResponses } from '../../util/openapi' import { GroupByValues, OrderByKeyToField } from './text-reuse-passages.class' -const passage = require('./schema/passage.json') - const findParameters: MethodParameter[] = [ { in: 'query', @@ -34,7 +32,7 @@ const findParameters: MethodParameter[] = [ required: false, schema: { type: 'array', - items: require('../../schema/filter.json'), + items: getSchemaRef('Filter'), }, description: 'Filters to apply', }, @@ -42,10 +40,7 @@ const findParameters: MethodParameter[] = [ in: 'query', name: 'addons', required: false, - schema: { - // TODO: this can be turned into a basic type parameter - ...require('./schema/addons.json'), - }, + schema: getParameterRef('textReusePassagesAddOns'), description: 'Add-ons to apply', }, ...getStandardParameters({ method: 'find', maxPageSize: 20 }), @@ -54,14 +49,13 @@ const findParameters: MethodParameter[] = [ export const docs: ServiceSwaggerOptions = { description: 'Text Reuse Passages', securities: ['find', 'get'], - schemas: { passage, passagesFindResponse: getFindResponse({ itemRef: 'passage', title: passage.title }) }, operations: { find: { description: 'Find text reuse passages', parameters: findParameters, responses: getStandardResponses({ method: 'find', - schema: 'passagesFindResponse', + schema: 'textReusePassagesFind', }), }, get: { @@ -69,7 +63,7 @@ export const docs: ServiceSwaggerOptions = { parameters: getStandardParameters({ method: 'get', idPattern: '[A-Za-z0-9-:@]+' }), responses: getStandardResponses({ method: 'get', - schema: 'passage', + schema: 'textReusePassagesGet', }), }, }, diff --git a/src/services/version/version.schema.ts b/src/services/version/version.schema.ts index 81c586fa..e50b3c3a 100644 --- a/src/services/version/version.schema.ts +++ b/src/services/version/version.schema.ts @@ -1,12 +1,9 @@ import type { ServiceSwaggerOptions } from 'feathers-swagger' import { getStandardResponses } from '../../util/openapi' -const versionResponseSchema = require('../../schema/version/response.json') - export const docs: ServiceSwaggerOptions = { description: 'Version of the API. Contains information about the current version of the API, features, etc.', securities: ['find'], - schemas: { versionResponseSchema }, operations: { find: { description: 'Get version object', @@ -14,7 +11,7 @@ export const docs: ServiceSwaggerOptions = { security: [], responses: getStandardResponses({ method: 'find', - schema: 'versionResponseSchema', + schema: 'versionFind', authEnabled: false, isRateLimited: false, }), diff --git a/src/util/openapi.ts b/src/util/openapi.ts index b10b0433..725189c3 100644 --- a/src/util/openapi.ts +++ b/src/util/openapi.ts @@ -1,12 +1,3 @@ -import type { Application, HookContext } from '@feathersjs/feathers' -import { - FormatName, - JSONSchemaDefinition, - addFormats, - getValidator, - Ajv, - hooks as schemaHooks, -} from '@feathersjs/schema' import type { JSONSchema7 as JSONSchema } from 'json-schema' interface Parameter { @@ -31,21 +22,29 @@ interface StatusResponse { content?: string | object } -export const jsonSchemaRef = (ref: string) => { - return { - 'application/json': { - schema: { - $ref: `#/components/schemas/${ref}`, - }, - }, - } -} +const asApplicationJson = (schema: JSONSchema) => ({ + 'application/json': { + schema, + }, +}) + +export const getSchemaRef = (schemaName: string) => ({ + $ref: `#/components/schemas/${schemaName}`, +}) + +export const getResponseRef = (schemaName: string) => ({ + $ref: `#/components/responses/${schemaName}`, +}) -const defaultErrorSchema = jsonSchemaRef('defaultErrorResponse') +export const getParameterRef = (schemaName: string) => ({ + $ref: `#/components/parameters/${schemaName}`, +}) + +const defaultErrorSchema = getSchemaRef('Error') interface GetStandardResponsesParams { method: 'create' | 'update' | 'patch' | 'remove' | 'find' | 'get' - schema: string | JSONSchema + schema: string authEnabled?: boolean isRateLimited?: boolean } @@ -59,7 +58,7 @@ export const getStandardResponses = ({ const defaultResponses: Record = { 422: { description: 'Unprocessable Entity', - content: defaultErrorSchema, + content: asApplicationJson(defaultErrorSchema), }, 500: { description: 'general error', @@ -68,34 +67,34 @@ export const getStandardResponses = ({ if (method === 'create') { defaultResponses[201] = { description: 'Created', - content: typeof schema === 'string' ? jsonSchemaRef(schema) : schema, + content: asApplicationJson(getResponseRef(schema)), } } else { defaultResponses[200] = { description: 'Success', - content: typeof schema === 'string' ? jsonSchemaRef(schema) : schema, + content: asApplicationJson(getResponseRef(schema)), } defaultResponses[404] = { description: 'Not Found', - content: defaultErrorSchema, + content: asApplicationJson(defaultErrorSchema), } } if (authEnabled) { defaultResponses[401] = { description: 'Not Authenticated', - content: defaultErrorSchema, + content: asApplicationJson(defaultErrorSchema), } defaultResponses[403] = { description: 'Unauthorized', - content: defaultErrorSchema, + content: asApplicationJson(defaultErrorSchema), } } if (isRateLimited) { defaultResponses[429] = { description: 'Rate limit exceeded', - content: defaultErrorSchema, + content: asApplicationJson(defaultErrorSchema), } } @@ -158,88 +157,22 @@ export const getStandardParameters = ({ return [] } -const DefaultFindSchema = require('../schema/common/findResponse') - -interface GetFindResponseOptions { - itemRef: string - title?: string -} - -export const getFindResponse = ({ itemRef, title }: GetFindResponseOptions): JSONSchema => { - const schema: JSONSchema = JSON.parse(JSON.stringify(DefaultFindSchema)) - const data: any = schema!.properties!.data - data.items.$ref = `#/components/schemas/${itemRef}` - schema!.properties!.data = data - - if (title != null) { - schema.title = `Find ${title} Response` - } - return schema -} - -const isQueryParameter = (parameter: MethodParameter): parameter is QueryParameter => { - return parameter.in === 'query' -} - -const isPathParameter = (parameter: MethodParameter): parameter is PathParameter => { - return parameter.in === 'path' -} - -const formats: FormatName[] = [ - 'date-time', - 'time', - 'date', - 'email', - 'hostname', - 'ipv4', - 'ipv6', - 'uri', - 'uri-reference', - 'uuid', - 'uri-template', - 'json-pointer', - 'relative-json-pointer', - 'regex', -] - -export const dataValidator = addFormats(new Ajv({}), formats) - -export const queryValidator = addFormats( - new Ajv({ - coerceTypes: true, - }), - formats -) - -export const validateParameters = (parameters: MethodParameter[]) => { - const querySchemaProperties = parameters.filter(isQueryParameter).reduce>( - (acc, parameter) => { - acc[parameter.name] = parameter.schema - return acc +export const getRequestBodyContent = (schemaName: string) => { + return { + 'application/json': { + schema: { + $ref: `#/components/requestBodies/${schemaName}`, + }, }, - {} as Record - ) - const querySchema: JSONSchema = { - properties: querySchemaProperties, } +} - const validateQuery = getValidator(querySchema as JSONSchemaDefinition, queryValidator) - - const pathSchemaProperties = parameters.filter(isPathParameter).reduce>( - (acc, parameter) => { - acc[parameter.name] = parameter.schema - return acc +export const getResponseContent = (schemaName: string) => { + return { + 'application/json': { + schema: { + $ref: `#/components/responses/${schemaName}`, + }, }, - {} as Record - ) - const pathSchema: JSONSchema = { - properties: pathSchemaProperties, - } - const validatePath = getValidator(pathSchema as JSONSchemaDefinition, queryValidator) - - return async (context: HookContext) => { - const [query, path] = await Promise.all([validateQuery(context.params.query), validatePath(context)]) - context.params.query = query - context.params.path = path } } diff --git a/yarn.lock b/yarn.lock index 5af9e206..9313ffa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,16 @@ lodash.assignwith "^4.2.0" typical "^7.1.1" +"@apidevtools/json-schema-ref-parser@^9.1.2": + version "9.1.2" + resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz" + integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + "@babel/code-frame@^7.0.0": version "7.24.2" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz" @@ -370,6 +380,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jsdevtools/ono@^7.1.3", "@jsdevtools/ono@7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + "@koa/cors@^5.0.0": version "5.0.0" resolved "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz" @@ -608,7 +623,7 @@ dependencies: "@types/node" "*" -"@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.15", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -695,6 +710,13 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== +"@types/multer@^1.4.7": + version "1.4.11" + resolved "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz" + integrity sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w== + dependencies: + "@types/express" "*" + "@types/node-fetch@^2.5.6": version "2.5.6" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.6.tgz" @@ -790,6 +812,11 @@ acorn-walk@^8.1.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== +ajv-draft-04@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz" + integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" @@ -827,7 +854,7 @@ ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.12.0, ajv@8.12.0: +ajv@^8.0.0, ajv@^8.11.2, ajv@^8.12.0, ajv@^8.5.0, ajv@8.12.0: version "8.12.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -1279,6 +1306,11 @@ call-bind@^1.0.2, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -1578,7 +1610,7 @@ content-security-policy-builder@2.1.0: resolved "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz" integrity sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ== -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.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -2390,6 +2422,25 @@ expect-ct@0.2.0: resolved "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz" integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g== +express-openapi-validator@5.1.6: + version "5.1.6" + resolved "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.1.6.tgz" + integrity sha512-CF24Pef5uThjdsCbjo1UP2mYx2YCkQl1HFoikCFFafFpZBCZ0YErD/RbqlcnKbKM9tMwXZsjAuuO84b2hmdF4g== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.1.2" + "@types/multer" "^1.4.7" + ajv "^8.11.2" + 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 "^6.2.0" + express@^4.18.3: version "4.19.0" resolved "https://registry.npmjs.org/express/-/express-4.19.0.tgz" @@ -3992,6 +4043,11 @@ md5-file@^4.0.0: resolved "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz" integrity sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" @@ -4095,9 +4151,9 @@ minimatch@4.2.1: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass@^3.0.0: version "3.3.6" @@ -4442,6 +4498,13 @@ only@~0.0.2: resolved "https://registry.npmjs.org/only/-/only-0.0.2.tgz" integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== +ono@^7.1.3: + version "7.1.3" + resolved "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz" + integrity sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ== + dependencies: + "@jsdevtools/ono" "7.1.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" @@ -4561,6 +4624,11 @@ path-scurry@^1.10.1: lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-to-regexp@^6.2.0: + version "6.2.2" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz" + integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"