UNPKG

9.47 kBJavaScriptView Raw
1// Modules Initializer and Loader
2'use strict';
3
4const cluster = require('cluster');
5const config = require('./config')();
6const winston = require('winston');
7const chalk = require('chalk');
8const fs = require('fs');
9const path = require('path');
10const express = require('express');
11const access = require('mio-roles');
12const extensionParser = require('mio-extensions');
13const services = require('mio-services');
14const wait = require('wait.for');
15
16// List of modules
17global.mio.modules = [];
18
19// Print Once
20function _printOnce(){
21 return (cluster.isMaster || (cluster.worker && cluster.worker.id==1));
22}
23
24// Load Module META
25function loadMETA(moduleName, logName){
26 var mod = {
27 name: moduleName
28 };
29
30 mod.path = path.join(global.mio.appPath, 'modules', moduleName);
31
32 // Load configs
33 mod.config = require('./config')(moduleName).get(moduleName);
34
35 // Set Module Version
36 if(mod.config && mod.config.version){
37 mod.version = mod.config.version;
38 }
39
40 // Load localizations
41 var i18nOptions = {
42 path: path.join(mod.path, 'i18n'),
43 prefix: mod.name
44 };
45 if(mod.config && mod.config.i18n && mod.config.i18n.defaultLocale){
46 i18nOptions.defaultLocale = mod.config.i18n.defaultLocale;
47 }
48 mod._i18n = require('mio-i18n')(i18nOptions);
49 mod.i18n = function(key, defaultValue, vars){
50 return mod._i18n.get(key, defaultValue, vars, i18nOptions.defaultLocale || 'en', mod.name);
51 };
52
53 // Extension parser/Render
54 mod.render = extensionParser.render;
55
56 // Create logger
57 var _transports = [];
58 if(process.env.NODE_ENV!='test'){
59 _transports.push(new (winston.transports.Console)());
60 }
61
62 var _logPath = path.join(mod.path, 'log');
63 logName = logName || mod.name;
64 if(fs.existsSync(_logPath)){
65 _transports.push(new (winston.transports.File)({ filename: path.join(_logPath, logName+'.log') }));
66 }
67
68 var _logOptions = { transports: _transports };
69
70 if(mod.config && mod.config.log && mod.config.log.level){
71 _logOptions.level = mod.config.log.level;
72 }
73
74 mod.log = new (winston.Logger)(_logOptions);
75
76 return mod;
77}
78
79// Load module as API component and load as lib if needed
80function load(moduleName){
81 var mod = loadMETA(moduleName);
82
83 // Load service connector or service in internal mode
84 if(mod.config && mod.config.mode){
85 mod.mode = mod.config.mode;
86 }else{
87 mod.mode = 'internal';
88 }
89
90 // Make default empty template
91 mod.rpc = (cmd, opts, cb)=>{
92 var msg = cmd+'() not implemented';
93 mod.log.error(msg);
94 if(cb){
95 cb(msg);
96 }
97 };
98
99 // Create module client for internal mode
100 if(mod.mode == 'internal'){
101 try {
102 mod._rpc = require(path.join(mod.path, 'service', 'rpc.js'))(mod);
103
104 for (var method in mod._rpc) {
105 mod[method] = mod._rpc[method];
106 }
107 } catch (e) {
108 mod.noRPC = true;
109 }
110 }
111
112 // Create module client for external mode
113 if(mod.mode == 'external'){
114 // Get MQ server from global config
115 var mqServerConfig = {
116 server: 'localhost'
117 };
118 if(config.get().mq){
119 mqServerConfig = config.get().mq;
120 }
121
122 var _useCache = false;
123 if(config.get(moduleName).RPC && config.get(moduleName).RPC.useCache){
124 _useCache = config.get(moduleName).RPC.useCache;
125 }
126
127 // Crete client
128 wait.launchFiber(()=>{
129 try {
130 mod._cli = wait.for(services.client, {server: mqServerConfig.server, expires: 15000, useCache: _useCache});
131 } catch (e) {
132 mod.log.error(e);
133 }
134
135 mod.rpc = function(cmd, opts, cb){
136 mod._cli.call(mod.name, cmd, opts, cb);
137 };
138
139 // Create methods for RPC calls
140 if(fs.existsSync(path.join(mod.path, 'service', 'rpc.js'))){
141 var _file = fs.readFileSync(path.join(mod.path, 'service', 'rpc.js') , {encoding: 'utf8'});
142 var _exp = _file.match(/module.exports[\s\S]*return\s*\{[\s\S]*(?=\})/m);
143 if(_exp && _exp[0]){
144 _exp = _exp[0];
145 _exp = _exp.match(/return\s?\{[\s\S]*(?=\})/m);
146 if(_exp && _exp[0]){
147 _exp = _exp[0];
148 _exp = _exp.replace('{','').replace('return','').replace(/\s\n?/m,'');
149 var _cmd = _exp.split(',');
150 if(_cmd && _cmd.length){
151 _cmd = _cmd.map((item)=>{
152 return item.split(':')[1].trim();
153 });
154 _cmd.map((_tmpCmd)=>{
155 mod[_tmpCmd] = function(opts, cb){
156 mod._cli.call(mod.name, _tmpCmd, opts, cb);
157 };
158 });
159 }
160 }
161 }
162 }
163
164 });
165 }
166
167 // Load API controller
168 var _controllerPath = path.join(mod.path, 'api', 'controller.js');
169 if(fs.existsSync(_controllerPath)){
170 mod.controller = require(_controllerPath)(mod);
171 }
172
173 // Load Policy controlled
174 var _policyPath = path.join(mod.path, 'api', 'policy.js');
175 if(mod.controller && fs.existsSync(_policyPath)){
176 mod.policy = require(_policyPath);
177 }
178
179 // Create routes based on configs
180 var _routesPath = path.join(mod.path, 'api', 'routes.json');
181 if(mod.controller && fs.existsSync(_routesPath)){
182 var _routesConfig = require(_routesPath);
183 var _router = express.Router();
184
185 for (var _method in _routesConfig) {
186 if (_routesConfig.hasOwnProperty(_method)) {
187 if(mod.controller && mod.controller[_method]){
188 var versionPrefix = '';
189 if(mod.version){
190 versionPrefix = '/v'+mod.version;
191 }
192
193 var _param = _routesConfig[_method].param;
194 if(_param){
195 _router['param'](_param, mod.controller[_method]);
196 }else{
197 var _routePath = _routesConfig[_method].route;
198 var _roles = _routesConfig[_method].roles;
199 for (var i = 0; i < _routesConfig[_method].methods.length; i++) {
200 var _httpMethod = _routesConfig[_method].methods[i];
201
202 if(!Array.isArray(_routePath)){
203 _routePath = [_routePath];
204 }
205
206 _routePath = _routePath.map((item)=>{
207 return versionPrefix+'/'+mod.name+item+'.:ext?';
208 });
209
210 for (var j = 0; j < _routePath.length; j++) {
211 if(_roles){
212 _router['route'](_routePath[j]).all([mod.policy, access(_roles)])[_httpMethod](mod.controller[_method]);
213 }else{
214 _router['route'](_routePath[j])[_httpMethod](mod.controller[_method]);
215 }
216 }
217 }
218 }
219 }
220 }
221 }
222
223 mod.routes = _router;
224 }
225
226 global.mio.modules[mod.name] = mod;
227
228 var _rpcMode = mod.noRPC?'':'with RPC';
229 if(_printOnce()){
230 winston.info(chalk.green(mod.name),'started in',chalk.blue(mod.mode),'mode',_rpcMode);
231 }
232}
233
234// List available modules
235function listModules(){
236 var _modules = fs.readdirSync(path.join(global.mio.appPath, 'modules'));
237 if(_modules && _modules.length){
238 _modules = _modules.filter((modName)=>{
239 return modName.substring(0, 1) != '.';
240 });
241 }
242 return _modules;
243}
244
245// List available external modules
246function listExternalModules(){
247 var _modules = listModules();
248 var _external = [];
249
250 for (var i = 0; i < _modules.length; i++) {
251 var cfg = require('./config')(_modules[i]).get(_modules[i]);
252 if(cfg && cfg.mode && cfg.mode == 'external'){
253 _external.push(_modules[i]);
254 }
255 }
256
257 return _external;
258}
259
260// Load all modules in server and project dirs
261function loadModules(){
262 var _modules = listModules();
263
264 if(_printOnce()){
265 winston.info('Detected',chalk.blue(_modules.length),'modules:',chalk.blue(_modules));
266 }
267
268 for (var i = 0; i < _modules.length; i++) {
269 load(_modules[i]);
270 }
271}
272
273// Load and start module as independent microservice
274function start(moduleName, options){
275 // Load module meta
276 var mod = loadMETA(moduleName, 'service');
277
278 // Intro message
279 winston.info('='.repeat(70));
280 winston.info('Just', chalk.green('Make It Once'),'|',chalk.blue('MakeItOnce.net'));
281 winston.info('-'.repeat(70));
282 winston.info('Starting',moduleName,'service...');
283
284 // Init cassandra
285 var bigDataConf = config.get(moduleName).bigdata;
286 if(bigDataConf && bigDataConf.use){
287 mod.cassandra = require('./cassandra').init(bigDataConf);
288 }
289
290 // Init MongoDB
291 if(options && options.mongoose){
292 mod.mongoose = options.mongoose;
293 var dbConf = config.get(moduleName).db;
294 if(dbConf && (dbConf.use || (dbConf.server && dbConf.name))){
295 require('./mongoose').init(options.mongoose, dbConf);
296 }
297 }
298
299 // Get MQ server from global config
300 var mqServerConfig = {
301 server: 'localhost'
302 };
303 if(config.get().mq){
304 mqServerConfig = config.get().mq;
305 }
306
307 // Load module
308 wait.launchFiber(()=>{
309 try {
310 mod._srv = wait.for(services.service, {name: mod.name, server: mqServerConfig.server});
311 } catch (e) {
312 mod.log.error(e);
313 process.exit(1);
314 }
315
316 try {
317 mod._rpc = require(path.join(mod.path, 'service', 'rpc.js'))(mod);
318 } catch (e) {
319 mod.log.error('Module doesn\'t have RPC or it\'s invalid');
320 mod.log.error(e);
321 process.exit(1);
322 }
323
324 for (var method in mod._rpc) {
325 if (mod._rpc.hasOwnProperty(method)) {
326 mod._srv.listen(method, mod._rpc[method]);
327 }
328 }
329 });
330
331 // Make module accessable at object
332 global.mio.modules[mod.name] = mod;
333}
334
335// Exports
336module.exports = {
337 modules: global.mio.modules,
338 loadModules: loadModules,
339 listModules: listModules,
340 listExternalModules: listExternalModules,
341 load: load,
342 start: start
343};