UNPKG

taboo-cms

Version:
472 lines (431 loc) 14.7 kB
const path = require('path'); const ejs = require('ejs'); const Logger = require('./Logger'); const FilesHelper = require('./FilesHelper'); const EjsHelper = require('./EjsHelper'); class CmsHelper { constructor() { this.app = null; this.isAllowedImplementation = () => { Logger.warn('Requires ACL module implementation'); }; } setup(app) { this.app = app; } getAllModuleNames() { return FilesHelper.getAllDirNames(path.join(this.app.cwd, this.app.config.server.modulesDir)); } setupAllModules() { const moduleNames = this.getAllModuleNames(); moduleNames.map(this.getModuleSetup.bind(this)); } getModuleSetup(moduleName) { const modulePath = path.join(this.app.cwd, this.app.config.server.modulesDir, moduleName); const moduleConfigPath = path.join(modulePath, this.app.config.server.modules.configFile); this.app.modules[moduleName] = { name: moduleName, path: modulePath, config: {}, controllers: {}, modelConfigs: {}, models: {}, services: {}, helpers: {}, }; const module = this.app.modules[moduleName]; const { modules: modulesConfig } = this.app.config.server; const moduleControllers = FilesHelper.getAllFileNames(path.join(modulePath, modulesConfig.controllersDir)); const moduleModels = FilesHelper.getAllFileNames(path.join(modulePath, modulesConfig.modelsDir)); const moduleServices = FilesHelper.getAllFileNames(path.join(modulePath, modulesConfig.servicesDir)); const moduleHelpers = FilesHelper.getAllFileNames(path.join(modulePath, modulesConfig.helpersDir)); try { // Order is important this.setupHelpers(module, moduleHelpers); this.setupModels(module, moduleModels); this.setupServices(module, moduleServices); this.setupControllers(module, moduleControllers); if (FilesHelper.fileExists(moduleConfigPath)) { module.config = require(moduleConfigPath); } if (module.config && module.config.afterModulesSetup) { this.app.afterModulesSetup.push(module.config.afterModulesSetup); } if (module.config && module.config.afterModelsSetup) { this.app.afterModelsSetup.push(module.config.afterModelsSetup); } this.parseAcl(module); } catch (e) { Logger.error(`Failed to load module '${moduleName}'`); Logger.error(e); } } parseAcl(module) { if (module.config && module.config.acl) { if (module.config.acl.isAllowedImplementation) { this.isAllowedImplementation = module.config.acl.isAllowedImplementation; } if (module.config.acl.resources) { module.config.acl.resources.map(resource => { if (this.app.aclResources.indexOf(resource) === -1) { this.app.aclResources.push(resource); } else { throw new Error(`ACL resource '${resource}' is already described, make sure it has a unique name`); } }); } } } setupControllers(module, moduleControllers) { if (moduleControllers) { [...moduleControllers].map(name => { const prettyName = name.replace('Controller.', '.').replace('.js', ''); module.controllers[prettyName] = require(path.join( module.path, this.app.config.server.modules.controllersDir, name )); }); } } setupModels(module, moduleModels) { if (moduleModels) { [...moduleModels].map(name => { const prettyName = name.replace('Model.', '.').replace('.js', ''); module.modelConfigs[prettyName] = require(path.join( module.path, this.app.config.server.modules.modelsDir, name )); }); } } setupServices(module, moduleServices) { if (moduleServices) { [...moduleServices].map(name => { const prettyName = name.replace('Service.', '.').replace('.js', ''); module.services[prettyName] = require(path.join(module.path, this.app.config.server.modules.servicesDir, name)); }); } } setupHelpers(module, moduleHelpers) { if (moduleHelpers) { [...moduleHelpers].map(name => { const prettyName = name.replace('Helper.', '.').replace('.js', ''); module.helpers[prettyName] = require(path.join(module.path, this.app.config.server.modules.helpersDir, name)); }); } } setupPolicies() { const policies = FilesHelper.getAllFileNames(path.join(this.app.cwd, this.app.config.server.policiesDir)); if (policies) { [...policies].map(policy => { try { let prettyName = policy.replace('.js', ''); this.app.policies[prettyName] = require(path.join(this.app.cwd, this.app.config.server.policiesDir, policy)); } catch (e) { Logger.error('Error loading policy', e); } }); } } getRouterRouteArgs(route) { const args = []; args.push(this.getRouteInitialSetup(route)); this.setupRoutePolicies(args, route); args.push(this.getRouteHandler(route)); return args; } getRouteInitialSetup(route) { return async (ctx, next) => { if (ctx.route) { ctx.taboo.moduleRoute = route; if (route.options && route.options.errorResponseAsJson) { ctx.taboo.errorResponseAsJson = true; } if (route.options && route.options.aclResource) { ctx.taboo.aclResource = route.options.aclResource; } } return next(); }; } setupRoutePolicies(args, route) { const { globalPolicies = [] } = this.app.config.server; const { policies = [], options: { disableGlobalPolicies = false } = {} } = route; let allPolicies; if (disableGlobalPolicies) { allPolicies = policies; } else { allPolicies = globalPolicies.concat(policies); } if (allPolicies && allPolicies.length > 0) { allPolicies.map(policyName => { if (this.app.policies[policyName]) { args.push(this.app.policies[policyName]); } }); } } getRouteHandler(route) { return route.action; } getModuleModel(module, model) { const { modules } = this.app; model = model.replace('Model', ''); if (modules[module] && modules[module].models && modules[module].models[model]) { return modules[module].models[model]; } return null; } getModuleController(module, controller) { const { modules } = this.app; controller = controller.replace('Controller', ''); if (modules[module] && modules[module].controllers && modules[module].controllers[controller]) { return modules[module].controllers[controller]; } return null; } getModuleService(module, service) { const { modules } = this.app; service = service.replace('Service', ''); if (modules[module] && modules[module].services && modules[module].services[service]) { return modules[module].services[service]; } return null; } getModuleHelper(module, helper) { const { modules } = this.app; helper = helper.replace('Helper', ''); if (modules[module] && modules[module].helpers && modules[module].helpers[helper]) { return modules[module].helpers[helper]; } return null; } getPageViewPath(moduleRoute, viewName) { const { server } = this.app.config; let pagePath = null; let view; if (moduleRoute && moduleRoute.action && moduleRoute.action.name && moduleRoute.modulePath) { view = `${moduleRoute.action.name.replace('bound ', '')}`; if (viewName) { view = viewName; } pagePath = path.join(moduleRoute.modulePath, server.modules.viewsDir, `${view}.${server.views.extension}`); if (!FilesHelper.fileExists(pagePath)) { pagePath = path.join( moduleRoute.modulePath, server.modules.viewsDir, `${server.modules.defaultView}.${server.views.extension}` ); } } return pagePath; } getLayoutPath(layoutName) { const { server } = this.app.config; const layout = layoutName || server.views.defaultLayout; return path.join(this.app.cwd, server.views.layoutsDir, `${layout}.${server.views.extension}`); } async getServerResponse(ctx) { const page = await this.getPage(ctx.taboo.moduleRoute, ctx.view._view); const layout = await this.getLayout(ctx.view._layout); return this.composeResponse(ctx, layout, page, ctx.view); } async getPage(moduleRoute, viewName) { const pagePath = this.getPageViewPath(moduleRoute, viewName); if (pagePath) { try { return await FilesHelper.readFile(pagePath); } catch (e) { Logger.error(e); throw Error('View not found'); } } } async getLayout(layoutName) { const layoutPath = this.getLayoutPath(layoutName); try { return await FilesHelper.readFile(layoutPath); } catch (e) { Logger.error(e); throw Error('Layout not found'); } } async getServerErrorResponse(err, ctx) { const page = await this.getErrorPage(err); const layout = await this.getErrorLayout(err); return this.composeResponse(ctx, layout, page, { error: err.message, }); } async getErrorPage(err) { const { server: { views }, } = this.app.config; let errorPage = `${views.defaultErrorView}.${views.extension}`; let errorStatusPage = `${err.status}.${views.extension}`; let pagePath; if (err.status && FilesHelper.fileExists(path.join(this.app.cwd, views.errorsDir, errorStatusPage))) { errorPage = errorStatusPage; } try { pagePath = path.join(this.app.cwd, views.errorsDir, errorPage); return await FilesHelper.readFile(pagePath); } catch (e) { Logger.error(e); pagePath = path.join(this.app.cwd, views.errorsDir, views.defaultErrorView); return FilesHelper.readFile(pagePath); } } async getErrorLayout(err, ctx) { const { views } = this.app.config.server; const layoutPath = path.join(this.app.cwd, views.layoutsDir, `${views.defaultErrorLayout}.${views.extension}`); try { return await FilesHelper.readFile(layoutPath); } catch (e) { ctx.status = 500; ctx.body = e.message; } } composeResponse(ctx, layoutTpl, pageTpl, params) { const { config } = this.app; const { taboo = {} } = ctx; const clientConfig = this.getClientConfig(ctx); let viewParams = Object.assign( { _clientConfig: clientConfig, _clientConfigJson: JSON.stringify(clientConfig), _version: config.version, _env: config.environment, _debug: config.debug, pageTitle: config.server.views.defaultPageTitle, language: taboo.language, locale: taboo.locale, translations: taboo.translations, flashMessages: ctx.flashMessages, }, params ); const tplConfig = Object.assign(viewParams, { helpers: EjsHelper.getAllHelpers(viewParams), }); ctx.set('Content-Language', viewParams.locale); tplConfig._body = ejs.render(pageTpl, tplConfig, config.server.ejsOptions); return ejs.render(layoutTpl, tplConfig, config.server.ejsOptions); } getClientConfig(ctx) { const { config } = this.app; const { taboo = {} } = ctx; return Object.assign( {}, config.client, { env: config.environment, debug: config.debug, locale: taboo.locale, language: taboo.language, translations: taboo.translations, server: { port: config.server.port, }, // sockets: { // port: config.sockets.port, // }, }, taboo.clientConfig ); } setDefaultLanguageParams(ctx) { const { i18n } = this.app.config; ctx.taboo.language = i18n.defaultLanguage; ctx.taboo.locale = i18n.defaultLocale; ctx.taboo.translations = {}; if (this.app.locales[ctx.taboo.locale]) { ctx.taboo.translations = this.app.locales[ctx.taboo.locale]; } } setDefaultAdminLanguageParams(ctx) { const { i18n: { admin: i18nAdmin = {} }, } = this.app.config; ctx.taboo.adminLanguage = i18nAdmin.defaultLanguage; ctx.taboo.adminLocale = i18nAdmin.defaultLocale; ctx.taboo.adminTranslations = {}; if (this.app.adminLocales[ctx.taboo.adminLocale]) { ctx.taboo.adminTranslations = this.app.adminLocales[ctx.taboo.adminLocale]; } } loadLocales(localesDir, localesNamespace = 'locales') { const localesPath = path.join(this.app.cwd, localesDir); const allLocales = FilesHelper.getAllFileNames(localesPath); if (allLocales) { [...allLocales].map(locale => { if (locale && locale.indexOf('.js') !== -1) { const localeName = locale.replace('.js', ''); const localePath = path.join(this.app.cwd, localesDir, locale); try { this.app[localesNamespace][localeName] = require(localePath); } catch (e) { Logger.error(e); } } }); } } getLocalesArray(admin = false) { const helper = {}; const locales = []; const localesArray = []; const appLocales = admin ? this.app.adminLocales : this.app.locales; let item; for (let locale in appLocales) { if (locales.indexOf(locale) === -1) { locales.push(locale); } for (let key in appLocales[locale]) { if (!helper.hasOwnProperty(key)) { helper[key] = {}; } helper[key][locale] = appLocales[locale][key]; } } for (let key in helper) { item = {}; item.key = key; locales.map(locale => { item[locale] = appLocales[locale][key]; }); localesArray.push(item); } return localesArray; } composeTemplate(ctx, tpl, params) { const { config } = this.app; const { taboo = {} } = ctx; const clientConfig = this.getClientConfig(ctx); let viewParams = Object.assign( { _clientConfig: clientConfig, _clientConfigJson: JSON.stringify(clientConfig), _version: config.version, _env: config.environment, _debug: config.debug, pageTitle: config.server.views.defaultPageTitle, language: taboo.language, locale: taboo.locale, translations: taboo.translations, flashMessages: ctx.flashMessages, }, params ); const tplConfig = Object.assign(viewParams, { helpers: EjsHelper.getAllHelpers(viewParams), }); return ejs.render(tpl, tplConfig, config.server.ejsOptions); } isAllowed(subject, resource) { return this.isAllowedImplementation(subject, resource); } } module.exports = new CmsHelper();