mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 01:47:13 +00:00 
			
		
		
		
	
		
			
	
	
		
			236 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			236 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | 'use strict'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Documentation.js controller | ||
|  |  * | ||
|  |  * @description: A set of functions called "actions" of the `documentation` plugin. | ||
|  |  */ | ||
|  | 
 | ||
|  | // Core dependencies.
 | ||
|  | const path = require('path'); | ||
|  | 
 | ||
|  | // Public dependencies.
 | ||
|  | const fs = require('fs'); | ||
|  | const cheerio = require('cheerio'); | ||
|  | const _ = require('lodash'); | ||
|  | 
 | ||
|  | module.exports = { | ||
|  |   getInfos: async (ctx) => { | ||
|  |     try { | ||
|  |       const prefix = _.get(strapi.plugins, ['documentation', 'config', 'x-strapi-config', 'path'], '/documentation'); | ||
|  |       const service = strapi.plugins.documentation.services.documentation; | ||
|  |       const docVersions = service.retrieveDocumentationVersions(); | ||
|  |       const form = await service.retrieveFrontForm(); | ||
|  |        | ||
|  |       ctx.send({ docVersions, currentVersion: service.getDocumentationVersion(), prefix: `/${prefix}`.replace('//', '/'), form }); | ||
|  |     } catch(err) { | ||
|  |       ctx.badRequest(null, err.message); | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   index: async (ctx, next) => { | ||
|  |     // Read layout file.
 | ||
|  |     const layoutPath = path.join(strapi.config.appPath, 'plugins', 'documentation', 'public', 'index.html'); | ||
|  | 
 | ||
|  |     try { | ||
|  |       const layout = fs.readFileSync(layoutPath, 'utf8'); | ||
|  |       const $ = cheerio.load(layout); | ||
|  |        | ||
|  |       /** | ||
|  |        * We don't expose the specs using koa-static or something else due to security reasons. | ||
|  |        * That's why, we need to read the file localy and send the specs through it when we serve the Swagger UI. | ||
|  |        */ | ||
|  |       const { major, minor, patch } = ctx.params; | ||
|  |       const version = major && minor && patch ? `${major}.${minor}.${patch}` : strapi.plugins.documentation.config.info.version; | ||
|  |       const openAPISpecsPath = path.join(strapi.config.appPath, 'plugins', 'documentation', 'documentation', version , 'full_documentation.json');  | ||
|  |        | ||
|  |       try {   | ||
|  |         const documentation = fs.readFileSync(openAPISpecsPath, 'utf8'); | ||
|  |          | ||
|  |         // Remove previous Swagger configuration.
 | ||
|  |         $('.custom-swagger-ui').remove(); | ||
|  |         // Set new Swagger configuration
 | ||
|  |         $('body').append(`
 | ||
|  |           <script class="custom-swagger-ui"> | ||
|  |             window.onload = function() { | ||
|  | 
 | ||
|  |               // Build a system
 | ||
|  |               const ui = SwaggerUIBundle({ | ||
|  |                 url: "https://petstore.swagger.io/v2/swagger.json", | ||
|  |                 spec: ${JSON.stringify(JSON.parse(documentation))}, | ||
|  |                 dom_id: '#swagger-ui', | ||
|  |                 docExpansion: "none", | ||
|  |                 deepLinking: true, | ||
|  |                 presets: [ | ||
|  |                   SwaggerUIBundle.presets.apis, | ||
|  |                   SwaggerUIStandalonePreset | ||
|  |                 ], | ||
|  |                 plugins: [ | ||
|  |                   SwaggerUIBundle.plugins.DownloadUrl | ||
|  |                 ], | ||
|  |                 layout: "StandaloneLayout" | ||
|  |               }) | ||
|  | 
 | ||
|  |               window.ui = ui | ||
|  |             } | ||
|  |           </script> | ||
|  |         `);
 | ||
|  |            | ||
|  |         try { | ||
|  |           // Write the layout with the new Swagger configuration.
 | ||
|  |           fs.writeFileSync(layoutPath, $.html()); | ||
|  | 
 | ||
|  |           // Serve the file.
 | ||
|  |           ctx.url = path.basename(`${ctx.url}/index.html`); | ||
|  | 
 | ||
|  |           try { | ||
|  |             return await strapi.koaMiddlewares.static(`./plugins/documentation/public`)(ctx, next); | ||
|  |           } catch (e) { | ||
|  |             console.error(e); | ||
|  |           } | ||
|  |         } catch (e){ | ||
|  |           strapi.log.error(`Impossible to write the layout file at ${layoutPath}`); | ||
|  |           strapi.log.warn('This file needs to be writable to keep the documentation up-to-date.'); | ||
|  |           console.error(e); | ||
|  |         } | ||
|  |       } catch (e) { | ||
|  |         strapi.log.error(`Impossible to read OpenAPI specs at ${openAPISpecsPath}`); | ||
|  |         strapi.log.warn('This file is required to serve the documentation.'); | ||
|  |         console.error(e); | ||
|  |       } | ||
|  |     } catch (e) { | ||
|  |       strapi.log.error(`Impossible to read the layout file at ${layoutPath}`); | ||
|  |       strapi.log.warn('This file is required to serve the documentation.'); | ||
|  |       console.error(e); | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   loginView: async (ctx, next) => { | ||
|  |     const { error } = ctx.query; | ||
|  |     const layoutPath = path.join(strapi.config.appPath, 'plugins', 'documentation', 'public', 'login.html'); | ||
|  | 
 | ||
|  |     try { | ||
|  |       const layout = fs.readFileSync(layoutPath, 'utf8'); | ||
|  |       const $ = cheerio.load(layout); | ||
|  | 
 | ||
|  |       $('form').attr('action', `${strapi.plugins.documentation.config['x-strapi-config'].path}/login`); | ||
|  |       $('.error').text(_.isEmpty(error) ? '' : 'Wrong password...'); | ||
|  | 
 | ||
|  |       try { | ||
|  |         fs.writeFileSync(layoutPath, $.html()); | ||
|  | 
 | ||
|  |         ctx.url = path.basename(`${ctx.url}/login.html`); | ||
|  |         return await strapi.koaMiddlewares.static(`./plugins/documentation/public`)(ctx, next); | ||
|  |       } catch (e) { | ||
|  |         console.log(e); | ||
|  |       } | ||
|  |     } catch (e) { | ||
|  |       console.log(e); | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   login: async (ctx) => { | ||
|  |     const { body: { password } } = ctx.request; | ||
|  |     const { password: storedPassword } = await strapi.store({ | ||
|  |       environment: '', | ||
|  |       type: 'plugin', | ||
|  |       name: 'documentation', | ||
|  |       key: 'config', | ||
|  |     }).get(); | ||
|  |     const isValid = strapi.plugins['users-permissions'].services.user.validatePassword(password, storedPassword); | ||
|  |     let querystring = '?error=password'; | ||
|  | 
 | ||
|  |     if (isValid) { | ||
|  |       ctx.session.documentation = password; | ||
|  |       querystring = ''; | ||
|  |     } | ||
|  | 
 | ||
|  |     ctx.redirect(`${strapi.plugins.documentation.config['x-strapi-config'].path}${querystring}`); | ||
|  |   }, | ||
|  |    | ||
|  |   regenerateDoc: async (ctx) => { | ||
|  |     const service = strapi.plugins.documentation.services.documentation; | ||
|  |     const documentationVersions = service.retrieveDocumentationVersions().map(el => el.version); | ||
|  |     const { request: { body: { version }, admin } } = ctx; | ||
|  | 
 | ||
|  |     if (_.isEmpty(version)) { | ||
|  |       return ctx.badRequest(null, admin ? 'documentation.error.noVersion' : 'Please provide a version.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!documentationVersions.includes(version)) { | ||
|  |       return ctx.badRequest(null, admin ? 'documentation.error.regenerateDoc.versionMissing' : 'The version you are trying to generate does not exist.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     try { | ||
|  |       strapi.reload.isWatching = false; | ||
|  |       const fullDoc = service.generateFullDoc(version); | ||
|  |       const documentationPath = service.getMergedDocumentationPath(version); | ||
|  |       // Write the file
 | ||
|  |       fs.writeFileSync(path.resolve(documentationPath, 'full_documentation.json'), JSON.stringify(fullDoc, null, 2), 'utf8'); | ||
|  |       ctx.send({ ok: true }); | ||
|  |     } catch(err) { | ||
|  |       ctx.badRequest(null, admin ? 'documentation.error.regenerateDoc' : 'An error occured'); | ||
|  |     } finally { | ||
|  |       strapi.reload.isWatching = true; | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   deleteDoc: async (ctx) => { | ||
|  |     strapi.reload.isWatching = false; | ||
|  |     const service = strapi.plugins.documentation.services.documentation; | ||
|  |     const documentationVersions = service.retrieveDocumentationVersions().map(el => el.version); | ||
|  |     const { request: { params: { version }, admin } } = ctx; | ||
|  | 
 | ||
|  |     if (_.isEmpty(version)) { | ||
|  |       return ctx.badRequest(null, admin ? 'documentation.error.noVersion' : 'Please provide a version.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!documentationVersions.includes(version)) { | ||
|  |       return ctx.badRequest(null, admin ? 'documentation.error.deleteDoc.versionMissing' : 'The version you are trying to delete does not exist.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     try { | ||
|  |       await service.deleteDocumentation(version); | ||
|  |       ctx.send({ ok: true }); | ||
|  |     } catch(err) { | ||
|  |       ctx.badRequest(null, admin ? 'notification.error' : err.message); | ||
|  |     } finally { | ||
|  |       strapi.reload.isWatching = true; | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   updateSettings: async (ctx) => { | ||
|  |     const { admin, body: { restrictedAccess, password } } = ctx.request; | ||
|  |     const usersPermService = strapi.plugins['users-permissions'].services; | ||
|  |     const pluginStore = strapi.store({ | ||
|  |       environment: '', | ||
|  |       type: 'plugin', | ||
|  |       name: 'documentation', | ||
|  |     }); | ||
|  |     const prevConfig = await pluginStore.get({ key: 'config' }); | ||
|  | 
 | ||
|  |     if (restrictedAccess && _.isEmpty(password)) { | ||
|  |       return ctx.badRequest(null, admin ? 'users-permissions.Auth.form.error.password.provide' : 'Please provide a password'); | ||
|  |     } | ||
|  | 
 | ||
|  |     const isNewPassword = !_.isEmpty(password) && password !== prevConfig.password; | ||
|  | 
 | ||
|  |     if (isNewPassword && usersPermService.user.isHashed(password)) { | ||
|  |       // Throw an error if the password selected by the user
 | ||
|  |       // contains more than two times the symbol '$'.
 | ||
|  |       return ctx.badRequest(null, admin ? 'users-permissions.Auth.form.error.password.format' : 'our password cannot contain more than three times the symbol `$`.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (isNewPassword) { | ||
|  |       prevConfig.password = await usersPermService.user.hashPassword({ password }); | ||
|  |     } | ||
|  |      | ||
|  |      | ||
|  |     _.set(prevConfig, 'restrictedAccess', restrictedAccess); | ||
|  |      | ||
|  |     await pluginStore.set({ key: 'config', value: prevConfig }); | ||
|  | 
 | ||
|  |     return ctx.send({ ok: true }); | ||
|  |   }, | ||
|  | }; |