1 |
|
2 | const path = require('path');
|
3 | const glob = require('glob');
|
4 | const favicon = require('serve-favicon');
|
5 | const _ = require("lodash");
|
6 | const debug = require('debug')('motif:process');
|
7 |
|
8 | const Macro = require('node-macro');
|
9 |
|
10 | const http = require( 'http' );
|
11 | const express = require('express');
|
12 |
|
13 | const session = require('express-session');
|
14 | const cookieParser = require('cookie-parser');
|
15 | const bodyParser = require('body-parser');
|
16 | const helmet = require('helmet');
|
17 | const cors = require('cors');
|
18 | const formidable = require('formidable');
|
19 |
|
20 | const Context = require('./Context');
|
21 | const Swagger = require('./Swagger');
|
22 |
|
23 | class Process {
|
24 |
|
25 | get app() {
|
26 | return this._app;
|
27 | }
|
28 |
|
29 | get context() {
|
30 | return this._context;
|
31 | }
|
32 |
|
33 | _loadService( file ) {
|
34 | debug("_loadService", file);
|
35 | delete require.cache[require.resolve(path.resolve(this._context.appPath, file))];
|
36 | return require(path.resolve(this._context.appPath, file));
|
37 | }
|
38 |
|
39 | _loadServices() {
|
40 |
|
41 | debug("_loadServices");
|
42 |
|
43 | const macro = new Macro();
|
44 |
|
45 | let serviceFiles = [];
|
46 |
|
47 | macro.add("CREATE-APP", (action) => {
|
48 |
|
49 | let app = express();
|
50 | app.use(helmet());
|
51 | app.use(bodyParser.urlencoded({ extended: false }));
|
52 | app.use(bodyParser.json());
|
53 | app.use(cors());
|
54 |
|
55 | if (this._context.useCookie === true) {
|
56 | app.use(cookieParser());
|
57 | }
|
58 |
|
59 | if (this._context.useFavicon === true) {
|
60 | app.use(favicon(path.join(this._context.appPath, 'public', 'favicon.ico')));
|
61 | }
|
62 |
|
63 | if (this._context.useProxy === true) {
|
64 | app.set('trust proxy', true);
|
65 | }
|
66 |
|
67 | app.on('uncaughtException', this.onException);
|
68 |
|
69 |
|
70 | app.use(function (req, res, next) {
|
71 | if (req.headers['content-type'] && req.headers['content-type'].indexOf('multipart/form-data') > -1) {
|
72 |
|
73 | const form = new formidable.IncomingForm({
|
74 | encoding: 'utf-8',
|
75 | multiples: true,
|
76 | type: 'multipart',
|
77 | maxFieldsSize: 20 * 1024 * 1024,
|
78 | maxFileSize: 200 * 1024 * 1024,
|
79 | maxFields: 1000,
|
80 | hash: true,
|
81 | keepExtensions: false
|
82 | });
|
83 | form.parse(req, function (err, fields) {
|
84 | _.merge(req.body, fields);
|
85 | req.openedFiles = [];
|
86 | });
|
87 | form.on('file', function (name, file) {
|
88 | if (!req.files) {
|
89 | req.files = {};
|
90 | }
|
91 | req.files[name] = file;
|
92 | req.body[name] = file;
|
93 | });
|
94 | form.on('end', function () {
|
95 | req.openedFiles = this.openedFiles;
|
96 | next();
|
97 | });
|
98 | } else {
|
99 | next();
|
100 | }
|
101 | });
|
102 |
|
103 | this._app = app;
|
104 | action.complete();
|
105 | });
|
106 |
|
107 | macro.add("LOAD-SERVICE-FILES", (action) => {
|
108 | glob("services/*/*Service.js", { cwd: this._context.appPath }, (err, files) => {
|
109 | if (err) {
|
110 | action.error(err);
|
111 | return;
|
112 | }
|
113 |
|
114 | serviceFiles = files;
|
115 | action.complete();
|
116 | });
|
117 | });
|
118 |
|
119 | macro.add("LOAD-SERVICES", (action) => {
|
120 | _.each(serviceFiles, (file) => {
|
121 | this._services.push(
|
122 | this._loadService( file )
|
123 | );
|
124 | });
|
125 |
|
126 | action.complete();
|
127 | });
|
128 |
|
129 | if (this._context.createDoc === true) {
|
130 |
|
131 | macro.add("CRAETE-SWAGGER-DOCS", async (action) => {
|
132 |
|
133 | try {
|
134 | let app = this._app;
|
135 | let swagger = new Swagger( this._context );
|
136 |
|
137 | let json = swagger.buildJSON( this._routerPaths, false );
|
138 | let swaggerPath = path.join(this._context.appPath, "public/swagger");
|
139 |
|
140 | await swagger.jsonWriteAsync( path.join(swaggerPath, "/data/swag.json"), json );
|
141 |
|
142 | app.use(express.static(path.join(this._context.appPath, 'public', 'swagger')));
|
143 |
|
144 | app.get('/doc/data', (req, res) => {
|
145 | res.sendFile('data/swag.json', {root: swaggerPath });
|
146 | });
|
147 |
|
148 | app.get('/doc', (req, res) => {
|
149 | res.sendFile('index.html', {root: swaggerPath });
|
150 | });
|
151 | }
|
152 | catch(e) {
|
153 | console.log( e );
|
154 | }
|
155 |
|
156 | action.complete();
|
157 | });
|
158 |
|
159 | macro.add("CRAETE-SWAGGER-ADMIN-DOCS", async (action) => {
|
160 |
|
161 | try {
|
162 | let app = this._app;
|
163 | let swagger = new Swagger( this._context );
|
164 |
|
165 | let json = swagger.buildJSON( this._routerPaths, true );
|
166 | let swaggerPath = path.join(this._context.appPath, "public/swagger");
|
167 |
|
168 | await swagger.jsonWriteAsync( path.join(swaggerPath, "/data/swag.admin.json"), json );
|
169 |
|
170 | app.use(express.static(path.join(this._context.appPath, 'public', 'swagger')));
|
171 |
|
172 | app.get('/doc/data-admin', (req, res) => {
|
173 | res.sendFile('data/swag.admin.json', {root: swaggerPath });
|
174 | });
|
175 |
|
176 | app.get('/doc-admin', (req, res) => {
|
177 | res.sendFile('index.admin.html', {root: swaggerPath });
|
178 | });
|
179 | }
|
180 | catch(e) {
|
181 | console.log( e );
|
182 | }
|
183 |
|
184 | action.complete();
|
185 | });
|
186 | }
|
187 |
|
188 | macro.add("RUN-SERVER", (action) => {
|
189 |
|
190 | let server = http.createServer( this._app );
|
191 | server.listen( this.context.port, () => {
|
192 | debug( `Server is running on port ${this.context.port}` );
|
193 | });
|
194 | server.on('error', this.onError);
|
195 | server.on('listening', this.onListening);
|
196 |
|
197 | this._server = server;
|
198 |
|
199 | action.complete();
|
200 | });
|
201 |
|
202 | macro.start((error) => {
|
203 | if (error) {
|
204 | debug(error);
|
205 | }
|
206 | });
|
207 | }
|
208 |
|
209 | _loadViews() {
|
210 |
|
211 | const macro = new Macro();
|
212 |
|
213 | let serviceFiles = [];
|
214 |
|
215 | macro.add("CREATE-APP", (action) => {
|
216 |
|
217 | let app = express();
|
218 |
|
219 | app.engine('.html', require('ejs').__express);
|
220 |
|
221 | app.set('views', path.join(this._context.appPath, 'views'));
|
222 |
|
223 |
|
224 | app.set('view engine', 'html');
|
225 |
|
226 | app.use(express.static(path.join(this._context.appPath, 'public', 'static')));
|
227 |
|
228 | app.use(bodyParser.urlencoded({ extended: false }));
|
229 |
|
230 |
|
231 | if (this._context.session || this._context.useCookie === true) {
|
232 | app.use(cookieParser());
|
233 | }
|
234 |
|
235 | if (this._context.session) {
|
236 | app.use(this._context.session);
|
237 | }
|
238 |
|
239 | app.on('uncaughtException', this.onException);
|
240 |
|
241 | this._app = app;
|
242 |
|
243 | action.complete();
|
244 | });
|
245 |
|
246 | macro.add("LOAD-SERVICE-FILES", (action) => {
|
247 | glob("services/*/*Service.js", { cwd: this._context.appPath }, (err, files) => {
|
248 | if (err) {
|
249 | action.error(err);
|
250 | return;
|
251 | }
|
252 |
|
253 | serviceFiles = files;
|
254 | action.complete();
|
255 | });
|
256 | });
|
257 |
|
258 | macro.add("LOAD-SERVICES", (action) => {
|
259 |
|
260 | _.each(serviceFiles, (file) => {
|
261 | this._services.push(
|
262 | this._loadService( file )
|
263 | );
|
264 | });
|
265 |
|
266 | action.complete();
|
267 | });
|
268 |
|
269 | macro.add("DEFAULT_ROUTERS", (action) => {
|
270 |
|
271 | let app = this._app;
|
272 | let defaultRouter = express.Router({strict: true});
|
273 | let routersPaths = process.motif._routerPaths.map((routePath) => { return routePath.path });
|
274 |
|
275 | glob("**/*.html", { cwd: path.join(this._context.appPath, "views") }, (err, files) => {
|
276 | _.each(files, (file) => {
|
277 | let viewFile = path.join(this._context.appPath, file);
|
278 | let fileName = path.basename(viewFile);
|
279 | let dirName = path.dirname(file).split(path.sep).pop();
|
280 |
|
281 | if (dirName.indexOf("_") === -1 || dirName.indexOf("_") > 0) {
|
282 | if (fileName.indexOf(".html") !== -1) {
|
283 | let viewName = file.substr(0, file.length - 5);
|
284 |
|
285 | if (dirName !== ".") {
|
286 | let path = "/" + viewName.replace("/index", "/");
|
287 |
|
288 | if (!routersPaths.includes(path)) {
|
289 | debug( "auto router", path, viewName );
|
290 |
|
291 | defaultRouter.get(path, (request, response) => {
|
292 |
|
293 | if (process.motif.main) {
|
294 | process.motif.main.htmlResponse( request, response, viewName, {});
|
295 | }
|
296 | else {
|
297 | response.render(viewName, {});
|
298 | }
|
299 | });
|
300 |
|
301 | routersPaths.push(path);
|
302 | }
|
303 | }
|
304 | }
|
305 | }
|
306 | });
|
307 | });
|
308 |
|
309 | if (process.motif.main) {
|
310 | process.motif.main._initialize( { app, router: defaultRouter } );
|
311 | }
|
312 |
|
313 | app.use("/", defaultRouter);
|
314 |
|
315 | app.use((request, response, next) => {
|
316 |
|
317 | debug( "404 error", request.path );
|
318 |
|
319 | response.status(404);
|
320 |
|
321 | response.render("404", { request });
|
322 | });
|
323 |
|
324 | app.use((error, request, response, next) => {
|
325 |
|
326 | debug( "500 error", error.message );
|
327 |
|
328 | response.status(500);
|
329 |
|
330 | response.render("50x", { error, request, response });
|
331 | });
|
332 |
|
333 | action.complete();
|
334 | });
|
335 |
|
336 | macro.add("RUN-SERVER", (action) => {
|
337 |
|
338 | let server = http.createServer( this._app );
|
339 | server.listen( this.context.port, () => {
|
340 | debug( `Server is running on port ${this.context.port}` );
|
341 | });
|
342 | server.on('error', this.onError);
|
343 | server.on('listening', this.onListening);
|
344 |
|
345 | this._server = server;
|
346 |
|
347 | action.complete();
|
348 | });
|
349 |
|
350 | macro.start((error) => {
|
351 | if (error) {
|
352 | debug(error);
|
353 | }
|
354 | });
|
355 | }
|
356 |
|
357 | _attachExceptionHandlers() {
|
358 | process.on('uncaughtException', async function (err) {
|
359 | console.error(err.stack);
|
360 | process.exit(1);
|
361 | });
|
362 |
|
363 | process.on('unhandledRejection', async function (err) {
|
364 | console.error(err.stack);
|
365 | process.exit(1);
|
366 | });
|
367 | }
|
368 |
|
369 | constructor( options ) {
|
370 |
|
371 | this._context = new Context({
|
372 | appPath: path.resolve(process.env.PWD, '.'),
|
373 | ...options
|
374 | });
|
375 |
|
376 | this._app = null;
|
377 | this._server = null;
|
378 | this._services = [];
|
379 | this._views = [];
|
380 | this._routerPaths = [];
|
381 |
|
382 | process.motif = this;
|
383 | }
|
384 |
|
385 | run( serviceType = "api" ) {
|
386 |
|
387 | this._attachExceptionHandlers();
|
388 |
|
389 | if (serviceType === "web") {
|
390 | this._loadViews();
|
391 | }
|
392 | else {
|
393 | this._loadServices();
|
394 | }
|
395 | }
|
396 |
|
397 | onListening() {
|
398 | debug( "onListening" );
|
399 | }
|
400 |
|
401 | onException(req, res, route, err) {
|
402 | debug( "onException", err );
|
403 | }
|
404 |
|
405 | onError(error) {
|
406 | debug( "onError", error );
|
407 | }
|
408 |
|
409 | };
|
410 |
|
411 | module.exports = Process;
|