UNPKG

sgapps-server

Version:
715 lines (659 loc) 16.2 kB
const TemplateManagerViewer = require("../prototypes/server/extend/template-manager/viewer"); /** * @memberof SGAppsServer.NodeJsMvc.Controller * @typedef {object} View * @property {string} name * @property {string} path * @property {string} code */; /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action * @callback OptionCapture * @param {SGAppsServerRequest} request * @param {SGAppsServerResponse} response * @param {SGAppsServer} server * @param {SGAppsServer.NodeJsMvc.Controller} controller * @param {SGAppsServer.NodeJsMvc.Controller.Action} action */ /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action * @typedef {object} Options * @property {boolean} public * @property {boolean} postData * @property {number} maxPostSize * @property {SGAppsServer.NodeJsMvc.Controller.Action.OptionCapture} [capture] */; /** * @memberof SGAppsServer.NodeJsMvc.Controller * @class * @name Action * @param {string} actionName * @param {SGAppsServer.NodeJsMvc.Controller} controller * @param {SGAppsServer.NodeJsMvc.Controller.Action.Options} options * @param {SGAppsServer} server */ function NodeJsMvcAction(actionName, controller, options, server ) { var _config = Object.assign( { public: false, postData: false, maxPostSize: server.MAX_POST_SIZE, capture: null }, options || {} ); /** * @private * @type {SGAppsServer.NodeJsMvc.Controller.Action} */ //@ts-ignore var actionObject = { /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @name controller * @type {SGAppsServer.NodeJsMvc.Controller} */ controller : controller, /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @name name * @type {string} */ name : actionName, /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @method run * @param {SGAppsServerRequest} request * @param {SGAppsServerResponse} response */ run : function(request, response) { if ( ('capture' in _config) && _config.capture ) { _config.capture( request, response, server, controller, actionObject ); } else { response.sendError(Error('[Server.NodeJsMvc.Controller.Action] is empty')); } } }; /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @name public * @type {boolean} */ Object.defineProperty( actionObject, 'public', { get: () => _config.public, set: (v) => { _config.public = !!v; } } ); /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @name postData * @type {boolean} */ Object.defineProperty( actionObject, 'postData', { get: () => _config.postData, set: (v) => { _config.postData = !!v; } } ); /** * @memberof SGAppsServer.NodeJsMvc.Controller.Action# * @name maxPostSize * @type {number} */ Object.defineProperty( actionObject, 'maxPostSize', { get: () => _config.maxPostSize, set: (v) => { _config.maxPostSize = v; } } ); return actionObject; } /** * @memberof SGAppsServer.NodeJsMvc * @class * @name Controller * @param {string} controllerName * @param {object} options * @param {object} [options.shared] * @param {SGAppsServer} server */ function NodeJsMvcController(controllerName, options, server ) { // options._onAction( actionName, controllerObject ); // options._noAction( actionName, controllerObject ); /** * @private * @type {SGAppsServer.NodeJsMvc.Controller} */ var controllerObject = { /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @name _actions * @type {Object<string,SGAppsServer.NodeJsMvc.Controller.Action>} */ _actions: {}, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @name _views * @type {Object<string,SGAppsServer.NodeJsMvc.Controller.View>} */ _views: {}, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @name viewer * @type {TemplateManagerViewer} */ //@ts-ignore viewer: server.TemplateManager._viewer, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @name name * @type {string} */ name: controllerName, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method getView * @param {string} viewName * @returns {SGAppsServer.NodeJsMvc.Controller.View} */ getView : function (viewName) { return ( (viewName in controllerObject._views) ? controllerObject._views[viewName] : null ); }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method viewExists * @param {string} viewName * @returns {boolean} */ viewExists : function (viewName) { return (viewName in controllerObject._views); }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method addView * @param {SGAppsServer.NodeJsMvc.Controller.View} view * @returns {SGAppsServer.NodeJsMvc.Controller.View} */ addView : function (view) { if(!(view.name in controllerObject._views)) { controllerObject._views[view.name] = view; return controllerObject._views[view.name]; } return null; }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method render * @param {SGAppsServerResponse} response * @param {string} viewName * @param {object} [options] */ render : function (response, viewName, options) { if(viewName in controllerObject._views) { var err; try { controllerObject.viewer.render( response, controllerObject.getView(viewName), options ); } catch (err) { console.error(err); } } else { response.sendError(Error('[SGAppsServer.Response] template not found')); } }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method removeView * @param {string} viewName * @returns {boolean} */ removeView : function (viewName) { if (viewName in controllerObject._views) { delete controllerObject._views[viewName]; return true; } return false; }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method getAction * @param {string} actionName * @returns {SGAppsServer.NodeJsMvc.Controller.Action} */ getAction : function( actionName ) { if (actionName in controllerObject._actions) { return controllerObject._actions[actionName]; } return null; }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method actionExists * @param {string} actionName * @returns {boolean} */ actionExists : function (actionName) { return (actionName in controllerObject._actions); }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method addAction * @param {string} actionName * @param {object} options * @returns {boolean} */ addAction : function( actionName, options ) { if (!(actionName in controllerObject._actions)) { //@ts-ignore controllerObject._actions[actionName] = new NodeJsMvcAction( actionName, controllerObject, options, server ); //@ts-ignore return controllerObject._actions[actionName]; } return null; }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @method removeAction * @param {string} actionName * @returns {boolean} */ removeAction : function( actionName ) { if( controllerObject.actionExists( actionName ) ) { delete controllerObject._actions[actionName]; return true; } return false; }, /** * @memberof SGAppsServer.NodeJsMvc.Controller# * @name shared * @type {Object<string,any>} */ shared: options.shared || {} }; return controllerObject; }; /** * @private * @function * @param {SGAppsServerRequest} request * @param {SGAppsServerResponse} response * @param {SGAppsServer} server * @param {function (Error, Object<string,SGAppsServer.NodeJsMvc.Controller>):void} [callback] */ function loadNodeJsMvcApp(request, response, server, callback) { /** * @private * @type {Object<string,SGAppsServer.NodeJsMvc.Controller>} */ const controllers = {}; const _path = server._path; /** * @private * @type {FSLibrary} */ const _fs = server._fs; const _appPath = server.NodeJsMvc.appPath; _fs.readdir(_appPath, "utf8", function (err, files) { if (err) { callback(err, controllers); return; } const _tick = () => { if (!files.length) { callback(null, controllers); return; } const controllerName = files.shift(); let configFile = _path.resolve( _appPath, controllerName, 'config.js' ); _fs.exists(configFile, (status) => { if (!status) { _tick(); return; } /** * @private * @type {SGAppsServer.NodeJsMvc.Controller} */ let controller; //@ts-ignore controller = new NodeJsMvcController( controllerName, require(configFile), server ); //@ts-ignore controllers[controllerName] = controller; server.logger.info(`[NodeJsMvc.Controller] added ${controllerName}`); _fs.readdir( _path.join( _appPath, controllerName, 'controller' ), 'utf8', (err, actions) => { if (err) { server.logger.error(err); _tick(); return; } const _tickAction = () => { if (!actions.length) { _tick(); return; } const actionFileName = actions.shift(); if (!actionFileName.match(/\.js$/)) { _tickAction(); return; } const actionName = actionFileName.replace(/\.js$/, ''); /** * @private * @type {SGAppsServer.NodeJsMvc.Controller.Action} */ let action; _fs.stat( _path.join( _appPath, controllerName, 'controller', actionFileName ), (err, stats) => { if (err) { server.logger.error(err); _tickAction(); return; } if (!stats.isFile()) { _tickAction(); return; } //@ts-ignore action = controller.addAction( actionName, require( _path.join( _appPath, controllerName, 'controller', actionFileName ) ) ); server.logger.info(` [NodeJsMvc.Action] added ${controllerName}:${actionName}`); _fs.stat( _path.join( _appPath, controllerName, 'views' ), (err, stats) => { if (err) { server.logger.error(err); _tickAction(); return; } if (!stats.isDirectory()) { _tickAction(); return; } _fs.readdir( _path.join( _appPath, controllerName, 'views' ), 'utf8', (err, views) => { if (err) { server.logger.error(err); _tickAction(); return; } const _tickView = () => { if (!views.length) { _tickAction(); return; } const viewFileName = views.shift(); if (!viewFileName.match(/\.(fbx\-tpl|tpl|html|htm|txt)$/)) { _tickView(); return; } const viewName = viewFileName.replace(/\.[^\.]+$/, ''); _fs.stat( _path.join( _appPath, controllerName, 'views' ), (err, stats) => { if (err) { server.logger.error(err); _tickView(); return; } if (!stats.isDirectory()) { _tickView(); return; } _fs.readFile( _path.join( _appPath, controllerName, 'views', viewFileName ), 'utf8', (err, code) => { if (err) { server.logger.error(err); _tickView(); return; } controller.addView({ path: _path.join( _appPath, controllerName, 'views', viewFileName ), name: viewName, code: ( server.logger._debug ? null : code ) }); _tickView(); } ); } ); }; _tickView(); } ); } ); } ); }; _tickAction(); } ); }); }; _tick(); }); } /** * this decorator is not enabled by default * @memberof SGAppsServerDecoratorsLibrary * @method NodeJsMvcDecorator * @param {SGAppsServerRequest} request * @param {SGAppsServerResponse} response * @param {SGAppsServer} server * @param {function} callback */ function NodeJsMvcDecorator(request, response, server, callback) { if ( request === null && response === null && server ) { /** * @memberof SGAppsServer * @class * @name NodeJsMvc */; /** * @memberof SGAppsServer.NodeJsMvc# * @name appPath * @type {string} */ let _appPath = null; /** * @memberof SGAppsServer# * @name NodeJsMvc * @type {SGAppsServer.NodeJsMvc} */ //@ts-ignore server.NodeJsMvc = { controllers: {} }; /** * @memberof SGAppsServer.NodeJsMvc# * @name controllers * @type {Object<string,SGAppsServer.NodeJsMvc.Controller>} */; let _ready, _reject; const _whenReady = new Promise((resolve, reject) => { _ready = resolve; _reject = reject; }); Object.defineProperty( server.NodeJsMvc, 'appPath', { get: () => _appPath, set: (v) => { if (typeof(v) === "string") { if (_appPath !== null) { server.logger.warn('[Server.NodeJsMvcDecorator] unable to set _appPath twice'); return; } _appPath = v; loadNodeJsMvcApp( request, response, server, function (err, controllers) { if (err) { server.logger.error(err); _reject(err); return; } server.NodeJsMvc.controllers = controllers; _ready(server.NodeJsMvc.controllers); } ); } } } ); /** * @memberof SGAppsServer.NodeJsMvc# * @name whenReady * @type {Promise<Object<string,SGAppsServer.NodeJsMvc.Controller>>} */; Object.defineProperty( server.NodeJsMvc, 'whenReady', { get: () => _whenReady, set: (v) => { server.logger.warn('[SGAppsServer.NodeJsMvc.whenReady] is readonly'); } } ); server.NodeJsMvc.whenReady.then((controllers) => { Object.values( controllers ).forEach((controller) => { Object.values(controller._actions).forEach((action) => { const handler = function (request, response, next) { request.params.shift(); request.params.shift(); request.params.shift(); action.run( request, response ); }; handler.toString = () => `NodeJSMvcAction() => { /** * @controller ${controller.name} * @action ${action.name} * @file ${server.NodeJsMvc.appPath}/${controller.name}/controllers/${action.name}.js */ // code is protected }`; const applyPath = (path) => { server.get(path, handler); if (action.postData) { server.post(path, server.handlePostData(), handler); } else { server.post(path, handler); } }; if (controller.name === 'index' && action.name === 'index') { applyPath('/'); } if (action.name === 'index') { applyPath(`^/${controller.name}(/|$)`); } applyPath(`^/${controller.name}/${action.name}(|/.*)`); }); }); }, server.logger.error); } callback(); }; module.exports = NodeJsMvcDecorator;