@vulkano/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,619 @@
1
+ /**
2
+ * Server.js
3
+ */
4
+
5
+ const express = require('express');
6
+ const frameguard = require('frameguard');
7
+ const { Server } = require('socket.io');
8
+ const nunjucks = require('nunjucks');
9
+ const morgan = require('morgan');
10
+ const compression = require('compression');
11
+ const multer = require('multer');
12
+ const helmet = require('helmet');
13
+ const timeout = require('connect-timeout');
14
+ const useragent = require('express-useragent');
15
+ const webp = require('webp-middleware');
16
+ const cookieParser = require('cookie-parser');
17
+ const merge = require('deepmerge');
18
+ const { createClient } = require('redis');
19
+ const { createAdapter } = require('@socket.io/redis-adapter');
20
+
21
+ // Include all api controllers
22
+ const AllControllers = require('include-all')({
23
+ dirname: `${APP_PATH}/controllers`,
24
+ filter: /(.+Controller)\.js$/,
25
+ optional: true
26
+ });
27
+ const responses = require('./responses');
28
+ const JWT = require(`${CORE_PATH}/libs/Jwt`);
29
+
30
+ module.exports = {
31
+
32
+ routes: {},
33
+
34
+ start: function loadServerApplication(cb) {
35
+
36
+ const jwtMiddleware = JWT;
37
+
38
+ // Common config by filename
39
+ const {
40
+ cors,
41
+ jwt,
42
+ settings,
43
+ sockets,
44
+ cookies,
45
+ redis,
46
+ // Folder express config files
47
+ express: expressServerConfig
48
+ } = app.config || {};
49
+
50
+ // express config by file in settings.js
51
+ const {
52
+ port: expressUserPort,
53
+ express: expressConfigInSettings
54
+ } = settings || {};
55
+
56
+ // express config by file in app/config/express/settings.js
57
+ const {
58
+ settings: expressGeneralSettings
59
+ } = expressServerConfig || {};
60
+
61
+ // express port via file in app/config/express/settings.js
62
+ const {
63
+ port: expressSettingsPort
64
+ } = expressGeneralSettings || {};
65
+
66
+ // Express default configuration
67
+ const expressDefaultConfig = {
68
+ timeout: 120000, // 2 min
69
+ poweredBy: false,
70
+ port: process.env.PORT || expressUserPort || expressSettingsPort || 8000,
71
+ cors: {},
72
+ cookies: {},
73
+ jwt: {},
74
+ multer: {
75
+ dest: 'public/files'
76
+ },
77
+ morgan: {
78
+ format: 'dev',
79
+ skip: ((req, res) => res.statusCode < 400)
80
+ },
81
+ compression: {},
82
+ json: {},
83
+ urlencoded: {
84
+ extended: true
85
+ },
86
+ helmet: {
87
+ contentSecurityPolicy: false,
88
+ crossOriginEmbedderPolicy: false
89
+ },
90
+ frameguard: null
91
+ };
92
+
93
+ // Merge all express configuration: config/file.js, config/express/file.js, config/settings.js
94
+ const expressConfig = merge.all([
95
+ {
96
+ cookies,
97
+ jwt,
98
+ cors
99
+ },
100
+ expressDefaultConfig || {},
101
+ expressServerConfig || {},
102
+ expressGeneralSettings || {},
103
+ expressConfigInSettings || {},
104
+ ]);
105
+
106
+ if (expressConfig && expressConfig.settings) {
107
+ delete expressConfig.settings;
108
+ }
109
+
110
+ const views = app.server.views || {};
111
+
112
+ // Middleware
113
+ const middleware = app.config.middleware || ((req, res, next) => {
114
+ next();
115
+ });
116
+
117
+ const server = express();
118
+
119
+ // Settings
120
+ server.enable('trust proxy');
121
+
122
+ // ---------------
123
+ // PORT - File: app/config/express/settings.js
124
+ // ---------------
125
+ server.set('port', expressConfig.port);
126
+
127
+ // ---------------
128
+ // MULTER - File: app/config/express/multer.js
129
+ // ---------------
130
+ const upload = multer(expressConfig.multer);
131
+
132
+ // ---------------
133
+ // MORGAN - File: app/config/express/morgan.js
134
+ // ---------------
135
+ server.use(morgan(expressConfig.morgan.format, expressConfig.morgan));
136
+
137
+ // ---------------
138
+ // USER AGENT
139
+ // ---------------
140
+ server.use(useragent.express());
141
+
142
+ // ---------------
143
+ // COMPRESSION - File: app/config/express/compression.js
144
+ // ---------------
145
+ server.use(compression( expressConfig.compression || {} ));
146
+
147
+ // ---------------
148
+ // COOKIES - File: app/config/express/cookies.js
149
+ // ---------------
150
+ if (expressConfig.cookies && expressConfig.cookies.enabled) {
151
+ const cookiesSecretKey = expressConfig.cookies && expressConfig.cookies.secret ? expressConfig.cookies.secret : '';
152
+ if (!cookiesSecretKey) {
153
+ console.log('Warning: Please set cookie secret key in the file config/express/cookie.js');
154
+ }
155
+ server.use(cookieParser(cookiesSecretKey));
156
+ }
157
+
158
+ // ---------------
159
+ // EXPRESS JSON - File: app/config/express/json.js
160
+ // ---------------
161
+ server.use(express.json(expressConfig.json));
162
+
163
+ // ---------------
164
+ // EXPRESS FORM DATA - File: app/config/express/urlencoded.js
165
+ // ---------------
166
+ server.use(express.urlencoded(expressConfig.urlencoded));
167
+
168
+ // ---------------
169
+ // HELMET - File: app/config/express/helmet.js
170
+ // ---------------
171
+ server.use(helmet(expressConfig.helmet));
172
+
173
+ // ---------------
174
+ // RESPONSES
175
+ // ---------------
176
+ server.use(responses);
177
+
178
+ // ---------------
179
+ // WEBP MIDDLEWARE - File: app/config/express/webp.js
180
+ // ---------------
181
+ if (expressConfig.webp && expressConfig.webp.enabled) {
182
+ const cacheDir = expressConfig.webp.folder || `${PUBLIC_PATH}/webp-cache`;
183
+ const webpConfig = {
184
+ quality: expressConfig.webp.quality || 80,
185
+ preset: expressConfig.webp.preset || 'photo',
186
+ cacheDir: expressConfig.webp.cacheDir || cacheDir
187
+ };
188
+ server.use(webp(PUBLIC_PATH, webpConfig));
189
+ }
190
+
191
+ // ---------------
192
+ // PUBLIC PATH - File: app/config/settings.js
193
+ // ---------------
194
+ server.use(express.static(PUBLIC_PATH));
195
+
196
+ // ---------------
197
+ // FRAMEGUARD - File: app/config/express/frameguard.js
198
+ // ---------------
199
+ if (expressConfig.frameguard) {
200
+ if (Array.isArray(expressConfig.frameguard)) {
201
+ expressConfig.frameguard.forEach( (frame) => {
202
+ server.use(frameguard(frame));
203
+ });
204
+ } else {
205
+ server.use(frameguard(expressConfig.frameguard));
206
+ }
207
+ }
208
+
209
+ // ---------------
210
+ // PROTOCOL & POWERED BY - File: app/config/settings.js
211
+ // ---------------
212
+ server.use( (req, res, next) => {
213
+
214
+ const proto = req.secure ? 'https' : 'http';
215
+ const forwarded = req.headers['x-forwaded-proto'] || null;
216
+ const currentProtocol = (forwarded || proto).split('://')[0];
217
+ req.protocol = currentProtocol;
218
+
219
+ if (expressConfig.poweredBy) {
220
+ res.setHeader('X-Powered-By', expressConfig.poweredBy);
221
+ }
222
+
223
+ next();
224
+
225
+ });
226
+
227
+ // ---------------
228
+ // REQUEST OPTIONS - File: app/config/express/cors.js
229
+ // ---------------
230
+ server.options('*', (req, res) => {
231
+
232
+ // ---------------
233
+ // CORS
234
+ // ---------------
235
+ if (expressConfig.cors && expressConfig.cors.enabled) {
236
+ let tmpCustomHeaders = ['X-Requested-With', 'X-HTTP-Method-Override', 'Content-Type', 'Accept'];
237
+ tmpCustomHeaders = tmpCustomHeaders.concat(expressConfig.cors.headers || []);
238
+ res.header('Access-Control-Allow-Origin', expressConfig.cors.origin);
239
+ res.header('Access-Control-Allow-Headers', tmpCustomHeaders.join(', '));
240
+ } else {
241
+ res.header('Access-Control-Allow-Origin', '*');
242
+ res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
243
+ }
244
+ res.header('Allow', 'GET,PUT,POST,DELETE,OPTIONS');
245
+ res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
246
+
247
+ res.status(200).end();
248
+
249
+ });
250
+
251
+ // ---------------
252
+ // TIMEOUT - File: app/config/settings.js
253
+ // ---------------
254
+ server.use(timeout( expressConfig.timeout || 120000 ));
255
+ server.use( (req, res, next) => {
256
+ if (!req.timedout) {
257
+ next();
258
+ }
259
+ });
260
+
261
+ // ---------------
262
+ // JWT - File: app/config/express/jwt.js
263
+ // ---------------
264
+ if (expressConfig.jwt && expressConfig.jwt.enabled) {
265
+
266
+ // JWT (secret key)
267
+ server.use(expressConfig.jwt.path || '*', jwtMiddleware.init().unless({
268
+ path: expressConfig.jwt.ignore || []
269
+ }));
270
+
271
+ // JWT Handler error
272
+ server.use((err, req, res, next) => {
273
+ if (err && err.name === 'UnauthorizedError') {
274
+ res.status(401).jsonp({ success: false, error: 'Invalid token' });
275
+ } else {
276
+ next();
277
+ }
278
+ });
279
+
280
+ }
281
+
282
+ // ---------------
283
+ // CORS - File: app/config/express/cors.js
284
+ // ---------------
285
+ if (expressConfig.cors && expressConfig.cors.enabled) {
286
+
287
+ server.use(expressConfig.cors.path, (req, res, next) => {
288
+
289
+ // Enable CORS.
290
+ let tmpCorsHeaders = ['X-Requested-With', 'X-HTTP-Method-Override', 'Content-Type', 'Accept'];
291
+ tmpCorsHeaders = tmpCorsHeaders.concat(expressConfig.cors.headers || []);
292
+
293
+ res.header('Access-Control-Allow-Origin', expressConfig.cors.origin);
294
+ res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
295
+ res.header('Access-Control-Allow-Headers', tmpCorsHeaders.join(', '));
296
+
297
+ // Disable CACHE in API resources.
298
+ res.header('Cache-Control', 'no-cache, no-store, must-revalidate'); // HTTP 1.1.
299
+ res.header('Pragma', 'no-cache'); // HTTP 1.0.
300
+ res.header('Expires', '0'); // Proxies.
301
+
302
+ next();
303
+
304
+ });
305
+ }
306
+
307
+ // ---------------
308
+ // VIEWS
309
+ // ---------------
310
+ server.set('views', views.path);
311
+
312
+ const envNunjucks = nunjucks.configure(views.path, {
313
+ express: server,
314
+ autoescape: true,
315
+ watch: !app.PRODUCTION
316
+ });
317
+
318
+ app.server.views._engine = envNunjucks;
319
+
320
+ envNunjucks.addGlobal('app', app);
321
+
322
+ if (views.globals && Array.isArray(views.globals)) {
323
+ views.globals.forEach((global) => {
324
+ Object.keys(global || []).forEach((i) => {
325
+ envNunjucks.addGlobal(i, global[i]);
326
+ });
327
+ });
328
+ }
329
+
330
+ if (views.filters && Array.isArray(views.filters)) {
331
+ views.filters.forEach((filter) => {
332
+ Object.keys(filter || []).forEach((i) => {
333
+ envNunjucks.addFilter(i, filter[i]);
334
+ });
335
+ });
336
+ }
337
+
338
+ if (views.extensions && Array.isArray(views.extensions)) {
339
+ views.extensions.forEach((extension) => {
340
+ Object.keys(extension || []).forEach((i) => {
341
+ envNunjucks.addExtension(i, extension[i]);
342
+ });
343
+ });
344
+ }
345
+
346
+ // ---------------
347
+ // ROUTES
348
+ // ---------------
349
+
350
+ const {
351
+ routes
352
+ } = app;
353
+
354
+ let method;
355
+ let pathToRoute;
356
+ let handler;
357
+
358
+ Object.keys(routes).forEach((route) => {
359
+
360
+ const parts = route.split(' ');
361
+
362
+ const [
363
+ methodToRun,
364
+ pathToRun
365
+ ] = parts;
366
+
367
+ method = methodToRun;
368
+
369
+ if (pathToRun) {
370
+ pathToRoute = pathToRun;
371
+ }
372
+
373
+ handler = routes[route];
374
+
375
+ if (method === 'post') {
376
+ server[method](pathToRoute, upload.any(), middleware, handler);
377
+ } else {
378
+ server[method](pathToRoute, middleware, handler);
379
+ }
380
+
381
+ });
382
+
383
+ Object.keys(this.routes || {}).forEach((i) => {
384
+
385
+ const fullPath = this.routes[i].split('.');
386
+
387
+ const [
388
+ moduleToRun,
389
+ controllerToRun,
390
+ actionToRun
391
+ ] = fullPath;
392
+
393
+ let module;
394
+ let controller;
395
+ let action;
396
+
397
+ if (actionToRun) { // Has folder
398
+ module = moduleToRun;
399
+ controller = controllerToRun;
400
+ action = actionToRun;
401
+ } else {
402
+ module = null;
403
+ controller = moduleToRun;
404
+ action = controllerToRun;
405
+ }
406
+
407
+ const parts = i.split(' ');
408
+ const pathToRun = parts.pop();
409
+ let option = (parts[0] !== undefined) ? parts[0].toLowerCase() : 'get';
410
+ if (option !== 'get' && option !== 'post' && option !== 'put' && option !== 'delete') {
411
+ option = 'get';
412
+ }
413
+
414
+ let toExecute = null;
415
+
416
+ try {
417
+ toExecute = module
418
+ ? (AllControllers[module][controller][action])
419
+ : AllControllers[controller][action];
420
+ } catch (e) {
421
+ toExecute = null;
422
+ }
423
+
424
+ if (toExecute) {
425
+ if (option === 'post') {
426
+ server[option](pathToRun || '/', upload.any(), middleware, toExecute);
427
+ } else {
428
+ server[option](pathToRun || '/', middleware, toExecute);
429
+ }
430
+ } else {
431
+ console.error('\x1b[31mError:', 'Controller not found in', (module) ? `${module}.${controller}.${action}` : `${controller}.${action}`, '\x1b[0m');
432
+ }
433
+
434
+ });
435
+
436
+ // ---------------
437
+ // ERROR 404
438
+ // ---------------
439
+ server.use((req, res) => {
440
+ if (+res.statusCode >= 500 && +res.statusCode < 600) {
441
+ throw new Error();
442
+ }
443
+ res.status(404).render(`${views.path}/_shared/errors/404.html`);
444
+ });
445
+
446
+ // ---------------
447
+ // ERROR 5XX
448
+ // ---------------
449
+ server.use((err, req, res) => {
450
+ const status = err.status || res.statusCode || 500;
451
+ res.status(status);
452
+ if (!res.xhr) {
453
+ if (+status > 400 && +status < 500) {
454
+ res.render(`${server.get('views')}/_shared/errors/404.html`, { content: err.stack });
455
+ } else {
456
+ res.render(`${server.get('views')}/_shared/errors/500.html`, { content: err.stack });
457
+ }
458
+ } else {
459
+ res.jsonp({
460
+ success: false,
461
+ error: err.message || err.error || err.invalidAttributes || err.toString() || 'Object Not Found',
462
+ data: (app.PRODUCTION) ? {} : (err.stack || {})
463
+ });
464
+ }
465
+ });
466
+
467
+ // ---------------
468
+ // SOCKETS
469
+ // ---------------
470
+ if (sockets.enabled) {
471
+
472
+ const socketProps = {
473
+ pingTimeout: +sockets.timeout || 4000,
474
+ pingInterval: +sockets.interval || 2000,
475
+ transports: sockets.transports || ['websocket', 'polling']
476
+ };
477
+
478
+ if (sockets.cors) {
479
+ if (typeof sockets.cors === 'function') {
480
+ socketProps.allowRequest = sockets.cors;
481
+ } else if (typeof sockets.cors === 'string') {
482
+ socketProps.cors = sockets.cors || '';
483
+ }
484
+ }
485
+
486
+ if (sockets.redis && !redis.enabled) {
487
+ throw new Error('Enable the Redis config "app/config/redis.js" to connect the sockets');
488
+ }
489
+
490
+ const io = new Server(server.listen(expressConfig.port), socketProps);
491
+
492
+ let pubClient = null;
493
+ let subClient = null;
494
+
495
+ if (sockets.redis) {
496
+
497
+ const propsToRedis = {
498
+ host: redis.host,
499
+ port: redis.port
500
+ };
501
+
502
+ if (redis.password) {
503
+ propsToRedis.password = redis.password;
504
+ }
505
+
506
+ pubClient = createClient(propsToRedis);
507
+ subClient = pubClient.duplicate();
508
+
509
+ io.adapter(createAdapter(pubClient, subClient));
510
+
511
+ }
512
+
513
+ Promise
514
+ .all([
515
+ (sockets.redis ? pubClient.connect() : null),
516
+ (sockets.redis ? subClient.connect() : null)
517
+ ])
518
+ .then(() => {
519
+
520
+ io.on('connection', (socket) => {
521
+
522
+ if ( typeof sockets.onConnect === 'function') {
523
+ sockets.onConnect(socket);
524
+ }
525
+
526
+ const socketEvents = sockets.events || {};
527
+
528
+ Object.keys(socketEvents).forEach( (i) => {
529
+
530
+ const checkPath = socketEvents[i] || '';
531
+
532
+ let toExecute = null;
533
+ let module = null;
534
+ let controller = null;
535
+ let action = null;
536
+
537
+ if (typeof checkPath === 'function') {
538
+
539
+ toExecute = checkPath;
540
+
541
+ } else {
542
+
543
+ const fullPath = checkPath.split('.');
544
+
545
+ if (fullPath.length > 2) { // Has folder
546
+
547
+ [
548
+ module,
549
+ controller,
550
+ action
551
+ ] = fullPath;
552
+
553
+ } else {
554
+
555
+ [
556
+ controller,
557
+ action
558
+ ] = fullPath;
559
+
560
+ }
561
+
562
+ try {
563
+ toExecute = module
564
+ ? (AllControllers[module][controller][action])
565
+ : AllControllers[controller][action];
566
+ } catch (e) {
567
+ toExecute = null;
568
+ }
569
+
570
+ }
571
+
572
+ if (toExecute) {
573
+ socket.on(i, (body) => {
574
+ toExecute({ socket, body: body || {} });
575
+ });
576
+ } else {
577
+ console.error('\x1b[31mError:', 'Controller not found in', (module) ? `${module}.${controller}.${action}` : `${controller}.${action}`, '\x1b[0m', 'to socket event', i);
578
+ }
579
+
580
+ });
581
+
582
+ server.set('socket', socket);
583
+ app.socket = socket;
584
+
585
+ });
586
+
587
+ // next line is the money
588
+ global.io = io;
589
+ server.set('socketio', io);
590
+
591
+ // middleware
592
+ if (sockets.middleware) {
593
+ io.use(sockets.middleware);
594
+ }
595
+
596
+ app.server = server;
597
+
598
+ cb();
599
+
600
+ });
601
+
602
+ return;
603
+
604
+ }
605
+
606
+ // ---------------
607
+ // START SERVER
608
+ // ---------------
609
+ server.listen(server.get('port'), () => {
610
+ app.server = {
611
+ ...app.server,
612
+ ...server
613
+ };
614
+ cb();
615
+ });
616
+
617
+ }
618
+
619
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Services
3
+ */
4
+
5
+ const path = require('path');
6
+
7
+ // Include all libs & services
8
+ const coreLibs = require('include-all')({
9
+ dirname: path.join(CORE_PATH, '/libs'),
10
+ filter: /(.+)\.js$/,
11
+ optional: true
12
+ });
13
+
14
+ const appServices = require('include-all')({
15
+ dirname: path.join(APP_PATH, '/services'),
16
+ filter: /(.+)\.js$/,
17
+ optional: true
18
+ });
19
+
20
+ const appLibs = require('include-all')({
21
+ dirname: path.join(APP_PATH, '/libs'),
22
+ filter: /(.+)\.js$/,
23
+ optional: true
24
+ });
25
+
26
+
27
+ const allServices = {
28
+ ...coreLibs,
29
+ ...appServices,
30
+ ...appLibs
31
+ };
32
+
33
+ module.exports = function loadServicesApplication() {
34
+
35
+ delete allServices.ActiveRecord;
36
+ delete allServices.AppController;
37
+
38
+ Object.keys(allServices).forEach((service) => {
39
+ global[service] = allServices[service];
40
+ });
41
+
42
+ };
package/bun.lockb ADDED
Binary file