diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 69f48b62..28747aa1 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -51,6 +51,9 @@ services: # will be sent through this proxy if set. # - OUTGOING_PROXY=http://proxy:3128 + # Set to true to expose the Swagger specification at /swagger.json + # - SWAGGER_EXPOSED=false + # - S3_ENDPOINT= # - S3_REGION= # - S3_ACCESS_KEY_ID= diff --git a/docker-compose.yml b/docker-compose.yml index c60582a5..9829ad35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,6 +65,9 @@ services: # which you can control via OUTGOING_BLOCKED_* and OUTGOING_ALLOWED_* below. # - OUTGOING_PROXY=http://proxy:3128 + # Set to true to expose the Swagger specification at /swagger.json + # - SWAGGER_EXPOSED=false + # - S3_ENDPOINT= # - S3_REGION= # - S3_ACCESS_KEY_ID= diff --git a/server/.env.sample b/server/.env.sample index d4afefdf..46dae16c 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -42,6 +42,9 @@ SECRET_KEY=notsecretkey # will be sent through this proxy if set. # OUTGOING_PROXY=http://proxy:3128 +# Set to true to expose the Swagger specification at /swagger.json +# SWAGGER_EXPOSED=false + # S3_ENDPOINT= # S3_REGION= # S3_ACCESS_KEY_ID= diff --git a/server/api/controllers/swagger/show.js b/server/api/controllers/swagger/show.js new file mode 100644 index 00000000..3b358ff1 --- /dev/null +++ b/server/api/controllers/swagger/show.js @@ -0,0 +1,28 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +const fs = require('fs'); +const path = require('path'); + +const SWAGGER_PATH = path.join(sails.config.appPath, 'swagger.json'); + +module.exports = { + async fn() { + if (!sails.config.custom.swaggerExposed) { + return this.res.notFound(); + } + + let specification; + try { + const content = fs.readFileSync(SWAGGER_PATH, 'utf8'); + specification = JSON.parse(content); + } catch (error) { + sails.log.warn('swagger.json not found, run "npm run swagger:generate" to create it'); + return this.res.notFound(); + } + + return specification; + }, +}; diff --git a/server/build.js b/server/build.js index eb8cf27b..4609be1a 100644 --- a/server/build.js +++ b/server/build.js @@ -2,6 +2,10 @@ const fs = require('fs'); const path = require('path'); // eslint-disable-next-line import/no-extraneous-dependencies const ignore = require('ignore'); +// eslint-disable-next-line import/no-extraneous-dependencies +const swaggerJsdoc = require('swagger-jsdoc'); + +const swaggerConfig = require('./config/swagger'); const OUT_DIR = 'dist'; @@ -37,6 +41,9 @@ const build = (src, dest) => { fs.copyFileSync(srcPath, destPath); } } + + const specification = swaggerJsdoc(swaggerConfig); + fs.writeFileSync(path.join(dest, 'swagger.json'), JSON.stringify(specification, null, 2)); }; build('./', OUT_DIR); diff --git a/server/config/custom.js b/server/config/custom.js index 8ba7f568..5378e407 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -64,6 +64,7 @@ module.exports.custom = { showDetailedAuthErrors: process.env.SHOW_DETAILED_AUTH_ERRORS === 'true', outgoingProxy: process.env.OUTGOING_PROXY, + swaggerExposed: process.env.SWAGGER_EXPOSED === 'true', s3Endpoint: process.env.S3_ENDPOINT, s3Region: process.env.S3_REGION, diff --git a/server/config/policies.js b/server/config/policies.js index 8c2879b4..9c62dd20 100644 --- a/server/config/policies.js +++ b/server/config/policies.js @@ -44,6 +44,7 @@ module.exports.policies = { '_internal/update-config': ['is-authenticated', 'is-internal'], + 'swagger/show': true, 'bootstrap/show': true, 'terms/show': true, 'access-tokens/create': true, diff --git a/server/config/routes.js b/server/config/routes.js index c4e325db..38cc16aa 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -235,6 +235,8 @@ module.exports.routes = { 'PATCH /api/_internal/config': '_internal/update-config', + 'GET /swagger.json': 'swagger/show', + 'GET /favicons/*': { fn: protectedStaticDirServer('/favicons', () => sails.config.custom.faviconsPathSegment), skipAssets: false,