From 141f7a5698ba9c6a164523f5d3b0ddcd52e563a9 Mon Sep 17 00:00:00 2001 From: xdan Date: Tue, 22 Aug 2023 12:58:03 +0300 Subject: [PATCH] Added router for autodoc --- docs/v1/spec.yaml | 164 +++++++++++++++++++++++++++++++++ package-lock.json | 76 +++++++++++++-- package.json | 4 + src/app/app.ts | 2 + src/app/middleware/api-docs.ts | 11 +++ 5 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 docs/v1/spec.yaml create mode 100644 src/app/middleware/api-docs.ts diff --git a/docs/v1/spec.yaml b/docs/v1/spec.yaml new file mode 100644 index 0000000..e367fa1 --- /dev/null +++ b/docs/v1/spec.yaml @@ -0,0 +1,164 @@ +openapi: '3.0.1' +info: + version: v1 + title: Mappable test server + license: + name: "Apache-2" +paths: + /ping: + get: + description: | + Handle for checking the readiness of the server to work + responses: + 200: + description: Server is ready. + content: + application/json: + example: {"ok": true} + schema: + type: object + properties: + ok: + type: boolean + 400: + description: Server is not ready. + content: + application/json: + example: { "ok": false } + schema: + type: object + properties: + ok: + type: boolean + + /v1/bbox: + post: + description: | + Returns geo features whose coordinates are within the given rectangle bbox + + requestBody: + content: + application/json: + schema: + type: object + required: ['leftBottom', 'rightTop'] + properties: + leftBottom: + $ref: '#/components/schemas/LngLat' + rightTop: + $ref: '#/components/schemas/LngLat' + + description: JSON object + responses: + 200: + description: Features list. + content: + application/json: + schema: + type: object + properties: + features: + type: array + items: + $ref: '#/components/schemas/Feature' + 400: + $ref: '#/components/responses/InvalidParameters' + + /v1/tile: + post: + description: | + Returns geo features whose coordinates are within a rectangle that is uniquely defined by its x,y and z tile coordinates + + requestBody: + content: + application/json: + schema: + type: object + required: ['x', 'y', 'z'] + properties: + x: + type: integer + y: + type: integer + z: + type: integer + + description: JSON object + responses: + 200: + description: Features list. + content: + application/json: + schema: + type: object + properties: + bounds: + description: Computed rectangle of coordinates lower left and upper right corner for a given tile + items: + $ref: '#/components/schemas/LngLat' + + features: + type: array + items: + $ref: '#/components/schemas/Feature' + 400: + $ref: '#/components/responses/InvalidParameters' + +components: + responses: + InvalidParameters: + description: Invalid parameters. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + schemas: + LngLat: + description: 'Array of coordinates [Longitude, Latitude].' + example: [24, 54] + type: array + items: + type: number + minItems: 2 + maxItems: 2 + + Feature: + description: 'GeoJSON Feature' + type: object + example: + { + type: 'Feature', + properties: {name: 'Abu Dhabi'}, + geometry: {'type': 'Point', 'coordinates': [24.189215755000077, 53.796540969000034]} + } + properties: + type: + type: string + properties: + type: object + properties: + name: + type: string + geometry: + type: object + properties: + type: + type: string + coordinates: + $ref: '#/components/schemas/LngLat' + + Error: + type: object + properties: + statusCode: + type: integer + error: + type: string + message: + type: string + required: + - statusCode + - error + - message + additionalProperties: false diff --git a/package-lock.json b/package-lock.json index 65a8d41..a6aa146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,11 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "jest": "^29.6.2", + "js-yaml": "^4.1.0", "nock": "^13.3.3", "pg": "^8.11.3", "pm2": "^5.3.0", + "swagger-ui-express": "^5.0.0", "winston": "^3.10.0", "zod": "^3.22.1" }, @@ -23,7 +25,9 @@ "@types/express": "^4.17.17", "@types/got": "^9.6.12", "@types/jest": "^29.5.3", + "@types/js-yaml": "^4.0.5", "@types/pg": "^8.10.2", + "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", "eslint": "^8.47.0", @@ -1837,6 +1841,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -1966,6 +1976,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", + "integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -2456,8 +2476,7 @@ "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/array-flatten": { "version": "1.1.1", @@ -5128,7 +5147,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" }, @@ -7256,6 +7274,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.4.2.tgz", + "integrity": "sha512-vT5QxP/NOr9m4gLZl+SpavWI3M9Fdh30+Sdw9rEtZbkqNmNNEPhjXas2xTD9rsJYYdLzAiMfwXvtooWH3xbLJA==" + }, + "node_modules/swagger-ui-express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.0.tgz", + "integrity": "sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -9353,6 +9390,12 @@ "pretty-format": "^29.0.0" } }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -9469,6 +9512,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/swagger-ui-express": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", + "integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -9794,8 +9847,7 @@ "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==" }, "array-flatten": { "version": "1.1.1", @@ -11800,7 +11852,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -13351,6 +13402,19 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "swagger-ui-dist": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.4.2.tgz", + "integrity": "sha512-vT5QxP/NOr9m4gLZl+SpavWI3M9Fdh30+Sdw9rEtZbkqNmNNEPhjXas2xTD9rsJYYdLzAiMfwXvtooWH3xbLJA==" + }, + "swagger-ui-express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.0.tgz", + "integrity": "sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==", + "requires": { + "swagger-ui-dist": ">=5.0.0" + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", diff --git a/package.json b/package.json index 5b71c83..181d8c8 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "jest": "^29.6.2", + "js-yaml": "^4.1.0", "nock": "^13.3.3", "pg": "^8.11.3", "pm2": "^5.3.0", + "swagger-ui-express": "^5.0.0", "winston": "^3.10.0", "zod": "^3.22.1" }, @@ -33,7 +35,9 @@ "@types/express": "^4.17.17", "@types/got": "^9.6.12", "@types/jest": "^29.5.3", + "@types/js-yaml": "^4.0.5", "@types/pg": "^8.10.2", + "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", "eslint": "^8.47.0", diff --git a/src/app/app.ts b/src/app/app.ts index 8c09b57..e3a19a9 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -6,6 +6,7 @@ import {asyncMiddleware} from './lib/async-middlware'; import {loadByBBox} from './middleware/load-by-bbox'; import {loadByTile} from './middleware/load-by-tile'; import {DataProvider} from "./data-provider/interface"; +import {apiDocs} from "./middleware/api-docs"; export function createApp(dataProvider: DataProvider) { return ( @@ -16,6 +17,7 @@ export function createApp(dataProvider: DataProvider) { .get('/ping', asyncMiddleware(pingMiddleware.bind(null, dataProvider))) .post('/v1/bbox', asyncMiddleware(loadByBBox.bind(null, dataProvider))) .post('/v1/tile', asyncMiddleware(loadByTile.bind(null, dataProvider))) + .use('/v1/api_docs', apiDocs) .use((req: express.Request, res: express.Response, next: express.NextFunction) => next(Boom.notFound('Endpoint not found')) ) diff --git a/src/app/middleware/api-docs.ts b/src/app/middleware/api-docs.ts new file mode 100644 index 0000000..80d5841 --- /dev/null +++ b/src/app/middleware/api-docs.ts @@ -0,0 +1,11 @@ +import {Router} from 'express'; +import * as fs from 'fs'; +import * as yaml from 'js-yaml'; +import * as path from 'path'; +import * as swaggerUi from 'swagger-ui-express'; + +const apiSpec = yaml.load(fs.readFileSync(path.join(__dirname, '../../../docs/v1/spec.yaml'), 'utf8')) as object; + +export const apiDocs = Router() + .use('/', swaggerUi.serve) + .get('/', swaggerUi.setup(apiSpec));