From: Dan Brown Date: Thu, 10 Feb 2022 12:53:25 +0000 (+0000) Subject: Added postman collection gen example X-Git-Url: http://source.bookstackapp.com/api-scripts/commitdiff_plain/d22d2eb86f1eda52597cf422302ba895bb73525b Added postman collection gen example --- diff --git a/node-generate-postman-collection/.gitignore b/node-generate-postman-collection/.gitignore new file mode 100644 index 0000000..569e7b9 --- /dev/null +++ b/node-generate-postman-collection/.gitignore @@ -0,0 +1 @@ +*.postman_collection.json \ No newline at end of file diff --git a/node-generate-postman-collection/index.js b/node-generate-postman-collection/index.js new file mode 100644 index 0000000..deabb67 --- /dev/null +++ b/node-generate-postman-collection/index.js @@ -0,0 +1,234 @@ +// Libraries used +const axios = require('axios'); + +// BookStack API variables +// Uses values on the environment unless hardcoded +// To hardcode, add values to the empty strings in the below. +const bookStackConfig = { + base_url: '' || process.env.BS_URL, + token_id: '' || process.env.BS_TOKEN_ID, + token_secret: '' || process.env.BS_TOKEN_SECRET, +}; + +// Script Logic +//////////////// + +// Create an axios instance for our API +const api = axios.create({ + baseURL: bookStackConfig.base_url.replace(/\/$/, '') + '/api/', + timeout: 5000, + headers: {'Authorization': `Token ${bookStackConfig.token_id}:${bookStackConfig.token_secret}`}, +}); + +// Wrap the rest of our code in an async function, so we can await within. +(async function () { + + // Get our default schema structure and look up to BookStack + // to get a JSON view of the BookStack docs. + const postmanSchema = getBaseCollectionSchema(); + const {data: docs} = await api.get('/docs.json'); + + // Cycle over the endpoint categories within the API docs + for (const [category, endpoints] of Object.entries(docs)) { + // Create the schema for the postman collection, which represents + // a BookStack API category. + const postmanFolderSchema = { + name: category.toUpperCase(), + item: [], + }; + + // Cycle over the endpoints within the category + for (const endpoint of endpoints) { + postmanFolderSchema.item.push(getEndpointSchema(endpoint)); + } + + // Push our endpoint data into the postman collection + postmanSchema.item.push(postmanFolderSchema); + } + + // Output the postman collection data to the command line + console.log(JSON.stringify(postmanSchema, null, 2)); + +})().catch(err => { + + // Handle API errors + if (err.response) { + console.error(`Request failed with status ${err.response.status} [${err.response.statusText}]`); + return; + } + + // Output all other errors + console.error(err) +}); + + +/** + * Get the postman collection data for a specific endpoint. + * @param {Object} apiEndpoint + * @return {{request: {method, header: *[]}, response: *[], name: string}} + */ +function getEndpointSchema(apiEndpoint) { + // Create our base format for the postman schema for a single endpoint + const postmanEndpointSchema = { + name: `${apiEndpoint.name}`, + request: { + method: apiEndpoint.method, + header: [], + }, + response: [] + }; + + // Create the base format used to represent a URL + const url = { + raw: `{{BASE_URL}}/${apiEndpoint.uri}`, + host: ['{{BASE_URL}}'], + path: apiEndpoint.uri.split('/'), + query: [] + }; + + // If a listing endpoint, add the standard list params, + // although we leave them disabled by default. + if (apiEndpoint.controller_method === 'list') { + url.query = [ + { + "key": "count", + "value": "100", + "disabled": true + }, + { + "key": "offset", + "value": "0", + "disabled": true + }, + { + "key": "sort", + "value": "+name", + "disabled": true + }, + { + "key": "filter[id]", + "value": "5", + "disabled": true + } + ]; + } + + // Add the url to the request schema + postmanEndpointSchema.request.url = url; + + // Build a description for the endpoint + // Formats the body parameters, if existing, to shown their validations. + const description = [apiEndpoint.description]; + if (apiEndpoint.body_params) { + description.push('', '', 'Available body parameters:', ''); + for (const [name, validations] of Object.entries(apiEndpoint.body_params)) { + description.push(`${name}: ${validations.join(' :: ')}`); + } + } + postmanEndpointSchema.request.description = description.join('\n'); + + // If we have an example request, push it as default body JSON data + if (apiEndpoint.example_request) { + postmanEndpointSchema.request.header.push({ + "key": "Content-Type", + "value": "application/json" + }); + postmanEndpointSchema.request.body = { + mode: "raw", + raw: apiEndpoint.example_request, + options: { + raw: { + language: 'json' + } + } + } + } + + // Push an example of a response if we have one + if (apiEndpoint.example_response) { + postmanEndpointSchema.response.push({ + name: 'Example Response', + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + header: [ + { + "key": "Content-Type", + "value": "application/json" + }, + ], + body: apiEndpoint.example_response, + }); + } + + // Provide back the postman schema data + return postmanEndpointSchema; +} + +/** + * Get the base Postman collection schema data structure. + * Contains auth data and variables. + * @return {{item: *[], auth: {apikey: [{type: string, value: string, key: string},{type: string, value: string, key: string}], type: string}, variable: [{type: string, value: string, key: string},{type: string, value: string, key: string},{type: string, value: string, key: string}], event: [{listen: string, script: {type: string, exec: string[]}},{listen: string, script: {type: string, exec: string[]}}], info: {schema: string, name: string}}} + */ +function getBaseCollectionSchema() { + return { + info: { + name: "BookStack REST API", + schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + item: [ + ], + auth: { + type: "apikey", + apikey: [ + { + key: "value", + value: "Token {{TOKEN_ID}}:{{TOKEN_SECRET}}", + type: "string" + }, + { + key: "key", + value: "Authorization", + type: "string" + } + ] + }, + event: [ + { + listen: "prerequest", + script: { + type: "text/javascript", + exec: [ + "" + ] + } + }, + { + listen: "test", + script: { + type: "text/javascript", + exec: [ + "" + ] + } + } + ], + variable: [ + { + key: "TOKEN_ID", + value: "", + type: "default" + }, + { + key: "TOKEN_SECRET", + value: "", + type: "default" + }, + { + key: "BASE_URL", + value: "", + type: "default" + } + ] + }; +} \ No newline at end of file diff --git a/node-generate-postman-collection/package-lock.json b/node-generate-postman-collection/package-lock.json new file mode 100644 index 0000000..a0f1aeb --- /dev/null +++ b/node-generate-postman-collection/package-lock.json @@ -0,0 +1,58 @@ +{ + "name": "bookstack-generate-postman-collection", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "bookstack-generate-postman-collection", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.25.0" + } + }, + "node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + } + }, + "dependencies": { + "axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "requires": { + "follow-redirects": "^1.14.7" + } + }, + "follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + } + } +} diff --git a/node-generate-postman-collection/package.json b/node-generate-postman-collection/package.json new file mode 100644 index 0000000..e12316d --- /dev/null +++ b/node-generate-postman-collection/package.json @@ -0,0 +1,15 @@ +{ + "name": "bookstack-generate-postman-collection", + "version": "1.0.0", + "description": "This script will read the BookStack REST API and attempt to build a postman collection from it", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Dan Brown", + "license": "MIT", + "dependencies": { + "axios": "^0.25.0" + } +} diff --git a/node-generate-postman-collection/readme.md b/node-generate-postman-collection/readme.md new file mode 100644 index 0000000..ec59ebd --- /dev/null +++ b/node-generate-postman-collection/readme.md @@ -0,0 +1,45 @@ +# Generate Postman Collection + +This script will scan the BookStack API documentation and generate +out an importable collection for [Postman](https://www.postman.com/). + +**Note:** This has been built quite hastily so the output may not be +100% accurate but should massively speed up most use-cases. + +[An example of the output can be found here](https://gist.githubusercontent.com/ssddanbrown/de805abfdf1a1defb54500055de5e7ea/raw/7ec246a4d140c98313f3bcda00e1bac6d9e68b68/bs.postman_collection.json). + +The output collection will contain a folder for each of the API categories. +Collection variables are used to configure the API base url, token ID +and token secret. + +## Requirements + +You will need NodeJS installed (Tested on v16, may work on earlier versions). + +## Running + +First, download all the files in the same directory as this readme to a folder on your system +and run the below from within that directory. + +```bash +# Install NodeJS dependencies via NPM +npm install + +# Setup +# ALTERNATIVELY: Open the script and add to the empty strings in the variables at the top. +export BS_URL=https://bookstack.example.com # Set to be your BookStack base URL +export BS_TOKEN_ID=abc123 # Set to be your API token_id +export BS_TOKEN_SECRET=123abc # Set to be your API token_secret + +# Running the script +node index.js +``` + +The script outputs stdout on the command line, so you'll most likely want to redirect the output to a file. + +## Examples + +```bash +# Generate the collection to a 'bookstack.postman_collection.json' file. +node index.js > bookstack.postman_collection.json +``` \ No newline at end of file