diff --git a/lib/components/services/file-downloading/index.js b/lib/components/services/file-downloading/index.js index afa740fe..a916fabe 100644 --- a/lib/components/services/file-downloading/index.js +++ b/lib/components/services/file-downloading/index.js @@ -13,7 +13,21 @@ class FileDownloadingService { const { app } = server const router = async app => { - app.get('/:downloadKey', (request, reply) => this.serveFile(request, reply)) + const opts = { + schema: { + description: 'Download a file', + params: { + type: 'object', + properties: { + downloadKey: { + type: 'string', + description: 'Download key' + } + } + } + } + } + app.get('/:downloadKey', opts, (request, reply) => this.serveFile(request, reply)) } app.register(router, { prefix: downloadPrefix }) diff --git a/lib/components/services/file-uploading/index.js b/lib/components/services/file-uploading/index.js index 9355a451..cd25ca29 100644 --- a/lib/components/services/file-uploading/index.js +++ b/lib/components/services/file-uploading/index.js @@ -27,7 +27,12 @@ class FileUploadingService { ) const router = async app => { - const opts = { preValidation: [app.jwtCheck] } + const opts = { + preValidation: [app.jwtCheck], + schema: { + description: 'Upload a file' + } + } app.post('/', opts, (request, reply) => this.upload(request, reply)) } diff --git a/lib/components/services/server/index.js b/lib/components/services/server/index.js index e1d61543..8da71b9d 100644 --- a/lib/components/services/server/index.js +++ b/lib/components/services/server/index.js @@ -1,6 +1,9 @@ const fastify = require('fastify') -const helmet = require('@fastify/helmet') const cors = require('@fastify/cors') +const swagger = require('@fastify/swagger') +const swaggerUi = require('@fastify/swagger-ui') +const swaggerOpts = require('./swagger') +const helmet = require('@fastify/helmet') const addStaticDir = require('./add-static-dir') @@ -19,8 +22,31 @@ class FastifyServerService { loggerInstance: this.logger }) - app.register(helmet, { - crossOriginResourcePolicy: { policy: 'cross-origin' } + app.register(swagger, swaggerOpts) + app.register(swaggerUi, { + routePrefix: '/documentation', + uiConfig: { + validatorUrl: null, + supportedSubmitMethods: [] + } + }) + app.after(() => { + app.register(helmet, instance => { + return { + contentSecurityPolicy: { + directives: { + ...helmet.contentSecurityPolicy.getDefaultDirectives(), + 'form-action': ['\'self\''], + 'img-src': ['\'self\'', 'data:', 'validator.swagger.io'], + 'script-src': ['\'self\''].concat(instance.swaggerCSP.script), + 'style-src': ['\'self\'', 'https:'].concat( + instance.swaggerCSP.style + ) + } + }, + crossOriginResourcePolicy: { policy: 'cross-origin' } + } + }) }) app.addContentTypeParser( @@ -75,7 +101,16 @@ class FastifyServerService { ) */ listen (port, host, callback) { - this.app.listen({ port, host }, callback) + return new Promise((resolve, reject) => { + this.app.listen({ port, host }, (err) => { + if (err) { + if (callback) callback(err) + return reject(err) + } + if (callback) callback(null) + resolve() + }) + }) } /** diff --git a/lib/components/services/server/swagger.js b/lib/components/services/server/swagger.js new file mode 100644 index 00000000..279ddcfd --- /dev/null +++ b/lib/components/services/server/swagger.js @@ -0,0 +1,33 @@ +module.exports = { + swagger: { + info: { + title: 'Tymly API', + description: 'Provides a CORS-enabled Fastify server' + }, + // host: '0.0.0.0', + // schemes: ['http', 'https'], + // schemes: ['http'], + consumes: ['application/json'], + produces: ['application/json'] + // securityDefinitions: { + // Bearer: { + // type: 'apiKey', + // name: 'Authorization', + // in: 'header' + // } + // }, + // security: [ + // { + // Bearer: [] + // } + // ] + // security: [{ bearerAuth: [] }], + // securityDefinitions: { + // bearerAuth: { + // type: "http", + // scheme: "bearer", + // bearerFormat: "JWT" + // } + // } + } +} diff --git a/lib/components/services/statebox-api/index.js b/lib/components/services/statebox-api/index.js index 81cee615..a5dbdd4a 100644 --- a/lib/components/services/statebox-api/index.js +++ b/lib/components/services/statebox-api/index.js @@ -7,10 +7,10 @@ class StateboxApiService { const router = async app => { const opts = { preValidation: [app.jwtCheck] } - app.post('/', opts, routes.startExecution) - app.get('/:executionName', opts, routes.describeExecution) - app.put('/:executionName', opts, routes.executionAction) - app.delete('/:executionName', opts, routes.stopExecution) + app.post('/', { ...opts, schema: routes.startExecution.schema }, routes.startExecution) + app.get('/:executionName', { ...opts, schema: routes.describeExecution.schema }, routes.describeExecution) + app.put('/:executionName', { ...opts, schema: routes.executionAction.schema }, routes.executionAction) + app.delete('/:executionName', { ...opts, schema: routes.stopExecution.schema }, routes.stopExecution) } app.register(router, { prefix: '/executions' }) diff --git a/lib/components/services/statebox-api/routes/common-schema.js b/lib/components/services/statebox-api/routes/common-schema.js new file mode 100644 index 00000000..fe642bf6 --- /dev/null +++ b/lib/components/services/statebox-api/routes/common-schema.js @@ -0,0 +1,10 @@ +const schema = { + tags: ['executions'] + // security: [ + // { + // Bearer: [] + // } + // ] +} + +module.exports = schema diff --git a/lib/components/services/statebox-api/routes/describe-execution.js b/lib/components/services/statebox-api/routes/describe-execution.js index 45858aca..34a0b4d9 100644 --- a/lib/components/services/statebox-api/routes/describe-execution.js +++ b/lib/components/services/statebox-api/routes/describe-execution.js @@ -1,6 +1,19 @@ const respond = require('../../../../util/respond') +const schema = { + ...require('./common-schema'), + description: 'Describe an existing Statebox execution', + params: { + type: 'object', + properties: { + executionName: { + type: 'string', + description: 'Execution name' + } + } + } +} -module.exports = function describeExecution (request, reply) { +function describeExecution (request, reply) { const jwtAuthService = this.services.jwtAuth const statebox = this.services.statebox @@ -31,3 +44,6 @@ module.exports = function describeExecution (request, reply) { } ) } + +module.exports = describeExecution +module.exports.schema = schema diff --git a/lib/components/services/statebox-api/routes/execution-action.js b/lib/components/services/statebox-api/routes/execution-action.js index ced8e34c..0d522505 100644 --- a/lib/components/services/statebox-api/routes/execution-action.js +++ b/lib/components/services/statebox-api/routes/execution-action.js @@ -1,8 +1,36 @@ const { cloneDeep } = require('lodash') const boom = require('@hapi/boom') const actions = require('../actions/index') +const schema = { + ...require('./common-schema'), + description: 'Perform an action on an existing Statebox execution', + params: { + type: 'object', + properties: { + executionName: { + type: 'string', + description: 'Execution name' + } + } + }, + body: { + type: 'object', + properties: { + action: { + type: 'string', + description: 'Action to perform on Statebox execution', + enum: [ + 'SendTaskSuccess', + 'SendTaskHeartbeat', + 'SendTaskRevivification', + 'WaitUntilStoppedRunning' + ] + } + } + } +} -module.exports = function (request, reply) { +function executionAction (request, reply) { const jwtAuthService = this.services.jwtAuth const statebox = this.services.statebox @@ -28,3 +56,6 @@ module.exports = function (request, reply) { ) } } + +module.exports = executionAction +module.exports.schema = schema diff --git a/lib/components/services/statebox-api/routes/start-execution.js b/lib/components/services/statebox-api/routes/start-execution.js index 008ad4f9..ba7dfd89 100644 --- a/lib/components/services/statebox-api/routes/start-execution.js +++ b/lib/components/services/statebox-api/routes/start-execution.js @@ -1,7 +1,43 @@ const { cloneDeep } = require('lodash') const respond = require('../../../../util/respond') +const cloneOrDefault = obj => obj ? cloneDeep(obj) : {} -module.exports = function startExecution (request, reply) { +const schema = { + ...require('./common-schema'), + description: 'Start a new Statebox execution', + body: { + type: 'object', + properties: { + stateMachineName: { + type: 'string' + }, + input: { + type: 'object' + }, + options: { + type: 'object', + properties: { + instigatingClient: { + type: 'object', + properties: { + appName: { + type: 'string' + }, + domain: { + type: 'string' + } + } + }, + sendResponse: { + type: 'string' + } + } + } + } + } +} + +function startExecution (request, reply) { const jwtAuthService = this.services.jwtAuth const statebox = this.services.statebox @@ -30,6 +66,5 @@ module.exports = function startExecution (request, reply) { ) } -function cloneOrDefault (obj) { - return obj ? cloneDeep(obj) : {} -} +module.exports = startExecution +module.exports.schema = schema diff --git a/lib/components/services/statebox-api/routes/stop-execution.js b/lib/components/services/statebox-api/routes/stop-execution.js index 99c08b62..fbe9c603 100644 --- a/lib/components/services/statebox-api/routes/stop-execution.js +++ b/lib/components/services/statebox-api/routes/stop-execution.js @@ -1,6 +1,19 @@ const respond = require('../../../../util/respond') +const schema = { + ...require('./common-schema'), + description: 'Stop an existing Statebox execution', + params: { + type: 'object', + properties: { + executionName: { + type: 'string', + description: 'Execution name' + } + } + } +} -module.exports = function cancelTymlyRoute (request, reply) { +function stopExecution (request, reply) { const jwtAuthService = this.services.jwtAuth const statebox = this.services.statebox @@ -25,3 +38,6 @@ module.exports = function cancelTymlyRoute (request, reply) { } ) } + +module.exports = stopExecution +module.exports.schema = schema diff --git a/package.json b/package.json index 5ecb5af6..cc01d16b 100644 --- a/package.json +++ b/package.json @@ -19,30 +19,32 @@ }, "main": "./lib/index.js", "dependencies": { - "axios": "1.11.0", - "@hapi/boom": "10.0.1", - "cpr": "3.0.1", - "debug": "4.4.1", - "dottie": "2.0.6", - "fastify": "5.4.0", "@fastify/cors": "11.0.1", "@fastify/helmet": "13.0.1", "@fastify/jwt": "9.1.0", "@fastify/multipart": "9.0.3", "@fastify/static": "8.2.0", + "@fastify/swagger": "9.0.0", + "@fastify/swagger-ui": "^5.2.5", + "@hapi/boom": "10.0.1", + "axios": "1.11.0", + "cpr": "3.0.1", + "debug": "4.4.1", + "dottie": "2.0.6", + "fastify": "5.4.0", "jsonwebtoken": "9.0.2", "lodash": "4.17.21", "uuid": "11.1.0" }, "devDependencies": { "@semantic-release/changelog": "6.0.3", - "@semantic-release/release-notes-generator": "14.1.0", - "@semantic-release/git": "10.0.1", "@semantic-release/exec": "7.1.0", + "@semantic-release/git": "10.0.1", + "@semantic-release/release-notes-generator": "14.1.0", "@wmfs/tymly": "1.315.0", + "@wmfs/tymly-cardscript-plugin": "1.51.0", "@wmfs/tymly-rbac-plugin": "1.32.0", "@wmfs/tymly-rest-client-plugin": "1.34.0", - "@wmfs/tymly-cardscript-plugin": "1.51.0", "chai": "4.5.0", "chai-string": "1.6.0", "codecov": "3.8.3", diff --git a/test/download-spec.js b/test/download-spec.js index d37ad870..42a3e351 100644 --- a/test/download-spec.js +++ b/test/download-spec.js @@ -44,10 +44,8 @@ describe('Download tests', function () { downloadService = tymlyServices.fileDownloading statebox = tymlyServices.statebox const server = tymlyServices.server - server.listen(PORT, HOST, (err) => { - expect(err).to.eql(null) - console.log(`Listening on ${PORT}`) - }) + await server.listen(PORT, HOST) + console.log(`Listening on ${PORT}`) }) describe('service tests', () => {