From 6a019273912c0d31cebf2f16479753dc149f7a1f Mon Sep 17 00:00:00 2001 From: linuxbandit Date: Mon, 23 Nov 2020 17:19:55 +0100 Subject: [PATCH 1/2] feat: add documentation endpoint this endpoint is to be queried by our swagger instance --- lib/info.js | 10 ++ lib/server.js | 13 +++ lib/swaggerDef.js | 25 +++++ middlewares/bodies.js | 152 +++++++++++++++++++++++++ middlewares/circles.js | 30 +++++ middlewares/generic.js | 249 +++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 480 insertions(+) create mode 100644 lib/info.js create mode 100644 lib/swaggerDef.js diff --git a/lib/info.js b/lib/info.js new file mode 100644 index 00000000..0af40005 --- /dev/null +++ b/lib/info.js @@ -0,0 +1,10 @@ +'use strict'; +const os = require('os'); + +// Assuming by default that we run in 'development' environment, if no +// NODE_ENV is specified. +exports.env = process.env.NODE_ENV || 'development'; +exports.host = process.env.X_HOST || os.hostname(); +exports.name = process.env.npm_package_name; +exports.version = process.env.npm_package_version; +exports.description = process.env.npm_package_description; diff --git a/lib/server.js b/lib/server.js index 737b2940..540fafee 100644 --- a/lib/server.js +++ b/lib/server.js @@ -55,7 +55,20 @@ process.on('unhandledRejection', (err) => { } }); +const swaggerJSDoc = require('swagger-jsdoc'); +const options = {}; // (1/3) stupid gimmick because fuck the library lol +options.definition = require('./swaggerDef.js'); // (2/3) cli options are slightly different +options.apis = require('./swaggerDef.js').apis; // (3/3) so i have to make this gimmick +const swaggerSpec = swaggerJSDoc(options); // Initialize swagger-jsdoc -> returns validated swagger spec in json format + // Endpoints not requiring authorization. +GeneralRouter.get('/api-docs.json', (req, res) => { // mini-route to retrieve the docs + log.info('request coming from ' + req.ip + ' to ' + req.hostname); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.status(200).send(swaggerSpec); +}); + GeneralRouter.get('/healthcheck', middlewares.healthcheck); GeneralRouter.get('/metrics', metrics.getMetrics); GeneralRouter.get('/metrics/requests', endpointsMetrics.getEndpointMetrics); diff --git a/lib/swaggerDef.js b/lib/swaggerDef.js new file mode 100644 index 00000000..1d85925a --- /dev/null +++ b/lib/swaggerDef.js @@ -0,0 +1,25 @@ +'use strict'; +const config = require('../config'); +const serverInfo = require('./info.js'); + +module.exports = { + info: { + // API informations (required) + title: serverInfo.name, // Title (required) + version: serverInfo.version, // Version (required) + description: serverInfo.description, // Description (optional) + termsOfService: "https://my.aegee.eu/legal/simple", + license: { + name: "Apache 2.0", + url: "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + // host, // Host (optional) -- automatically taken who's serving this docs + apis: ['middlewares/*.js'], // where are the files with the comments + basePath: config.basePath, // Base path (optional) + schemes: ['http'], + externalDocs: { + description: "Find out more about MyAEGEE", + url: "https://myaegee.atlassian.net/wiki/spaces/GENERAL/overview" + } +}; diff --git a/middlewares/bodies.js b/middlewares/bodies.js index cfe7d5f8..5e61f9a9 100644 --- a/middlewares/bodies.js +++ b/middlewares/bodies.js @@ -4,6 +4,24 @@ const constants = require('../lib/constants'); const errors = require('../lib/errors'); const { sequelize } = require('../lib/sequelize'); +/** + * @swagger + * + * /bodies: + * get: + * tags: + * - "Bodies" + * summary: "List all bodies" + * description: "This endpoint is to list all bodies" + * operationId: "listAllBodies" + * produces: + * - "application/json" + * responses: + * 200: + * description: "Successful operation" + * 403: + * description: "Not enough permissions" + */ exports.listAllBodies = async (req, res) => { if (req.query.all) { if (!req.user) { @@ -40,6 +58,24 @@ exports.listAllBodies = async (req, res) => { }); }; +/** + * @swagger + * + * /bodies/{body_id}: + * get: + * tags: + * - "Bodies" + * summary: "List specific body" + * description: "This endpoint is to list specified body" + * operationId: "listBody" + * produces: + * - "application/json" + * responses: + * 200: + * description: "Successful operation" + * 403: + * description: "Not enough permissions" + */ exports.getBody = async (req, res) => { return res.json({ success: true, @@ -47,6 +83,35 @@ exports.getBody = async (req, res) => { }); }; +/** + * @swagger + * + * /bodies: + * post: + * tags: + * - "Bodies" + * summary: "Create bodies" + * description: "This endpoint is to create bodies" + * operationId: "createBody" + * consumes: + * - "application/json" + * produces: + * - "application/json" + * parameters: + * - name: "body" + * in: "body" + * description: "The data containing information on the new body" + * required: true + * schema: + * $ref: '#/definitions/Body' + * responses: + * 201: + * description: "Successful operation" + * 400: + * description: "Invalid input" + * 409: + * description: "Duplicate entity" + */ exports.createBody = async (req, res) => { if (!req.permissions.hasPermission('global:create:body')) { return errors.makeForbiddenError(res, 'Permission global:create:body is required, but not present.'); @@ -60,6 +125,35 @@ exports.createBody = async (req, res) => { }); }; +/** + * @swagger + * + * /bodies/{body_id}: + * put: + * tags: + * - "Bodies" + * summary: "Update bodies" + * description: "This endpoint is to update bodies" + * operationId: "updateBody" + * consumes: + * - "application/json" + * produces: + * - "application/json" + * parameters: + * - name: "body" + * in: "body" + * description: "The data containing information on the new body" + * required: true + * schema: + * $ref: '#/definitions/Body' + * responses: + * 200: + * description: "Successful operation" + * 400: + * description: "Invalid input" + * 409: + * description: "Duplicate entity" + */ exports.updateBody = async (req, res) => { if (!req.permissions.hasPermission('update:body')) { return errors.makeForbiddenError(res, 'Permission update:body is required, but not present.'); @@ -72,6 +166,35 @@ exports.updateBody = async (req, res) => { }); }; +/** + * @swagger + * + * /bodies/{body_id}/status: + * put: + * tags: + * - "Bodies" + * summary: "Update bodies' status" + * description: "This endpoint is to update bodies' status" + * operationId: "updateBodyStatus" + * consumes: + * - "application/json" + * produces: + * - "application/json" + * parameters: + * - name: "body" + * in: "body" + * description: "The data containing information on the new body" + * required: true + * schema: + * $ref: '#/definitions/Body' + * responses: + * 200: + * description: "Successful operation" + * 400: + * description: "Invalid input" + * 403: + * description: "Unauthorised" + */ exports.setBodyStatus = async (req, res) => { if (!req.permissions.hasPermission('global:delete:body')) { return errors.makeForbiddenError(res, 'Permission global:delete:body is required, but not present.'); @@ -97,6 +220,35 @@ exports.setBodyStatus = async (req, res) => { }); }; +/** + * @swagger + * + * /bodies/{body_id}/members: + * put: + * tags: + * - "Bodies" + * summary: "Add body member" + * description: "This endpoint is to add body member" + * operationId: "addBodyMember" + * consumes: + * - "application/json" + * produces: + * - "application/json" + * parameters: + * - name: "body" + * in: "body" + * description: "The data containing information on the new body" + * required: true + * schema: + * $ref: '#/definitions/Body' + * responses: + * 200: + * description: "Successful operation" + * 400: + * description: "Invalid input" + * 403: + * description: "Unauthorised" + */ exports.createMember = async (req, res) => { if (!req.permissions.hasPermission('create_member:body')) { return errors.makeForbiddenError(res, 'Permission create_member:body is required, but not present.'); diff --git a/middlewares/circles.js b/middlewares/circles.js index a66cb916..570e3672 100644 --- a/middlewares/circles.js +++ b/middlewares/circles.js @@ -4,6 +4,36 @@ const helpers = require('../lib/helpers'); const constants = require('../lib/constants'); const { Sequelize, sequelize } = require('../lib/sequelize'); +/** + * @swagger + * + * /circles: + * post: + * tags: + * - "Circles" + * summary: "Create Circle" + * description: "This endpoint is to create a Circle group" + * operationId: "createGroup" + * consumes: + * - "application/json" + * produces: + * - "application/json" + * parameters: + * - name: "data" + * in: "body" + * description: "The data containing information on the group" + * required: true + * schema: + * $ref: '#/definitions/Group' + * responses: + * 201: + * description: "Successful operation" + * 400: + * description: "Invalid input" + * 409: + * description: "Duplicate entity" + */ + exports.listAllCircles = async (req, res) => { const where = helpers.filterBy(req.query.query, constants.FIELDS_TO_QUERY.CIRCLE); diff --git a/middlewares/generic.js b/middlewares/generic.js index 6170ef4f..51a82425 100644 --- a/middlewares/generic.js +++ b/middlewares/generic.js @@ -102,3 +102,252 @@ exports.errorHandler = (err, req, res, next) => { /* istanbul ignore next */ return errors.makeInternalError(res, err); }; + +////////////////////////////////////////////////////////////// +// GENERIC SWAGGER DEFINITION: 'tags' and example responses +/** + * @swagger + * + * tags: + * - name: "Members" + * description: "Accounts operations: add/remove user; modify user details; add/remove aliases for user email" + * externalDocs: + * description: "?" + * url: "https://my.aegee.eu" + * - name: "Bodies" + * description: "Bodies operations: add/remove a body; add/remove a user membership to a body" + * externalDocs: + * description: "??" + * url: "https://my.aegee.eu" + * - name: "Circles" + * description: "Circles operations: add " + * externalDocs: + * description: "???" + * url: "https://my.aegee.eu" + * - name: "Permissions" + * description: "Permissions operations: add " + * externalDocs: + * description: "???" + * url: "https://my.aegee.eu" + * - name: "Campaigns" + * description: "Campaigns operations: add " + * externalDocs: + * description: "???" + * url: "https://my.aegee.eu" + * + * definitions: + * generalResponse: + * type: object + * required: + * - success + * - message + * properties: + * success: + * type: boolean + * message: + * type: string + * + * errorResponse: + * allOf: + * - '$ref': '#/definitions/generalResponse' + * - type: object + * required: + * - error + * properties: + * error: + * type: string + * + * + * successResponse: + * allOf: + * - '$ref': '#/definitions/generalResponse' + * - type: object + * required: + * - data + * properties: + * data: + * type: object + * example: + * "": "" + * + * Group: + * type: "object" + * properties: + * primaryEmail: + * type: "string" + * description: "The google ID (xxx@aegee.eu) of the group that is added" + * format: "email" + * groupName: + * type: "string" + * description: "The name of the Google group" + * format: "string" + * bodyPK: + * type: "string" + * description: "The primary key that identifies the body/circle in MyAEGEE" + * format: "string" + * required: + * - groupName + * - primaryEmail + * - bodyPK + * example: + * groupName: "The Straight Banana Committee" + * primaryEmail: "sbc@aegee.eu" + * bodyPK: "(idk how it's represented)" + * + * Account: + * type: "object" + * properties: + * primaryEmail: + * type: "string" + * description: "The username @aegee.eu for the account" + * format: "string" + * name: + * $ref: "#/definitions/Account_name" + * secondaryEmail: + * type: "string" + * description: "The email of the user. For password reset and first-time sign up" + * format: "email" + * password: + * type: "string" + * description: "MUST be a SHA-1 password" + * format: "password" + * antenna: + * type: "string" + * description: "The (primary) antenna the user belongs to" + * format: "string" + * userPK: + * type: "string" + * description: "The primary key of the user in MyAEGEE" + * required: + * - primaryEmail + * - name + * - secondaryEmail + * - password + * - antenna + * - userPK + * example: + * primaryEmail: "cave.johnson@aegee.eu" + * name: + * givenName: "Cave" + * familyName: "Johnson" + * secondaryEmail: "cave.aegee@example.com" + * password: "[SOME-SHA1-HASH]" + * antenna: "AEGEE-Tallahassee" + * userPK: "(idk how it's represented)" + * + * Account_name: + * properties: + * givenName: + * type: "string" + * familyName: + * type: "string" + * required: + * - givenName + * - familyName + * example: + * givenName: "Cave" + * familyName: "Johnson" + * + * Membership: + * type: "object" + * properties: + * groupPK: + * type: "string" + * description: "(required) The group in which the user \ + * is added. MyAEGEE's PK of the body/circle" + * operation: + * type: "string" + * description: "(required) 'add'/'remove' member" + * required: + * - groupPK + * - operation + * example: + * groupPK: "(idk how it's represented)" + * operation: "add" + * + * aliasOperation: + * type: "object" + * properties: + * aliasName: + * type: "string" + * description: "The alias that is added" + * operation: + * type: "string" + * description: "'add'/'remove' alias" + * required: + * - aliasName + * - operation + * example: + * aliasName: "example@aegee.eu" + * operation: "add" + * + * Body: + * properties: + * name: + * type: "string" + * description: "The name of the event" + * description: + * type: "string" + * description: "Format MUST be YYYY-MM-DD" + * task_description: + * type: "string" + * description: "Format MUST be YYYY-MM-DD" + * code: + * type: "string" + * description: "The description of the event" + * email: + * type: "string" + * description: "The city where the event is happening. Can be any string" + * phone: + * type: "string" + * description: "Format MUST be a-v 0-9" + * address: + * type: "string" + * description: "Format MUST be a-v 0-9" + * type: + * type: "string" + * description: "One of 'antenna', 'contact antenna', 'contact', 'interest group', 'working group', 'commission', 'committee', 'project', 'partner', 'other'" + * fee_currency: + * type: "string" + * description: "Format MUST be a-v 0-9" + * pays_fees: + * type: "bool" + * description: "Format MUST be a-v 0-9" + * founded_at: + * type: "date" + * description: "Format MUST be a-v 0-9" + * status: + * type: "One of 'active', 'deleted'" + * description: "Format MUST be a-v 0-9" + * country: + * type: "string" + * description: "Format MUST be a-v 0-9" + * website: + * type: "string" + * description: "Format MUST be a-v 0-9" + * description: "(required)" + * required: + * - name + * - description + * - task_description + * - code + * - email + * - phone + * - address + * - type + * - fee_currency + * - pays_fees + * - founded_at + * - status + * - website + * - country + * example: + * name: "RTC Tallahassee" + * startDate: "2019-04-25" + * endDate: "2019-04-25" + * description: "An RTC in a far away place" + * location: "Tallahassee, Florida" + * eventID: "rtctallahassee19" + */ +////////////////////////////////////////////////////////////// + diff --git a/package.json b/package.json index fc7e2f05..8e64e18b 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "request": "^2.88.2", "request-promise-native": "^1.0.9", "sequelize": "^6.3.5", + "swagger-jsdoc": "^4.3.1", "sequelize-cli": "^6.2.0" } } From af8e6f924a2f84f93837022a8c1a11fda2a75853 Mon Sep 17 00:00:00 2001 From: Rik Smale Date: Wed, 25 Nov 2020 20:40:42 +0100 Subject: [PATCH 2/2] chore(lint): fix linter --- lib/info.js | 1 - lib/server.js | 13 +++--- lib/swaggerDef.js | 35 ++++++++------- middlewares/generic.js | 5 +-- package-lock.json | 98 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 28 deletions(-) diff --git a/lib/info.js b/lib/info.js index 0af40005..910a46aa 100644 --- a/lib/info.js +++ b/lib/info.js @@ -1,4 +1,3 @@ -'use strict'; const os = require('os'); // Assuming by default that we run in 'development' environment, if no diff --git a/lib/server.js b/lib/server.js index 540fafee..204c8593 100644 --- a/lib/server.js +++ b/lib/server.js @@ -2,6 +2,7 @@ const express = require('express'); const router = require('express-promise-router'); const bodyParser = require('body-parser'); const boolParser = require('express-query-boolean'); +const swaggerJSDoc = require('swagger-jsdoc'); const morgan = require('./morgan'); const db = require('./sequelize'); @@ -55,18 +56,18 @@ process.on('unhandledRejection', (err) => { } }); -const swaggerJSDoc = require('swagger-jsdoc'); const options = {}; // (1/3) stupid gimmick because fuck the library lol options.definition = require('./swaggerDef.js'); // (2/3) cli options are slightly different -options.apis = require('./swaggerDef.js').apis; // (3/3) so i have to make this gimmick +options.apis = require('./swaggerDef.js').apis; +// (3/3) so i have to make this gimmick const swaggerSpec = swaggerJSDoc(options); // Initialize swagger-jsdoc -> returns validated swagger spec in json format // Endpoints not requiring authorization. GeneralRouter.get('/api-docs.json', (req, res) => { // mini-route to retrieve the docs - log.info('request coming from ' + req.ip + ' to ' + req.hostname); - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.status(200).send(swaggerSpec); + log.info('request coming from ' + req.ip + ' to ' + req.hostname); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + res.status(200).send(swaggerSpec); }); GeneralRouter.get('/healthcheck', middlewares.healthcheck); diff --git a/lib/swaggerDef.js b/lib/swaggerDef.js index 1d85925a..98b30b01 100644 --- a/lib/swaggerDef.js +++ b/lib/swaggerDef.js @@ -1,25 +1,24 @@ -'use strict'; const config = require('../config'); const serverInfo = require('./info.js'); module.exports = { - info: { + info: { // API informations (required) - title: serverInfo.name, // Title (required) - version: serverInfo.version, // Version (required) - description: serverInfo.description, // Description (optional) - termsOfService: "https://my.aegee.eu/legal/simple", - license: { - name: "Apache 2.0", - url: "http://www.apache.org/licenses/LICENSE-2.0.html" + title: serverInfo.name, // Title (required) + version: serverInfo.version, // Version (required) + description: serverInfo.description, // Description (optional) + termsOfService: 'https://my.aegee.eu/legal/simple', + license: { + name: 'Apache 2.0', + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' + } + }, + // host, // Host (optional) -- automatically taken who's serving this docs + apis: ['middlewares/*.js'], // where are the files with the comments + basePath: config.basePath, // Base path (optional) + schemes: ['http'], + externalDocs: { + description: 'Find out more about MyAEGEE', + url: 'https://myaegee.atlassian.net/wiki/spaces/GENERAL/overview' } - }, - // host, // Host (optional) -- automatically taken who's serving this docs - apis: ['middlewares/*.js'], // where are the files with the comments - basePath: config.basePath, // Base path (optional) - schemes: ['http'], - externalDocs: { - description: "Find out more about MyAEGEE", - url: "https://myaegee.atlassian.net/wiki/spaces/GENERAL/overview" - } }; diff --git a/middlewares/generic.js b/middlewares/generic.js index 51a82425..fb258661 100644 --- a/middlewares/generic.js +++ b/middlewares/generic.js @@ -103,7 +103,7 @@ exports.errorHandler = (err, req, res, next) => { return errors.makeInternalError(res, err); }; -////////////////////////////////////////////////////////////// +/// /////////////////////////////////////////////////////////// // GENERIC SWAGGER DEFINITION: 'tags' and example responses /** * @swagger @@ -349,5 +349,4 @@ exports.errorHandler = (err, req, res, next) => { * location: "Tallahassee, Florida" * eventID: "rtctallahassee19" */ -////////////////////////////////////////////////////////////// - +/// /////////////////////////////////////////////////////////// diff --git a/package-lock.json b/package-lock.json index e4ddb794..b9093d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,39 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz", + "integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, + "@apidevtools/openapi-schemas": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.0.4.tgz", + "integrity": "sha512-ob5c4UiaMYkb24pNhvfSABShAwpREvUGCkqjiz/BX9gKZ32y/S22M+ALIHftTAuv9KsFVSpVdIDzi9ZzFh5TCA==" + }, + "@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "@apidevtools/swagger-parser": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz", + "integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==", + "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^4.2.3" + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -1585,6 +1618,11 @@ } } }, + "@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==" + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -2932,6 +2970,11 @@ "get-intrinsic": "^1.0.0" } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9754,6 +9797,16 @@ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -17118,6 +17171,33 @@ } } }, + "swagger-jsdoc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-4.3.2.tgz", + "integrity": "sha512-GK+J0LftvEurROVi70bMiIrd/A7pJD2AiI8faMkznsuyokGEu8WCdFsuZhmcE0XQt8hP/UTTkHEZpe3pS1eUjw==", + "requires": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "js-yaml": "3.14.0", + "swagger-parser": "10.0.2" + }, + "dependencies": { + "commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==" + } + } + }, + "swagger-parser": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz", + "integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==", + "requires": { + "@apidevtools/swagger-parser": "10.0.2" + } + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -18016,6 +18096,24 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "z-schema": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz", + "integrity": "sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A==", + "requires": { + "commander": "^2.7.1", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^12.0.0" + }, + "dependencies": { + "validator": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", + "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" + } + } } } }