@reldens/cms 0.27.0 → 0.28.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.
package/lib/manager.js CHANGED
@@ -1,507 +1,515 @@
1
- /**
2
- *
3
- * Reldens - CMS - Manager
4
- *
5
- */
6
-
7
- const { TemplatesList } = require('./templates-list');
8
- const { DefaultTranslations } = require('./admin-manager/default-translations');
9
- const { AdminTemplatesLoader } = require('./admin-templates-loader');
10
- const { AdminManagerValidator } = require('./admin-manager-validator');
11
- const { MimeTypes } = require('./mime-types');
12
- const { AllowedExtensions } = require('./allowed-extensions');
13
- const { TemplatesToPathMapper } = require('./templates-to-path-mapper');
14
- const { AdminEntitiesGenerator } = require('./admin-entities-generator');
15
- const { LoadedEntitiesProcessor } = require('./loaded-entities-processor');
16
- const { AdminManager } = require('./admin-manager');
17
- const { CmsPagesRouteManager } = require('./cms-pages-route-manager');
18
- const { Installer } = require('./installer');
19
- const { Frontend } = require('./frontend');
20
- const { CacheManager } = require('./cache/cache-manager');
21
- const { TemplateReloader } = require('./template-reloader');
22
- const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils');
23
- const { DriversMap } = require('@reldens/storage');
24
- const { AppServerFactory, FileHandler, Encryptor } = require('@reldens/server-utils');
25
- const dotenv = require('dotenv');
26
- const mustache = require('mustache');
27
-
28
- class Manager
29
- {
30
-
31
- constructor(props = {})
32
- {
33
- this.projectRoot = sc.get(props, 'projectRoot', './');
34
- this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
35
- this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
36
- dotenv.config({path: this.envFilePath});
37
- this.config = this.loadConfigFromEnv();
38
- this.adminTranslations = sc.get(props, 'adminTranslations', {});
39
- this.adminEntities = sc.get(props, 'adminEntities', {});
40
- this.rawRegisteredEntities = sc.get(props, 'rawRegisteredEntities', {});
41
- this.entitiesTranslations = sc.get(props, 'entitiesTranslations', {});
42
- this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
43
- this.processedEntities = sc.get(props, 'processedEntities', {});
44
- this.entityAccess = sc.get(props, 'entityAccess', {});
45
- this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
46
- this.authenticationCallback = sc.get(props, 'authenticationCallback', false);
47
- this.events = sc.get(props, 'events', EventsManagerSingleton);
48
- this.adminTemplatesList = sc.get(props, 'adminTemplatesList', TemplatesList);
49
- this.projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
50
- this.projectAdminTemplatesPath = FileHandler.joinPaths(this.projectAdminPath, 'templates');
51
- this.mimeTypes = sc.get(props, 'mimeTypes', MimeTypes);
52
- this.allowedExtensions = sc.get(props, 'allowedExtensions', AllowedExtensions);
53
- this.adminRoleId = sc.get(props, 'adminRoleId', 99);
54
- this.mappedAdminTemplates = TemplatesToPathMapper.map(this.adminTemplatesList, this.projectAdminTemplatesPath);
55
- this.stylesFilePath = sc.get(props, 'stylesFilePath', '/css/reldens-admin-client.css');
56
- this.scriptsFilePath = sc.get(props, 'scriptsFilePath', '/js/reldens-admin-client.js');
57
- this.companyName = sc.get(props, 'companyName', 'Reldens - CMS');
58
- this.logo = sc.get(props, 'logo', '/assets/web/reldens-your-logo-mage.png');
59
- this.favicon = sc.get(props, 'favicon', '/assets/web/favicon.ico');
60
- this.defaultDomain = sc.get(props, 'defaultDomain', (process.env.RELDENS_DEFAULT_DOMAIN || ''));
61
- this.domainMapping = sc.get(props, 'domainMapping', sc.toJson(process.env.RELDENS_DOMAIN_MAPPING));
62
- this.siteKeyMapping = sc.get(props, 'siteKeyMapping', sc.toJson(process.env.RELDENS_SITE_KEY_MAPPING));
63
- this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.template']);
64
- this.cache = sc.get(props, 'cache', false);
65
- this.reloadTime = sc.get(props, 'reloadTime', 0);
66
- this.app = sc.get(props, 'app', false);
67
- this.appServer = sc.get(props, 'appServer', false);
68
- this.dataServer = sc.get(props, 'dataServer', false);
69
- this.adminManager = sc.get(props, 'adminManager', false);
70
- this.frontend = sc.get(props, 'frontend', false);
71
- this.renderEngine = sc.get(props, 'renderEngine', mustache);
72
- this.prismaClient = sc.get(props, 'prismaClient', false);
73
- this.defaultDevelopmentPatterns = ['localhost', '127.0.0.1', '.local', '.test', '.dev'];
74
- this.developmentPatterns = sc.get(props, 'developmentPatterns', this.defaultDevelopmentPatterns);
75
- this.developmentEnvironments = sc.get(props, 'developmentEnvironments', ['development', 'dev', 'test']);
76
- this.developmentPorts = sc.get(props, 'developmentPorts', [3000, 8080, 8081]);
77
- this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
78
- this.appServerConfig = sc.get(props, 'appServerConfig', {});
79
- this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
80
- this.appServerFactory = new AppServerFactory();
81
- this.adminEntitiesGenerator = new AdminEntitiesGenerator();
82
- this.cacheManager = new CacheManager({projectRoot: this.projectRoot, enabled: this.cache});
83
- this.templateReloader = new TemplateReloader({
84
- reloadTime: this.reloadTime,
85
- events: this.events,
86
- adminTemplatesLoader: AdminTemplatesLoader,
87
- mappedAdminTemplates: this.mappedAdminTemplates,
88
- templatesPath: FileHandler.joinPaths(this.projectRoot, 'templates'),
89
- templateExtensions: this.templateExtensions
90
- });
91
- this.installer = new Installer({
92
- projectRoot: this.projectRoot,
93
- prismaClient: this.prismaClient,
94
- postInstallCallback: this.initializeCmsAfterInstall.bind(this)
95
- });
96
- this.useProvidedServer = this.validateProvidedServer();
97
- this.useProvidedDataServer = this.validateProvidedDataServer();
98
- this.useProvidedAdminManager = this.validateProvidedAdminManager();
99
- this.useProvidedFrontend = this.validateProvidedFrontend();
100
- this.cmsPagesRouteManager = new CmsPagesRouteManager({
101
- dataServer: this.dataServer,
102
- events: this.events
103
- });
104
- }
105
-
106
- validateProvidedServer()
107
- {
108
- if(!this.app){
109
- return false;
110
- }
111
- if(!this.appServer){
112
- return false;
113
- }
114
- if('function' !== typeof this.app.use){
115
- Logger.critical('Invalid app instance provided - missing use method.');
116
- return false;
117
- }
118
- if('function' !== typeof this.appServer.listen){
119
- Logger.critical('Invalid appServer instance provided - missing listen method.');
120
- return false;
121
- }
122
- return true;
123
- }
124
-
125
- validateProvidedDataServer()
126
- {
127
- if(!this.dataServer){
128
- return false;
129
- }
130
- if('function' !== typeof this.dataServer.connect){
131
- Logger.critical('Invalid dataServer instance provided - missing connect method.');
132
- return false;
133
- }
134
- if('function' !== typeof this.dataServer.generateEntities){
135
- Logger.critical('Invalid dataServer instance provided - missing generateEntities method.');
136
- return false;
137
- }
138
- return true;
139
- }
140
-
141
- validateProvidedAdminManager()
142
- {
143
- if(!this.adminManager){
144
- return false;
145
- }
146
- if('function' !== typeof this.adminManager.setupAdmin){
147
- Logger.critical('Invalid adminManager instance provided - missing setupAdmin method.');
148
- return false;
149
- }
150
- return true;
151
- }
152
-
153
- validateProvidedFrontend()
154
- {
155
- if(!this.frontend){
156
- return false;
157
- }
158
- if('function' !== typeof this.frontend.initialize){
159
- Logger.critical('Invalid frontend instance provided - missing initialize method.');
160
- return false;
161
- }
162
- return true;
163
- }
164
-
165
- loadConfigFromEnv()
166
- {
167
- let envVars = process.env;
168
- return {
169
- host: sc.get(envVars, 'RELDENS_APP_HOST', 'http://localhost'),
170
- port: Number(sc.get(envVars, 'RELDENS_APP_PORT', 8080)),
171
- adminPath: sc.get(envVars, 'RELDENS_ADMIN_ROUTE_PATH', '/reldens-admin'),
172
- adminSecret: sc.get(envVars, 'RELDENS_ADMIN_SECRET', ''),
173
- database: {
174
- client: sc.get(envVars, 'RELDENS_DB_CLIENT', 'mysql'),
175
- host: sc.get(envVars, 'RELDENS_DB_HOST', 'localhost'),
176
- port: Number(sc.get(envVars, 'RELDENS_DB_PORT', 3306)),
177
- name: sc.get(envVars, 'RELDENS_DB_NAME', 'reldens_cms'),
178
- user: sc.get(envVars, 'RELDENS_DB_USER', ''),
179
- password: sc.get(envVars, 'RELDENS_DB_PASSWORD', ''),
180
- driver: sc.get(envVars, 'RELDENS_STORAGE_DRIVER', 'prisma')
181
- }
182
- };
183
- }
184
-
185
- isInstalled()
186
- {
187
- return FileHandler.exists(this.installLockPath);
188
- }
189
-
190
- async start()
191
- {
192
- if(!this.useProvidedServer){
193
- let appServerConfig = this.buildAppServerConfiguration();
194
- let createdAppServer = this.appServerFactory.createAppServer(appServerConfig);
195
- if(this.appServerFactory.error.message){
196
- Logger.error('App server error: '+this.appServerFactory.error.message);
197
- return false;
198
- }
199
- this.app = createdAppServer.app;
200
- this.appServer = createdAppServer.appServer;
201
- }
202
- if(!this.isInstalled()){
203
- Logger.info('CMS not installed, preparing setup');
204
- await this.installer.prepareSetup(this.app, this.appServer, this.appServerFactory, this.renderEngine);
205
- if(!this.useProvidedServer){
206
- await this.appServer.listen(this.config.port);
207
- }
208
- Logger.info('Installer running on '+this.config.host+':'+this.config.port);
209
- return true;
210
- }
211
- try {
212
- await this.initializeServices();
213
- Logger.info('CMS running on '+this.config.host+':'+this.config.port);
214
- return true;
215
- } catch (error) {
216
- Logger.critical('Failed to start CMS: '+error.message);
217
- return false;
218
- }
219
- }
220
-
221
- buildAppServerConfiguration()
222
- {
223
- let baseConfig = {
224
- port: this.config.port,
225
- useHttps: this.config.host.startsWith('https://'),
226
- domainMapping: this.domainMapping || {},
227
- defaultDomain: this.defaultDomain,
228
- developmentPatterns: this.developmentPatterns,
229
- developmentEnvironments: this.developmentEnvironments,
230
- developmentPorts: this.developmentPorts,
231
- developmentMultiplier: this.developmentMultiplier,
232
- developmentExternalDomains: this.developmentExternalDomains
233
- };
234
- let appServerConfig = Object.assign({}, baseConfig, this.appServerConfig);
235
- if(this.domainMapping && 'object' === typeof this.domainMapping){
236
- let mappingKeys = Object.keys(this.domainMapping);
237
- for(let domain of mappingKeys){
238
- this.appServerFactory.addDevelopmentDomain(domain);
239
- }
240
- this.appServerFactory.setDomainMapping(this.domainMapping);
241
- }
242
- return appServerConfig;
243
- }
244
-
245
- async initializeCmsAfterInstall(props)
246
- {
247
- try {
248
- this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
249
- this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
250
- this.entitiesConfig = props.loadedEntities.entitiesConfig;
251
- this.config = props.mappedVariablesForConfig;
252
- await this.initializeServices();
253
- Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
254
- return true;
255
- } catch (error) {
256
- Logger.critical('Failed to initialize CMS after installation: '+error.message);
257
- return false;
258
- }
259
- }
260
-
261
- async initializeServices()
262
- {
263
- this.events.emit('reldens.cmsManagerInitializeServices', {manager: this});
264
- if(!this.useProvidedDataServer){
265
- if(!await this.initializeDataServer()){
266
- Logger.debug('Initialize Data Server failed.');
267
- return false;
268
- }
269
- }
270
- if(0 < Object.keys(this.entityAccess).length){
271
- await this.setupEntityAccess();
272
- }
273
- if(!this.loadProcessedEntities()){
274
- Logger.debug('Load Processed Entities for Entities failed.');
275
- return false;
276
- }
277
- if(!await this.generateAdminEntities()){
278
- Logger.debug('Generate Admin Entities for Entities failed.');
279
- return false;
280
- }
281
- if(!this.useProvidedAdminManager){
282
- if(!await this.initializeAdminManager()){
283
- Logger.debug('Initialize Admin Manager failed.');
284
- return false;
285
- }
286
- }
287
- if(!await this.initializeCmsPagesRouteManager()){
288
- Logger.debug('Initialize CMS Pages Route Manager failed.');
289
- return false;
290
- }
291
- if(!this.useProvidedFrontend){
292
- if(!await this.initializeFrontend()){
293
- Logger.debug('Initialize Frontend failed.');
294
- return false;
295
- }
296
- }
297
- if(!this.useProvidedServer){
298
- await this.appServer.listen(this.config.port);
299
- }
300
- Logger.debug('Initialize Services successfully.');
301
- return true;
302
- }
303
-
304
- async setupEntityAccess()
305
- {
306
- let accessEntity = this.dataServer.getEntity('entitiesAccess');
307
- if(!accessEntity){
308
- Logger.warning('Entities Access not found.');
309
- return;
310
- }
311
- for(let entityName of Object.keys(this.entityAccess)){
312
- let accessConfig = this.entityAccess[entityName];
313
- if(!await accessEntity.loadOneBy('entity_name', entityName)){
314
- await accessEntity.create({
315
- entity_name: entityName,
316
- is_public: sc.get(accessConfig, 'public', false),
317
- allowed_operations: JSON.stringify(sc.get(accessConfig, 'operations', ['read']))
318
- });
319
- }
320
- }
321
- }
322
-
323
- loadProcessedEntities()
324
- {
325
- if(0 === Object.keys(this.processedEntities).length){
326
- this.processedEntities = LoadedEntitiesProcessor.process(
327
- this.rawRegisteredEntities,
328
- this.entitiesTranslations,
329
- this.entitiesConfig
330
- );
331
- }
332
- if(!this.processedEntities?.entities){
333
- Logger.critical('Processed entities undefined.');
334
- return false;
335
- }
336
- return true;
337
- }
338
-
339
- async generateAdminEntities()
340
- {
341
- if(0 < Object.keys(this.adminEntities).length){
342
- return true;
343
- }
344
- if(!this.dataServer.rawEntities && this.rawRegisteredEntities){
345
- this.dataServer.rawEntities = this.rawRegisteredEntities;
346
- }
347
- Logger.debug('Generate entities count: '+Object.keys(this.rawRegisteredEntities).length);
348
- await this.dataServer.generateEntities();
349
- this.adminEntities = this.adminEntitiesGenerator.generate(
350
- this.processedEntities.entities,
351
- this.dataServer.entityManager.entities
352
- );
353
- if(0 === Object.keys(this.adminEntities).length){
354
- Logger.warning('Admin entities not found.');
355
- return false;
356
- }
357
- return true;
358
- }
359
-
360
- async initializeDataServer()
361
- {
362
- let dbConfig = {
363
- client: this.config.database.client,
364
- config: {
365
- host: this.config.database.host,
366
- port: this.config.database.port,
367
- database: this.config.database.name,
368
- user: this.config.database.user,
369
- password: this.config.database.password
370
- },
371
- rawEntities: this.rawRegisteredEntities,
372
- entitiesConfig: this.entitiesConfig
373
- };
374
- let driverClass = DriversMap[this.config.database.driver];
375
- if(!driverClass){
376
- Logger.critical('Invalid database driver: '+this.config.database.driver);
377
- return false;
378
- }
379
- if('prisma' === this.config.database.driver && this.prismaClient){
380
- dbConfig.prismaClient = this.prismaClient;
381
- }
382
- this.dataServer = new driverClass(dbConfig);
383
- if(!await this.dataServer.connect()){
384
- Logger.critical('Failed to connect to database.');
385
- return false;
386
- }
387
- Logger.debug('Entities count: '+Object.keys(this.rawRegisteredEntities).length);
388
- await this.dataServer.generateEntities();
389
- return true;
390
- }
391
-
392
- async initializeAdminManager()
393
- {
394
- let authenticationCallback = this.authenticationCallback;
395
- if('db-users' === this.authenticationMethod && !authenticationCallback){
396
- authenticationCallback = async (email, password, roleId) => {
397
- Logger.debug('Running default "db-users" authentication.');
398
- let usersEntity = this.dataServer.getEntity('users');
399
- if(!usersEntity){
400
- Logger.critical('No users entity found.');
401
- return false;
402
- }
403
- let user = await usersEntity.loadOneBy('email', email);
404
- if(!user){
405
- Logger.debug('User not found by email: '+email+'.', user);
406
- return false;
407
- }
408
- if(Number(user.role_id) !== Number(roleId)){
409
- Logger.debug('Invalid user role ID: '+roleId+' / '+user.role_id+'.');
410
- return false;
411
- }
412
- let passwordResult = Encryptor.validatePassword(password, user.password) ? user : false;
413
- if(!passwordResult){
414
- Logger.debug('Invalid user password for: '+email+'.');
415
- }
416
- return passwordResult;
417
- };
418
- }
419
- this.templateReloader.trackTemplateFiles(this.mappedAdminTemplates);
420
- let adminFilesContents = await AdminTemplatesLoader.fetchAdminFilesContents(this.mappedAdminTemplates);
421
- let translations = sc.deepMergeProperties(
422
- sc.deepMergeProperties({}, DefaultTranslations),
423
- sc.deepMergeProperties(this.entitiesTranslations, this.adminTranslations)
424
- );
425
- this.events.emit('reldens.manager.initializeAdminManager', {
426
- manager: this,
427
- authenticationCallback,
428
- adminFilesContents,
429
- translations
430
- });
431
- let adminConfig = {
432
- events: this.events,
433
- dataServer: this.dataServer,
434
- authenticationCallback,
435
- app: this.app,
436
- appServerFactory: this.appServerFactory,
437
- entities: this.adminEntities,
438
- validator: new AdminManagerValidator(),
439
- renderCallback: this.renderCallback.bind(this),
440
- secret: this.config.adminSecret,
441
- rootPath: this.config.adminPath,
442
- translations,
443
- adminFilesContents,
444
- mimeTypes: this.mimeTypes,
445
- allowedExtensions: this.allowedExtensions,
446
- adminRoleId: this.adminRoleId,
447
- stylesFilePath: this.stylesFilePath,
448
- scriptsFilePath: this.scriptsFilePath,
449
- cacheManager: this.cacheManager,
450
- branding: {
451
- companyName: this.companyName,
452
- logo: this.logo,
453
- favicon: this.favicon,
454
- copyRight: await FileHandler.fetchFileContents(
455
- FileHandler.joinPaths(this.projectAdminTemplatesPath, this.adminTemplatesList.defaultCopyRight)
456
- )
457
- }
458
- };
459
- this.adminManager = new AdminManager(adminConfig);
460
- this.adminManager.router.checkAndReloadAdminTemplates = async () => {
461
- return await this.templateReloader.handleAdminTemplateReload(this.adminManager);
462
- };
463
- await this.adminManager.setupAdmin();
464
- return true;
465
- }
466
-
467
- async initializeCmsPagesRouteManager()
468
- {
469
- if(!this.dataServer){
470
- Logger.warning('CmsPagesRouteManager initialization skipped - missing dataServer.');
471
- return false;
472
- }
473
- this.cmsPagesRouteManager.dataServer = this.dataServer;
474
- Logger.debug('CmsPagesRouteManager initialized successfully');
475
- return true;
476
- }
477
-
478
- async renderCallback(template, params = {})
479
- {
480
- if(!template){
481
- return '';
482
- }
483
- return this.renderEngine.render(template, params);
484
- }
485
-
486
- async initializeFrontend()
487
- {
488
- this.frontend = new Frontend({
489
- app: this.app,
490
- dataServer: this.dataServer,
491
- events: this.events,
492
- renderEngine: this.renderEngine,
493
- projectRoot: this.projectRoot,
494
- appServerFactory: this.appServerFactory,
495
- defaultDomain: this.defaultDomain,
496
- domainMapping: this.domainMapping,
497
- siteKeyMapping: this.siteKeyMapping,
498
- templateExtensions: this.templateExtensions,
499
- entitiesConfig: this.entitiesConfig,
500
- cacheManager: this.cacheManager,
501
- handleFrontendTemplateReload: this.templateReloader.handleFrontendTemplateReload.bind(this.templateReloader)
502
- });
503
- return await this.frontend.initialize();
504
- }
505
- }
506
-
507
- module.exports.Manager = Manager;
1
+ /**
2
+ *
3
+ * Reldens - CMS - Manager
4
+ *
5
+ */
6
+
7
+ const { TemplatesList } = require('./templates-list');
8
+ const { DefaultTranslations } = require('./admin-manager/default-translations');
9
+ const { AdminTemplatesLoader } = require('./admin-templates-loader');
10
+ const { AdminManagerValidator } = require('./admin-manager-validator');
11
+ const { MimeTypes } = require('./mime-types');
12
+ const { AllowedExtensions } = require('./allowed-extensions');
13
+ const { TemplatesToPathMapper } = require('./templates-to-path-mapper');
14
+ const { AdminEntitiesGenerator } = require('./admin-entities-generator');
15
+ const { LoadedEntitiesProcessor } = require('./loaded-entities-processor');
16
+ const { AdminManager } = require('./admin-manager');
17
+ const { CmsPagesRouteManager } = require('./cms-pages-route-manager');
18
+ const { Installer } = require('./installer');
19
+ const { Frontend } = require('./frontend');
20
+ const { CacheManager } = require('./cache/cache-manager');
21
+ const { TemplateReloader } = require('./template-reloader');
22
+ const { EventsManagerSingleton, Logger, sc } = require('@reldens/utils');
23
+ const { DriversMap } = require('@reldens/storage');
24
+ const { AppServerFactory, FileHandler, Encryptor } = require('@reldens/server-utils');
25
+ const dotenv = require('dotenv');
26
+ const mustache = require('mustache');
27
+
28
+ class Manager
29
+ {
30
+
31
+ constructor(props = {})
32
+ {
33
+ this.projectRoot = sc.get(props, 'projectRoot', './');
34
+ this.envFilePath = FileHandler.joinPaths(this.projectRoot, '.env');
35
+ this.installLockPath = FileHandler.joinPaths(this.projectRoot, 'install.lock');
36
+ dotenv.config({path: this.envFilePath});
37
+ this.config = this.loadConfigFromEnv();
38
+ this.adminTranslations = sc.get(props, 'adminTranslations', {});
39
+ this.adminEntities = sc.get(props, 'adminEntities', {});
40
+ this.rawRegisteredEntities = sc.get(props, 'rawRegisteredEntities', {});
41
+ this.entitiesTranslations = sc.get(props, 'entitiesTranslations', {});
42
+ this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
43
+ this.processedEntities = sc.get(props, 'processedEntities', {});
44
+ this.entityAccess = sc.get(props, 'entityAccess', {});
45
+ this.authenticationMethod = sc.get(props, 'authenticationMethod', 'db-users');
46
+ this.authenticationCallback = sc.get(props, 'authenticationCallback', false);
47
+ this.events = sc.get(props, 'events', EventsManagerSingleton);
48
+ this.adminTemplatesList = sc.get(props, 'adminTemplatesList', TemplatesList);
49
+ this.projectAdminPath = FileHandler.joinPaths(this.projectRoot, 'admin');
50
+ this.projectAdminTemplatesPath = FileHandler.joinPaths(this.projectAdminPath, 'templates');
51
+ this.mimeTypes = sc.get(props, 'mimeTypes', MimeTypes);
52
+ this.allowedExtensions = sc.get(props, 'allowedExtensions', AllowedExtensions);
53
+ this.adminRoleId = sc.get(props, 'adminRoleId', 99);
54
+ this.mappedAdminTemplates = TemplatesToPathMapper.map(this.adminTemplatesList, this.projectAdminTemplatesPath);
55
+ this.stylesFilePath = sc.get(props, 'stylesFilePath', '/css/reldens-admin-client.css');
56
+ this.scriptsFilePath = sc.get(props, 'scriptsFilePath', '/js/reldens-admin-client.js');
57
+ this.companyName = sc.get(props, 'companyName', 'Reldens - CMS');
58
+ this.logo = sc.get(props, 'logo', '/assets/web/reldens-your-logo-mage.png');
59
+ this.favicon = sc.get(props, 'favicon', '/assets/web/favicon.ico');
60
+ this.defaultDomain = sc.get(props, 'defaultDomain', (process.env.RELDENS_DEFAULT_DOMAIN || ''));
61
+ this.domainMapping = sc.get(props, 'domainMapping', sc.toJson(process.env.RELDENS_DOMAIN_MAPPING));
62
+ this.siteKeyMapping = sc.get(props, 'siteKeyMapping', sc.toJson(process.env.RELDENS_SITE_KEY_MAPPING));
63
+ this.domainPublicUrlMapping = sc.get(
64
+ props,
65
+ 'domainPublicUrlMapping',
66
+ sc.toJson(process.env.RELDENS_DOMAIN_PUBLIC_URL_MAPPING)
67
+ );
68
+ this.templateExtensions = sc.get(props, 'templateExtensions', ['.html', '.template']);
69
+ this.cache = sc.get(props, 'cache', false);
70
+ this.reloadTime = sc.get(props, 'reloadTime', 0);
71
+ this.app = sc.get(props, 'app', false);
72
+ this.appServer = sc.get(props, 'appServer', false);
73
+ this.dataServer = sc.get(props, 'dataServer', false);
74
+ this.adminManager = sc.get(props, 'adminManager', false);
75
+ this.frontend = sc.get(props, 'frontend', false);
76
+ this.renderEngine = sc.get(props, 'renderEngine', mustache);
77
+ this.prismaClient = sc.get(props, 'prismaClient', false);
78
+ this.defaultDevelopmentPatterns = ['localhost', '127.0.0.1', '.local', '.test', '.dev'];
79
+ this.developmentPatterns = sc.get(props, 'developmentPatterns', this.defaultDevelopmentPatterns);
80
+ this.developmentEnvironments = sc.get(props, 'developmentEnvironments', ['development', 'dev', 'test']);
81
+ this.developmentPorts = sc.get(props, 'developmentPorts', [3000, 8080, 8081]);
82
+ this.developmentMultiplier = sc.get(props, 'developmentMultiplier', 10);
83
+ this.appServerConfig = sc.get(props, 'appServerConfig', {});
84
+ this.developmentExternalDomains = sc.get(props, 'developmentExternalDomains', {});
85
+ this.appServerFactory = new AppServerFactory();
86
+ this.adminEntitiesGenerator = new AdminEntitiesGenerator();
87
+ this.cacheManager = new CacheManager({projectRoot: this.projectRoot, enabled: this.cache});
88
+ this.templateReloader = new TemplateReloader({
89
+ reloadTime: this.reloadTime,
90
+ events: this.events,
91
+ adminTemplatesLoader: AdminTemplatesLoader,
92
+ mappedAdminTemplates: this.mappedAdminTemplates,
93
+ templatesPath: FileHandler.joinPaths(this.projectRoot, 'templates'),
94
+ templateExtensions: this.templateExtensions
95
+ });
96
+ this.installer = new Installer({
97
+ projectRoot: this.projectRoot,
98
+ prismaClient: this.prismaClient,
99
+ postInstallCallback: this.initializeCmsAfterInstall.bind(this)
100
+ });
101
+ this.useProvidedServer = this.validateProvidedServer();
102
+ this.useProvidedDataServer = this.validateProvidedDataServer();
103
+ this.useProvidedAdminManager = this.validateProvidedAdminManager();
104
+ this.useProvidedFrontend = this.validateProvidedFrontend();
105
+ this.cmsPagesRouteManager = new CmsPagesRouteManager({
106
+ dataServer: this.dataServer,
107
+ events: this.events
108
+ });
109
+ }
110
+
111
+ validateProvidedServer()
112
+ {
113
+ if(!this.app){
114
+ return false;
115
+ }
116
+ if(!this.appServer){
117
+ return false;
118
+ }
119
+ if('function' !== typeof this.app.use){
120
+ Logger.critical('Invalid app instance provided - missing use method.');
121
+ return false;
122
+ }
123
+ if('function' !== typeof this.appServer.listen){
124
+ Logger.critical('Invalid appServer instance provided - missing listen method.');
125
+ return false;
126
+ }
127
+ return true;
128
+ }
129
+
130
+ validateProvidedDataServer()
131
+ {
132
+ if(!this.dataServer){
133
+ return false;
134
+ }
135
+ if('function' !== typeof this.dataServer.connect){
136
+ Logger.critical('Invalid dataServer instance provided - missing connect method.');
137
+ return false;
138
+ }
139
+ if('function' !== typeof this.dataServer.generateEntities){
140
+ Logger.critical('Invalid dataServer instance provided - missing generateEntities method.');
141
+ return false;
142
+ }
143
+ return true;
144
+ }
145
+
146
+ validateProvidedAdminManager()
147
+ {
148
+ if(!this.adminManager){
149
+ return false;
150
+ }
151
+ if('function' !== typeof this.adminManager.setupAdmin){
152
+ Logger.critical('Invalid adminManager instance provided - missing setupAdmin method.');
153
+ return false;
154
+ }
155
+ return true;
156
+ }
157
+
158
+ validateProvidedFrontend()
159
+ {
160
+ if(!this.frontend){
161
+ return false;
162
+ }
163
+ if('function' !== typeof this.frontend.initialize){
164
+ Logger.critical('Invalid frontend instance provided - missing initialize method.');
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+
170
+ loadConfigFromEnv()
171
+ {
172
+ let envVars = process.env;
173
+ return {
174
+ host: sc.get(envVars, 'RELDENS_APP_HOST', 'http://localhost'),
175
+ port: Number(sc.get(envVars, 'RELDENS_APP_PORT', 8080)),
176
+ adminPath: sc.get(envVars, 'RELDENS_ADMIN_ROUTE_PATH', '/reldens-admin'),
177
+ adminSecret: sc.get(envVars, 'RELDENS_ADMIN_SECRET', ''),
178
+ database: {
179
+ client: sc.get(envVars, 'RELDENS_DB_CLIENT', 'mysql'),
180
+ host: sc.get(envVars, 'RELDENS_DB_HOST', 'localhost'),
181
+ port: Number(sc.get(envVars, 'RELDENS_DB_PORT', 3306)),
182
+ name: sc.get(envVars, 'RELDENS_DB_NAME', 'reldens_cms'),
183
+ user: sc.get(envVars, 'RELDENS_DB_USER', ''),
184
+ password: sc.get(envVars, 'RELDENS_DB_PASSWORD', ''),
185
+ driver: sc.get(envVars, 'RELDENS_STORAGE_DRIVER', 'prisma')
186
+ },
187
+ publicUrl: sc.get(envVars, 'RELDENS_PUBLIC_URL', '')
188
+ };
189
+ }
190
+
191
+ isInstalled()
192
+ {
193
+ return FileHandler.exists(this.installLockPath);
194
+ }
195
+
196
+ async start()
197
+ {
198
+ if(!this.useProvidedServer){
199
+ let appServerConfig = this.buildAppServerConfiguration();
200
+ let createdAppServer = this.appServerFactory.createAppServer(appServerConfig);
201
+ if(this.appServerFactory.error.message){
202
+ Logger.error('App server error: '+this.appServerFactory.error.message);
203
+ return false;
204
+ }
205
+ this.app = createdAppServer.app;
206
+ this.appServer = createdAppServer.appServer;
207
+ }
208
+ if(!this.isInstalled()){
209
+ Logger.info('CMS not installed, preparing setup');
210
+ await this.installer.prepareSetup(this.app, this.appServer, this.appServerFactory, this.renderEngine);
211
+ if(!this.useProvidedServer){
212
+ await this.appServer.listen(this.config.port);
213
+ }
214
+ Logger.info('Installer running on '+this.config.host+':'+this.config.port);
215
+ return true;
216
+ }
217
+ try {
218
+ await this.initializeServices();
219
+ Logger.info('CMS running on '+this.config.host+':'+this.config.port);
220
+ return true;
221
+ } catch (error) {
222
+ Logger.critical('Failed to start CMS: '+error.message);
223
+ return false;
224
+ }
225
+ }
226
+
227
+ buildAppServerConfiguration()
228
+ {
229
+ let baseConfig = {
230
+ port: this.config.port,
231
+ useHttps: this.config.host.startsWith('https://'),
232
+ domainMapping: this.domainMapping || {},
233
+ defaultDomain: this.defaultDomain,
234
+ developmentPatterns: this.developmentPatterns,
235
+ developmentEnvironments: this.developmentEnvironments,
236
+ developmentPorts: this.developmentPorts,
237
+ developmentMultiplier: this.developmentMultiplier,
238
+ developmentExternalDomains: this.developmentExternalDomains
239
+ };
240
+ let appServerConfig = Object.assign({}, baseConfig, this.appServerConfig);
241
+ if(this.domainMapping && 'object' === typeof this.domainMapping){
242
+ let mappingKeys = Object.keys(this.domainMapping);
243
+ for(let domain of mappingKeys){
244
+ this.appServerFactory.addDevelopmentDomain(domain);
245
+ }
246
+ this.appServerFactory.setDomainMapping(this.domainMapping);
247
+ }
248
+ return appServerConfig;
249
+ }
250
+
251
+ async initializeCmsAfterInstall(props)
252
+ {
253
+ try {
254
+ this.rawRegisteredEntities = props.loadedEntities.rawRegisteredEntities;
255
+ this.entitiesTranslations = props.loadedEntities.entitiesTranslations;
256
+ this.entitiesConfig = props.loadedEntities.entitiesConfig;
257
+ this.config = props.mappedVariablesForConfig;
258
+ await this.initializeServices();
259
+ Logger.info('CMS initialized after installation on '+this.config.host+':'+this.config.port);
260
+ return true;
261
+ } catch (error) {
262
+ Logger.critical('Failed to initialize CMS after installation: '+error.message);
263
+ return false;
264
+ }
265
+ }
266
+
267
+ async initializeServices()
268
+ {
269
+ this.events.emit('reldens.cmsManagerInitializeServices', {manager: this});
270
+ if(!this.useProvidedDataServer){
271
+ if(!await this.initializeDataServer()){
272
+ Logger.debug('Initialize Data Server failed.');
273
+ return false;
274
+ }
275
+ }
276
+ if(0 < Object.keys(this.entityAccess).length){
277
+ await this.setupEntityAccess();
278
+ }
279
+ if(!this.loadProcessedEntities()){
280
+ Logger.debug('Load Processed Entities for Entities failed.');
281
+ return false;
282
+ }
283
+ if(!await this.generateAdminEntities()){
284
+ Logger.debug('Generate Admin Entities for Entities failed.');
285
+ return false;
286
+ }
287
+ if(!this.useProvidedAdminManager){
288
+ if(!await this.initializeAdminManager()){
289
+ Logger.debug('Initialize Admin Manager failed.');
290
+ return false;
291
+ }
292
+ }
293
+ if(!await this.initializeCmsPagesRouteManager()){
294
+ Logger.debug('Initialize CMS Pages Route Manager failed.');
295
+ return false;
296
+ }
297
+ if(!this.useProvidedFrontend){
298
+ if(!await this.initializeFrontend()){
299
+ Logger.debug('Initialize Frontend failed.');
300
+ return false;
301
+ }
302
+ }
303
+ if(!this.useProvidedServer){
304
+ await this.appServer.listen(this.config.port);
305
+ }
306
+ Logger.debug('Initialize Services successfully.');
307
+ return true;
308
+ }
309
+
310
+ async setupEntityAccess()
311
+ {
312
+ let accessEntity = this.dataServer.getEntity('entitiesAccess');
313
+ if(!accessEntity){
314
+ Logger.warning('Entities Access not found.');
315
+ return;
316
+ }
317
+ for(let entityName of Object.keys(this.entityAccess)){
318
+ let accessConfig = this.entityAccess[entityName];
319
+ if(!await accessEntity.loadOneBy('entity_name', entityName)){
320
+ await accessEntity.create({
321
+ entity_name: entityName,
322
+ is_public: sc.get(accessConfig, 'public', false),
323
+ allowed_operations: JSON.stringify(sc.get(accessConfig, 'operations', ['read']))
324
+ });
325
+ }
326
+ }
327
+ }
328
+
329
+ loadProcessedEntities()
330
+ {
331
+ if(0 === Object.keys(this.processedEntities).length){
332
+ this.processedEntities = LoadedEntitiesProcessor.process(
333
+ this.rawRegisteredEntities,
334
+ this.entitiesTranslations,
335
+ this.entitiesConfig
336
+ );
337
+ }
338
+ if(!this.processedEntities?.entities){
339
+ Logger.critical('Processed entities undefined.');
340
+ return false;
341
+ }
342
+ return true;
343
+ }
344
+
345
+ async generateAdminEntities()
346
+ {
347
+ if(0 < Object.keys(this.adminEntities).length){
348
+ return true;
349
+ }
350
+ if(!this.dataServer.rawEntities && this.rawRegisteredEntities){
351
+ this.dataServer.rawEntities = this.rawRegisteredEntities;
352
+ }
353
+ Logger.debug('Generate entities count: '+Object.keys(this.rawRegisteredEntities).length);
354
+ await this.dataServer.generateEntities();
355
+ this.adminEntities = this.adminEntitiesGenerator.generate(
356
+ this.processedEntities.entities,
357
+ this.dataServer.entityManager.entities
358
+ );
359
+ if(0 === Object.keys(this.adminEntities).length){
360
+ Logger.warning('Admin entities not found.');
361
+ return false;
362
+ }
363
+ return true;
364
+ }
365
+
366
+ async initializeDataServer()
367
+ {
368
+ let dbConfig = {
369
+ client: this.config.database.client,
370
+ config: {
371
+ host: this.config.database.host,
372
+ port: this.config.database.port,
373
+ database: this.config.database.name,
374
+ user: this.config.database.user,
375
+ password: this.config.database.password
376
+ },
377
+ rawEntities: this.rawRegisteredEntities,
378
+ entitiesConfig: this.entitiesConfig
379
+ };
380
+ let driverClass = DriversMap[this.config.database.driver];
381
+ if(!driverClass){
382
+ Logger.critical('Invalid database driver: '+this.config.database.driver);
383
+ return false;
384
+ }
385
+ if('prisma' === this.config.database.driver && this.prismaClient){
386
+ dbConfig.prismaClient = this.prismaClient;
387
+ }
388
+ this.dataServer = new driverClass(dbConfig);
389
+ if(!await this.dataServer.connect()){
390
+ Logger.critical('Failed to connect to database.');
391
+ return false;
392
+ }
393
+ Logger.debug('Entities count: '+Object.keys(this.rawRegisteredEntities).length);
394
+ await this.dataServer.generateEntities();
395
+ return true;
396
+ }
397
+
398
+ async initializeAdminManager()
399
+ {
400
+ let authenticationCallback = this.authenticationCallback;
401
+ if('db-users' === this.authenticationMethod && !authenticationCallback){
402
+ authenticationCallback = async (email, password, roleId) => {
403
+ Logger.debug('Running default "db-users" authentication.');
404
+ let usersEntity = this.dataServer.getEntity('users');
405
+ if(!usersEntity){
406
+ Logger.critical('No users entity found.');
407
+ return false;
408
+ }
409
+ let user = await usersEntity.loadOneBy('email', email);
410
+ if(!user){
411
+ Logger.debug('User not found by email: '+email+'.', user);
412
+ return false;
413
+ }
414
+ if(Number(user.role_id) !== Number(roleId)){
415
+ Logger.debug('Invalid user role ID: '+roleId+' / '+user.role_id+'.');
416
+ return false;
417
+ }
418
+ let passwordResult = Encryptor.validatePassword(password, user.password) ? user : false;
419
+ if(!passwordResult){
420
+ Logger.debug('Invalid user password for: '+email+'.');
421
+ }
422
+ return passwordResult;
423
+ };
424
+ }
425
+ this.templateReloader.trackTemplateFiles(this.mappedAdminTemplates);
426
+ let adminFilesContents = await AdminTemplatesLoader.fetchAdminFilesContents(this.mappedAdminTemplates);
427
+ let translations = sc.deepMergeProperties(
428
+ sc.deepMergeProperties({}, DefaultTranslations),
429
+ sc.deepMergeProperties(this.entitiesTranslations, this.adminTranslations)
430
+ );
431
+ this.events.emit('reldens.manager.initializeAdminManager', {
432
+ manager: this,
433
+ authenticationCallback,
434
+ adminFilesContents,
435
+ translations
436
+ });
437
+ let adminConfig = {
438
+ events: this.events,
439
+ dataServer: this.dataServer,
440
+ authenticationCallback,
441
+ app: this.app,
442
+ appServerFactory: this.appServerFactory,
443
+ entities: this.adminEntities,
444
+ validator: new AdminManagerValidator(),
445
+ renderCallback: this.renderCallback.bind(this),
446
+ secret: this.config.adminSecret,
447
+ rootPath: this.config.adminPath,
448
+ translations,
449
+ adminFilesContents,
450
+ mimeTypes: this.mimeTypes,
451
+ allowedExtensions: this.allowedExtensions,
452
+ adminRoleId: this.adminRoleId,
453
+ stylesFilePath: this.stylesFilePath,
454
+ scriptsFilePath: this.scriptsFilePath,
455
+ cacheManager: this.cacheManager,
456
+ branding: {
457
+ companyName: this.companyName,
458
+ logo: this.logo,
459
+ favicon: this.favicon,
460
+ copyRight: await FileHandler.fetchFileContents(
461
+ FileHandler.joinPaths(this.projectAdminTemplatesPath, this.adminTemplatesList.defaultCopyRight)
462
+ )
463
+ }
464
+ };
465
+ this.adminManager = new AdminManager(adminConfig);
466
+ this.adminManager.router.checkAndReloadAdminTemplates = async () => {
467
+ return await this.templateReloader.handleAdminTemplateReload(this.adminManager);
468
+ };
469
+ await this.adminManager.setupAdmin();
470
+ return true;
471
+ }
472
+
473
+ async initializeCmsPagesRouteManager()
474
+ {
475
+ if(!this.dataServer){
476
+ Logger.warning('CmsPagesRouteManager initialization skipped - missing dataServer.');
477
+ return false;
478
+ }
479
+ this.cmsPagesRouteManager.dataServer = this.dataServer;
480
+ Logger.debug('CmsPagesRouteManager initialized successfully');
481
+ return true;
482
+ }
483
+
484
+ async renderCallback(template, params = {})
485
+ {
486
+ if(!template){
487
+ return '';
488
+ }
489
+ return this.renderEngine.render(template, params);
490
+ }
491
+
492
+ async initializeFrontend()
493
+ {
494
+ this.frontend = new Frontend({
495
+ app: this.app,
496
+ dataServer: this.dataServer,
497
+ events: this.events,
498
+ renderEngine: this.renderEngine,
499
+ projectRoot: this.projectRoot,
500
+ appServerFactory: this.appServerFactory,
501
+ defaultDomain: this.defaultDomain,
502
+ domainMapping: this.domainMapping,
503
+ siteKeyMapping: this.siteKeyMapping,
504
+ domainPublicUrlMapping: this.domainPublicUrlMapping,
505
+ defaultPublicUrl: this.config.publicUrl,
506
+ templateExtensions: this.templateExtensions,
507
+ entitiesConfig: this.entitiesConfig,
508
+ cacheManager: this.cacheManager,
509
+ handleFrontendTemplateReload: this.templateReloader.handleFrontendTemplateReload.bind(this.templateReloader)
510
+ });
511
+ return await this.frontend.initialize();
512
+ }
513
+ }
514
+
515
+ module.exports.Manager = Manager;