1 |
|
2 | 'use strict';
|
3 |
|
4 | const cluster = require('cluster');
|
5 | const config = require('./config')();
|
6 | const winston = require('winston');
|
7 | const chalk = require('chalk');
|
8 | const fs = require('fs');
|
9 | const path = require('path');
|
10 | const express = require('express');
|
11 | const access = require('mio-roles');
|
12 | const extensionParser = require('mio-extensions');
|
13 | const services = require('mio-services');
|
14 | const wait = require('wait.for');
|
15 |
|
16 |
|
17 | global.mio.modules = [];
|
18 |
|
19 |
|
20 | function _printOnce(){
|
21 | return (cluster.isMaster || (cluster.worker && cluster.worker.id==1));
|
22 | }
|
23 |
|
24 |
|
25 | function loadMETA(moduleName, logName){
|
26 | var mod = {
|
27 | name: moduleName
|
28 | };
|
29 |
|
30 | mod.path = path.join(global.mio.appPath, 'modules', moduleName);
|
31 |
|
32 |
|
33 | mod.config = require('./config')(moduleName).get(moduleName);
|
34 |
|
35 |
|
36 | if(mod.config && mod.config.version){
|
37 | mod.version = mod.config.version;
|
38 | }
|
39 |
|
40 |
|
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 |
|
54 | mod.render = extensionParser.render;
|
55 |
|
56 |
|
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 |
|
80 | function load(moduleName){
|
81 | var mod = loadMETA(moduleName);
|
82 |
|
83 |
|
84 | if(mod.config && mod.config.mode){
|
85 | mod.mode = mod.config.mode;
|
86 | }else{
|
87 | mod.mode = 'internal';
|
88 | }
|
89 |
|
90 |
|
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 |
|
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 |
|
113 | if(mod.mode == 'external'){
|
114 |
|
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 |
|
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 |
|
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 |
|
168 | var _controllerPath = path.join(mod.path, 'api', 'controller.js');
|
169 | if(fs.existsSync(_controllerPath)){
|
170 | mod.controller = require(_controllerPath)(mod);
|
171 | }
|
172 |
|
173 |
|
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 |
|
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 |
|
235 | function 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 |
|
246 | function 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 |
|
261 | function 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 |
|
274 | function start(moduleName, options){
|
275 |
|
276 | var mod = loadMETA(moduleName, 'service');
|
277 |
|
278 |
|
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 |
|
285 | var bigDataConf = config.get(moduleName).bigdata;
|
286 | if(bigDataConf && bigDataConf.use){
|
287 | mod.cassandra = require('./cassandra').init(bigDataConf);
|
288 | }
|
289 |
|
290 |
|
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 |
|
300 | var mqServerConfig = {
|
301 | server: 'localhost'
|
302 | };
|
303 | if(config.get().mq){
|
304 | mqServerConfig = config.get().mq;
|
305 | }
|
306 |
|
307 |
|
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 |
|
332 | global.mio.modules[mod.name] = mod;
|
333 | }
|
334 |
|
335 |
|
336 | module.exports = {
|
337 | modules: global.mio.modules,
|
338 | loadModules: loadModules,
|
339 | listModules: listModules,
|
340 | listExternalModules: listExternalModules,
|
341 | load: load,
|
342 | start: start
|
343 | };
|