From 5561bc58aec50a571b168c68be5380ccddfe996a Mon Sep 17 00:00:00 2001 From: DVGY Date: Thu, 23 Sep 2021 22:25:07 +0530 Subject: [PATCH 1/2] add swagger docs and open api specs --- tours-server/package-lock.json | 23 +++++++++++++++++++++++ tours-server/package.json | 2 ++ tours-server/src/api-docs/index.ts | 1 + tours-server/src/api-docs/server.ts | 14 ++++++++++++++ tours-server/src/api-docs/tags.ts | 9 +++++++++ tours-server/src/app.ts | 3 +++ 6 files changed, 52 insertions(+) create mode 100644 tours-server/src/api-docs/index.ts create mode 100644 tours-server/src/api-docs/server.ts create mode 100644 tours-server/src/api-docs/tags.ts diff --git a/tours-server/package-lock.json b/tours-server/package-lock.json index c6fd036..82a2317 100644 --- a/tours-server/package-lock.json +++ b/tours-server/package-lock.json @@ -1261,6 +1261,16 @@ "@types/superagent": "*" } }, + "@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/tmp": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.0.tgz", @@ -7375,6 +7385,19 @@ } } }, + "swagger-ui-dist": { + "version": "3.52.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.3.tgz", + "integrity": "sha512-7QSY4milmYx5O8dbzU5tTftiaoZt+4JGxahTTBiLAnbTvhTyzum9rsjDIJjC+xeT8Tt1KfB38UuQQjmrh2THDQ==" + }, + "swagger-ui-express": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", + "integrity": "sha512-Xs2BGGudvDBtL7RXcYtNvHsFtP1DBFPMJFRxHe5ez/VG/rzVOEjazJOOSc/kSCyxreCTKfJrII6MJlL9a6t8vw==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/tours-server/package.json b/tours-server/package.json index 2f54193..d0dd883 100644 --- a/tours-server/package.json +++ b/tours-server/package.json @@ -31,6 +31,7 @@ "morgan": "^1.10.0", "slugify": "^1.5.2", "stripe": "^8.167.0", + "swagger-ui-express": "^4.1.6", "validator": "^13.6.0", "xss-clean": "^0.1.1" }, @@ -49,6 +50,7 @@ "@types/morgan": "^1.9.2", "@types/node": "^15.0.1", "@types/supertest": "^2.0.11", + "@types/swagger-ui-express": "^4.1.3", "@types/validator": "^13.6.3", "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", diff --git a/tours-server/src/api-docs/index.ts b/tours-server/src/api-docs/index.ts new file mode 100644 index 0000000..8717dbf --- /dev/null +++ b/tours-server/src/api-docs/index.ts @@ -0,0 +1 @@ +export = {}; diff --git a/tours-server/src/api-docs/server.ts b/tours-server/src/api-docs/server.ts new file mode 100644 index 0000000..ec65b45 --- /dev/null +++ b/tours-server/src/api-docs/server.ts @@ -0,0 +1,14 @@ +const server = { + servers: [ + { + url: 'http://localhost:1337/api/v1', // url + description: 'Local server', // name + }, + { + url: 'https://tours-api-prod.herokuapp.com', // url + description: 'Production server', // name + }, + ], +}; + +export { server }; diff --git a/tours-server/src/api-docs/tags.ts b/tours-server/src/api-docs/tags.ts new file mode 100644 index 0000000..1b6ae05 --- /dev/null +++ b/tours-server/src/api-docs/tags.ts @@ -0,0 +1,9 @@ +const tag = { + tags: [ + { + name: 'An API to book tours', // name of a tag + }, + ], +}; + +export { tag }; diff --git a/tours-server/src/app.ts b/tours-server/src/app.ts index d149571..2b4db0f 100644 --- a/tours-server/src/app.ts +++ b/tours-server/src/app.ts @@ -7,6 +7,7 @@ import xss from 'xss-clean'; import hpp from 'hpp'; import cors from 'cors'; import cookieParser from 'cookie-parser'; +import swaggerUI from 'swagger-ui-express'; import tripsRouter from './routes/tripsRoutes'; import usersRouter from './routes/usersRoutes'; @@ -15,6 +16,7 @@ import bookingsRouter from './routes/bookingsRoutes'; import { createBookingsStripeWebhook } from './controllers/bookingsController'; import { errorHandler } from './utils/errorHandler'; import { stripe } from './utils/stripe'; +import docs from './api-docs'; const app = express(); const allowedOrigins = ['http://localhost:3000', /\.vercel\.app$/]; @@ -86,6 +88,7 @@ app.get('/docker', (req, res) => { data: 'Hi, I am inside docker and NGINX is looking after me', }); }); +app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(docs)); app.use('/api/v1/trips', tripsRouter); app.use('/api/v1/users', usersRouter); From ce0fd4f0fdc8027196285170fda0e5d66e499205 Mon Sep 17 00:00:00 2001 From: DVGY Date: Sat, 25 Sep 2021 12:02:07 +0530 Subject: [PATCH 2/2] add basic docs for trip schema and get one trip route --- tours-server/.eslintrc.js | 22 +-- tours-server/src/api-docs/basicinfo.ts | 15 ++ tours-server/src/api-docs/components.ts | 152 ++++++++++++++++++++ tours-server/src/api-docs/index.ts | 14 +- tours-server/src/api-docs/trips/getATrip.ts | 34 +++++ tours-server/src/api-docs/trips/index.ts | 9 ++ tours-server/src/app.ts | 2 + tours-server/tsconfig.json | 3 +- 8 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 tours-server/src/api-docs/basicinfo.ts create mode 100644 tours-server/src/api-docs/components.ts create mode 100644 tours-server/src/api-docs/trips/getATrip.ts create mode 100644 tours-server/src/api-docs/trips/index.ts diff --git a/tours-server/.eslintrc.js b/tours-server/.eslintrc.js index ecee129..226c2b1 100644 --- a/tours-server/.eslintrc.js +++ b/tours-server/.eslintrc.js @@ -5,27 +5,27 @@ module.exports = { es2021: true, }, extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', ], - parser: "@typescript-eslint/parser", + parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, - project: "tsconfig.eslint.json", + project: 'tsconfig.eslint.json', tsconfigRootDir: __dirname, }, - plugins: ["@typescript-eslint"], + plugins: ['@typescript-eslint'], rules: { - "prefer-const": "error", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-unused-params": "off", + 'prefer-const': 'error', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-params': 'off', }, overrides: [ { - files: ["test/**/*.ts"], + files: ['test/**/*.ts'], env: { jest: true, node: true }, }, ], - ignorePatterns: [".eslintrc.js"], + ignorePatterns: ['.eslintrc.js', 'src/api-docs/**'], }; diff --git a/tours-server/src/api-docs/basicinfo.ts b/tours-server/src/api-docs/basicinfo.ts new file mode 100644 index 0000000..8fe8b6b --- /dev/null +++ b/tours-server/src/api-docs/basicinfo.ts @@ -0,0 +1,15 @@ +const basicInfo = { + openapi: '3.0.3', // present supported openapi version + info: { + title: 'Tours.dev api', // short title. + description: 'An API to book tours', // desc. + version: '1.0.0', // version number + contact: { + name: 'Gaurav Yadav', // your name + email: 'gaurav.y@hotmail.com', // your email + url: 'https://tours-api-prod.herokuapp.com', // your website + }, + }, +}; + +export { basicInfo }; diff --git a/tours-server/src/api-docs/components.ts b/tours-server/src/api-docs/components.ts new file mode 100644 index 0000000..5119414 --- /dev/null +++ b/tours-server/src/api-docs/components.ts @@ -0,0 +1,152 @@ +const component = { + components: { + schemas: { + // todo model + Trips: { + type: 'object', // data type + properties: { + id: { + type: 'string', // data-type + description: 'A trip id', // desc + example: '6145a6c4991f41fb47f63bfe', // example of an id + }, + name: { + type: 'string', // data-type + description: 'Name of Trip', // desc + example: 'The Visitor Palace', // example of a title + unique: true, + required: true, + }, + slug: { + type: 'string', // data type + description: 'Name of Trip, used in client trip search', // desc + example: 'the visitor palace', // example of a completed value + }, + duration: { + type: 'number', // data type + description: 'Total Number of Days', // desc + example: 4, // example of a completed value + required: true, + }, + price: { + type: 'number', // data type + description: 'Price of Trip', // desc + example: '220', // example of a completed value + }, + priceDiscount: { + type: 'number', // data type + description: 'Discount on Trip', // desc + example: '10', // example of a completed value + }, + difficulty: { + type: 'string', // data type + description: 'Trip difficulty level (Easy, Medium, Hard)', // desc + example: 'medium', // example of a completed value + }, + ratingsAverage: { + type: 'number', // data type + description: 'Average ratings of a Trip', // desc + example: '4.4', // example of a completed value + default: 4.5, + }, + ratingsQuantity: { + type: 'number', + description: 'Number of ratings of a Trip', // desc + example: '50', + default: 0, + }, + summary: { + type: 'string', // data type + description: 'A short summary of Trip', // desc + example: 'This is best ............', // example of a completed value + }, + description: { + type: 'string', // data type + description: 'A short description of Trip', // desc + example: 'The tours of the .....', // example of a completed value + }, + imageCover: { + type: 'string', // data type + description: 'A cover photo/image url of a Trip', // des + example: 'https://www.myimageurl.com', + }, + images: { + type: 'array', + description: 'All images/photos url of a Trip', // des + example: [ + 'https://www.myimageurl1.com', + 'https://www.myimageurl2.com', + ], + }, + createdAt: { + type: 'string', + description: 'Timestamp when Trip was created', // des + example: 'https://www.myimageurl.com', + }, + guides: { + $ref: '#/components/schemas/user', + }, + startDates: { + type: 'array', + description: 'Start dates of each trip location', // des + example: [ + '2021-06-19T09:00:00.000Z', + '2021-07-20T09:00:00.000Z', + '2021-08-18T09:00:00.000Z', + ], + }, + startLocation: { + type: 'object', + description: 'Geo JSON Point', + example: { + description: 'Banff, CAN', + type: 'Point', + coordinates: [-115.570154, 51.178456], + address: '224 Banff Ave, Banff, AB, Canada', + }, + properties: { + coordinates: { + type: 'array', + }, + address: { + type: 'string', + }, + description: { + type: 'string', + }, + }, + // longitute, latitude + }, + locations: { + type: 'array', + description: 'Geo JSON Points', + }, + + secretTrip: { + type: 'boolean', + description: 'Special Secret Trips ', + }, + }, + }, + + // error model + Error: { + type: 'object', //data type + properties: { + message: { + type: 'string', // data type + description: 'Error message', // desc + example: 'Not found', // example of an error message + }, + internal_code: { + type: 'string', // data type + description: 'Error internal code', // desc + example: 'Invalid parameters', // example of an error internal code + }, + }, + }, + }, + }, +}; + +export { component }; diff --git a/tours-server/src/api-docs/index.ts b/tours-server/src/api-docs/index.ts index 8717dbf..8baf9a9 100644 --- a/tours-server/src/api-docs/index.ts +++ b/tours-server/src/api-docs/index.ts @@ -1 +1,13 @@ -export = {}; +import { basicInfo } from './basicInfo'; +import { server } from './server'; +import { tag } from './tags'; +import { component } from './components'; +import trips from './trips'; + +export = { + ...basicInfo, + ...server, + ...tag, + ...component, + ...trips, +}; diff --git a/tours-server/src/api-docs/trips/getATrip.ts b/tours-server/src/api-docs/trips/getATrip.ts new file mode 100644 index 0000000..e3eed1a --- /dev/null +++ b/tours-server/src/api-docs/trips/getATrip.ts @@ -0,0 +1,34 @@ +const getATrip = { + // method of operation + get: { + tags: ['Get A Trip'], // operation's tag. + description: 'Get A Trip', // operation's desc. + operationId: 'getATrip', // unique operation id. + parameters: [ + { + name: 'id', // name of the param + in: 'path', // location of the param + schema: { type: 'string', default: null }, + required: false, // Mandatory param + description: 'id of Trip', // param desc. + }, + ], // expected params. + // expected responses + responses: { + // response code + 200: { + description: 'Get A Trip', // response desc. + content: { + // content-type + 'application/json': { + schema: { + $ref: '#/components/schemas/Trips', // Todo model + }, + }, + }, + }, + }, + }, +}; + +export { getATrip }; diff --git a/tours-server/src/api-docs/trips/index.ts b/tours-server/src/api-docs/trips/index.ts new file mode 100644 index 0000000..64e1169 --- /dev/null +++ b/tours-server/src/api-docs/trips/index.ts @@ -0,0 +1,9 @@ +import { getATrip } from './getATrip'; + +export = { + paths: { + '/trips/:id': { + ...getATrip, + }, + }, +}; diff --git a/tours-server/src/app.ts b/tours-server/src/app.ts index 2b4db0f..1aee6fd 100644 --- a/tours-server/src/app.ts +++ b/tours-server/src/app.ts @@ -80,6 +80,8 @@ app.use( app.get('/', (req, res) => { res.status(200).json({ status: 'success', + message: + 'Visit https://tours-api-prod.herokuapp.com/api-docs for documentation', }); }); app.get('/docker', (req, res) => { diff --git a/tours-server/tsconfig.json b/tours-server/tsconfig.json index 1e9d401..307c477 100644 --- a/tours-server/tsconfig.json +++ b/tours-server/tsconfig.json @@ -19,5 +19,6 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, "typeRoots": ["src/@types", "./node_modules/@types"] }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/api-docs/**"] }