@reldens/cms 0.18.0 → 0.20.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.
Files changed (36) hide show
  1. package/README.md +260 -20
  2. package/admin/reldens-admin-client.css +127 -80
  3. package/admin/reldens-admin-client.js +24 -0
  4. package/admin/templates/clear-all-cache-button.html +18 -0
  5. package/lib/admin-manager/contents-builder.js +7 -6
  6. package/lib/admin-manager/router-contents.js +58 -16
  7. package/lib/admin-manager-validator.js +2 -1
  8. package/lib/admin-manager.js +4 -2
  9. package/lib/admin-translations.js +9 -1
  10. package/lib/cache/add-cache-button-subscriber.js +53 -5
  11. package/lib/cache/cache-manager.js +47 -8
  12. package/lib/cache/cache-routes-handler.js +23 -0
  13. package/lib/cms-pages-route-manager.js +16 -4
  14. package/lib/frontend.js +310 -119
  15. package/lib/manager.js +43 -3
  16. package/lib/pagination-handler.js +243 -0
  17. package/lib/search-renderer.js +116 -0
  18. package/lib/search.js +344 -0
  19. package/lib/template-engine/asset-transformer.js +41 -0
  20. package/lib/template-engine/collections-single-transformer.js +70 -0
  21. package/lib/template-engine/collections-transformer-base.js +84 -0
  22. package/lib/template-engine/collections-transformer.js +374 -0
  23. package/lib/template-engine/date-transformer.js +53 -0
  24. package/lib/template-engine/entities-transformer.js +67 -0
  25. package/lib/template-engine/partials-transformer.js +175 -0
  26. package/lib/template-engine/system-variables-provider.js +105 -0
  27. package/lib/template-engine/translate-transformer.js +98 -0
  28. package/lib/template-engine/translation-service.js +104 -0
  29. package/lib/template-engine/url-transformer.js +41 -0
  30. package/lib/template-engine.js +133 -438
  31. package/lib/templates-list.js +1 -0
  32. package/migrations/install.sql +18 -18
  33. package/package.json +4 -4
  34. package/templates/page.html +19 -2
  35. package/templates/partials/entriesListView.html +14 -0
  36. package/templates/partials/pagedCollection.html +33 -0
@@ -37,7 +37,10 @@ class ContentsBuilder
37
37
  this.adminContents.layout = await this.buildLayout();
38
38
  this.adminContents.sideBar = await this.buildSideBar();
39
39
  this.adminContents.login = await this.renderRoute(this.adminFilesContents.login, '');
40
- this.adminContents.dashboard = await this.renderRoute(this.adminFilesContents.dashboard, this.adminContents.sideBar);
40
+ this.adminContents.dashboard = await this.renderRoute(
41
+ this.adminFilesContents.dashboard,
42
+ this.adminContents.sideBar
43
+ );
41
44
  this.adminContents.entities = await this.buildEntitiesContents();
42
45
  await this.emitEvent('reldens.buildAdminContentsAfter');
43
46
  return this.adminContents;
@@ -67,7 +70,8 @@ class ContentsBuilder
67
70
  navigationContents = eventBuildSideBarBefore.navigationContents;
68
71
  for(let driverResource of this.resources()){
69
72
  let navigation = driverResource.options?.navigation;
70
- let name = this.translations.labels[driverResource.id()] || this.translations.labels[driverResource.entityKey];
73
+ let name = this.translations.labels[driverResource.id()]
74
+ || this.translations.labels[driverResource.entityKey];
71
75
  let path = this.rootPath+'/'+(driverResource.id().replace(/_/g, '-'));
72
76
  if(navigation?.name){
73
77
  if(!navigationContents[navigation.name]){
@@ -93,10 +97,7 @@ class ContentsBuilder
93
97
  for(let subId of Object.keys(navigationContents[id])){
94
98
  subItems += navigationContents[id][subId];
95
99
  }
96
- navigationView += await this.render(
97
- this.adminFilesContents.sideBarHeader,
98
- {name: id, subItems}
99
- );
100
+ navigationView += await this.render(this.adminFilesContents.sideBarHeader, {name: id, subItems});
100
101
  continue;
101
102
  }
102
103
  navigationView += navigationContents[id];
@@ -51,6 +51,14 @@ class RouterContents
51
51
  }
52
52
  );
53
53
  }
54
+ let listProperties = {
55
+ entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath
56
+ };
57
+ await this.emitEvent('reldens.adminListPropertiesPopulation', {
58
+ req,
59
+ driverResource,
60
+ listProperties
61
+ });
54
62
  return await this.adminContentsRenderRoute(
55
63
  await this.adminContentsRender(
56
64
  this.adminContentsEntities()[entityPath].list,
@@ -67,7 +75,8 @@ class RouterContents
67
75
  }),
68
76
  rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, filters)
69
77
  }),
70
- pagination: renderedPagination
78
+ pagination: renderedPagination,
79
+ extraContentForList: sc.get(listProperties, 'extraContentForList', '')
71
80
  }, ...driverResource.options.filterProperties.map((property) => {
72
81
  let filterValue = (filtersFromParams[property] || '');
73
82
  return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
@@ -274,13 +283,21 @@ class RouterContents
274
283
  );
275
284
  fields.push({
276
285
  name: property,
277
- value: await this.generatePropertyRenderedValue(fieldValue, fieldName, resourceProperties[property]),
278
- viewLink: '' !== idProperty ? this.generateEntityRoute('viewPath', driverResource, idProperty, entity) : ''
286
+ value: await this.generatePropertyRenderedValue(
287
+ fieldValue,
288
+ fieldName,
289
+ resourceProperties[property]
290
+ ),
291
+ viewLink: '' !== idProperty
292
+ ? this.generateEntityRoute('viewPath', driverResource, idProperty, entity)
293
+ : ''
279
294
  });
280
295
  }
281
296
  entityRows.push({
282
297
  fields,
283
- editLink: '' !== idProperty ? this.generateEntityRoute('editPath', driverResource, idProperty, entity) : '',
298
+ editLink: '' !== idProperty
299
+ ? this.generateEntityRoute('editPath', driverResource, idProperty, entity)
300
+ : '',
284
301
  deleteLink: this.rootPath + '/' + driverResource.entityPath + this.deletePath,
285
302
  id: entity[idProperty]
286
303
  });
@@ -317,12 +334,13 @@ class RouterContents
317
334
  continue;
318
335
  }
319
336
  for(let entity of entities){
337
+ let bucket = sc.get(property, 'bucket', '');
320
338
  if(!property.isArray){
321
- FileHandler.remove([(property.bucket || ''), entity[propertyKey]]);
339
+ FileHandler.remove([bucket, entity[propertyKey]]);
322
340
  continue;
323
341
  }
324
342
  for(let entityFile of entity[propertyKey].split(property.isArray)){
325
- FileHandler.remove([(property.bucket || ''), entityFile]);
343
+ FileHandler.remove([bucket, entityFile]);
326
344
  }
327
345
  }
328
346
  }
@@ -337,7 +355,10 @@ class RouterContents
337
355
  }
338
356
  let propertyUpdateValue = sc.get(req.body, i, null);
339
357
  let property = resourceProperties[i];
340
- let propertyType = property.type || 'string';
358
+ if('null' === propertyUpdateValue){
359
+ propertyUpdateValue = null;
360
+ }
361
+ let propertyType = sc.get(property, 'type', 'string');
341
362
  if(property.isUpload){
342
363
  propertyType = 'upload';
343
364
  propertyUpdateValue = this.prepareUploadPatchData(req, i, propertyUpdateValue, property);
@@ -345,21 +366,23 @@ class RouterContents
345
366
  if('boolean' === propertyType){
346
367
  propertyUpdateValue = '1' === propertyUpdateValue || 'on' === propertyUpdateValue;
347
368
  }
348
- if('number' === propertyType && null !== propertyUpdateValue && '' !== propertyUpdateValue){
369
+ let isEmpty = '' === propertyUpdateValue;
370
+ let isNull = null === propertyUpdateValue;
371
+ if('number' === propertyType && !isNull && !isEmpty){
349
372
  propertyUpdateValue = Number(propertyUpdateValue);
350
373
  }
351
- if('string' === propertyType && null !== propertyUpdateValue && '' !== propertyUpdateValue){
374
+ if('string' === propertyType && !isNull && !isEmpty){
352
375
  propertyUpdateValue = String(propertyUpdateValue);
353
376
  }
354
- if('' === propertyUpdateValue && !property.isRequired){
377
+ if(isEmpty && !property.isRequired){
355
378
  propertyUpdateValue = null;
356
379
  }
357
380
  let isUploadCreate = property.isUpload && !id;
358
- if(property.isRequired && (null === propertyUpdateValue || '' === propertyUpdateValue) && (!property.isUpload || isUploadCreate)){
381
+ if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
359
382
  Logger.critical('Bad patch data on update.', propertyUpdateValue, property);
360
383
  return false;
361
384
  }
362
- if(!property.isUpload || (property.isUpload && null !== propertyUpdateValue)){
385
+ if(!property.isUpload || (property.isUpload && !isNull)){
363
386
  entityDataPatch[i] = propertyUpdateValue;
364
387
  }
365
388
  }
@@ -405,9 +428,14 @@ class RouterContents
405
428
  generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
406
429
  {
407
430
  let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
431
+ if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
432
+ fieldValue = sc.toJsonString(fieldValue);
433
+ }
408
434
  let fieldName = propertyKey;
409
435
  if('boolean' === resourceProperty.type){
410
- fieldValue = 1 === entity[propertyKey] || '1' === entity[propertyKey] || true === entity[propertyKey] ? 'Yes' : 'No';
436
+ fieldValue = 1 === entity[propertyKey]
437
+ || '1' === entity[propertyKey]
438
+ || true === entity[propertyKey] ? 'Yes' : 'No';
411
439
  }
412
440
  if('datetime' === resourceProperty.type){
413
441
  fieldValue = '' !== fieldValue ? sc.formatDate(new Date(fieldValue)) : '';
@@ -440,8 +468,16 @@ class RouterContents
440
468
  {
441
469
  let entityPropertyValue = sc.get(entity, propertyKey, null);
442
470
  let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '');
471
+ if('null' === fieldValue){
472
+ fieldValue = '';
473
+ }
474
+ if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
475
+ fieldValue = sc.toJsonString(fieldValue);
476
+ }
443
477
  if('boolean' === resourceProperty.type){
444
- fieldValue = 1 === entityPropertyValue || '1' === entityPropertyValue || true === entityPropertyValue ? ' checked="checked"' : '';
478
+ fieldValue = 1 === entityPropertyValue
479
+ || '1' === entityPropertyValue
480
+ || true === entityPropertyValue ? ' checked="checked"' : '';
445
481
  }
446
482
  if('datetime' === resourceProperty.type){
447
483
  fieldValue = !entityPropertyValue || '' === entityPropertyValue
@@ -452,7 +488,9 @@ class RouterContents
452
488
  let relationDriverResource = this.resourcesByReference()[resourceProperty.reference];
453
489
  let relation = this.relations()[resourceProperty.reference];
454
490
  let relationKey = resourceProperty.alias || resourceProperty.reference;
455
- let relationTitleProperty = relation ? relation[relationKey] : this.fetchEntityIdPropertyKey(relationDriverResource);
491
+ let relationTitleProperty = relation
492
+ ? relation[relationKey]
493
+ : this.fetchEntityIdPropertyKey(relationDriverResource);
456
494
  let options = (await this.fetchRelationOptions(relationDriverResource)).map((option) => {
457
495
  let value = option[this.fetchEntityIdPropertyKey(relationDriverResource)];
458
496
  return {
@@ -462,10 +500,11 @@ class RouterContents
462
500
  };
463
501
  });
464
502
  if(!resourceProperty.isRequired){
503
+ let isSelected = !entity || null === entity[propertyKey] || '' === entity[propertyKey];
465
504
  options.unshift({
466
505
  label: '-- Select --',
467
506
  value: '',
468
- selected: (!entity || null === entity[propertyKey] || '' === entity[propertyKey]) ? ' selected="selected"' : ''
507
+ selected: isSelected ? ' selected="selected"' : ''
469
508
  });
470
509
  }
471
510
  return options;
@@ -476,6 +515,9 @@ class RouterContents
476
515
  async fetchRelationOptions(relationDriverResource)
477
516
  {
478
517
  let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
518
+ if(!relationEntityRepository){
519
+ return false;
520
+ }
479
521
  return await relationEntityRepository.loadAll();
480
522
  }
481
523
 
@@ -3,6 +3,7 @@
3
3
  * Reldens - AdminManagerValidator
4
4
  *
5
5
  */
6
+
6
7
  const { ValidatorInterface, Logger } = require('@reldens/utils');
7
8
 
8
9
  class AdminManagerValidator extends ValidatorInterface
@@ -29,7 +30,7 @@ class AdminManagerValidator extends ValidatorInterface
29
30
  return false;
30
31
  }
31
32
  }
32
- return true
33
+ return true;
33
34
  }
34
35
 
35
36
  }
@@ -115,7 +115,7 @@ class AdminManager
115
115
  viewPath: this.viewPath,
116
116
  editPath: this.editPath,
117
117
  deletePath: this.deletePath,
118
- emitEvent: (eventName, eventData = {}) => this.events.emit(eventName, {adminManager: this, ...eventData}),
118
+ emitEvent: this.emitEvent,
119
119
  adminContentsRender: (...args) => this.contentsBuilder.render(...args),
120
120
  adminContentsRenderRoute: (...args) => this.contentsBuilder.renderRoute(...args),
121
121
  adminContentsEntities: () => this.contentsBuilder.adminContents.entities,
@@ -135,8 +135,10 @@ class AdminManager
135
135
  cacheManager: this.cacheManager,
136
136
  renderCallback: this.renderCallback,
137
137
  cacheCleanButton: this.adminFilesContents.cacheCleanButton,
138
+ clearAllCacheButton: this.adminFilesContents.clearAllCacheButton,
138
139
  translations: this.translations,
139
- cacheCleanRoute: this.cacheRoutesHandler.cacheCleanRoute
140
+ cacheCleanRoute: this.cacheRoutesHandler.cacheCleanRoute,
141
+ clearAllCacheRoute: this.cacheRoutesHandler.clearAllCacheRoute
140
142
  });
141
143
  }
142
144
 
@@ -21,7 +21,9 @@ class AdminTranslations
21
21
  reldensGithubText: 'Need a new feature?'
22
22
  +' Would you like to contribute with code?'
23
23
  +' Find the source code or create an issue in GitHub',
24
- reldensLoading: 'Loading...'
24
+ reldensLoading: 'Loading...',
25
+ confirmClearCacheMessage: 'Are you sure you want to clear all cache? This action cannot be undone.',
26
+ clearCacheWarning: 'This will clear all cached routes and may impact site performance temporarily.'
25
27
  },
26
28
  labels: {
27
29
  navigation: 'Reldens - CMS',
@@ -32,6 +34,12 @@ class AdminTranslations
32
34
  shuttingDown: 'Server is shutting down in:',
33
35
  submitShutdownLabel: 'Shutdown Server',
34
36
  submitCancelLabel: 'Cancel Server Shutdown',
37
+ cleanCache: 'Clean Cache',
38
+ clearAllCache: 'Clear All Cache',
39
+ confirmClearCache: 'Confirm Clear Cache',
40
+ warning: 'Warning:',
41
+ cancel: 'Cancel',
42
+ confirm: 'Continue',
35
43
  }
36
44
  };
37
45
  for(let i of Object.keys(translations)){
@@ -15,8 +15,10 @@ class AddCacheButtonSubscriber
15
15
  this.cacheManager = sc.get(props, 'cacheManager', false);
16
16
  this.renderCallback = sc.get(props, 'renderCallback', false);
17
17
  this.cacheCleanButton = sc.get(props, 'cacheCleanButton', '');
18
+ this.clearAllCacheButton = sc.get(props, 'clearAllCacheButton', '');
18
19
  this.translations = sc.get(props, 'translations', {});
19
20
  this.cacheCleanRoute = sc.get(props, 'cacheCleanRoute', '');
21
+ this.clearAllCacheRoute = sc.get(props, 'clearAllCacheRoute', '');
20
22
  this.setupEvents();
21
23
  }
22
24
 
@@ -37,14 +39,14 @@ class AddCacheButtonSubscriber
37
39
  //Logger.debug('Listening events PropertiesPopulation.');
38
40
  this.events.on('reldens.adminViewPropertiesPopulation', this.populateViewFields.bind(this));
39
41
  this.events.on('reldens.adminEditPropertiesPopulation', this.populateEditFields.bind(this));
40
-
42
+ this.events.on('reldens.adminListPropertiesPopulation', this.populateListFields.bind(this));
41
43
  }
42
44
 
43
45
  async populateViewFields(event)
44
46
  {
45
47
  let cacheButton = await this.generateCacheCleanButton(event);
46
48
  if(!cacheButton){
47
- Logger.warning('Missing cache button contents on AddCacheButtonSubscriber.');
49
+ //Logger.info('Missing cache button contents on AddCacheButtonSubscriber.');
48
50
  return false;
49
51
  }
50
52
  if(!event.renderedViewProperties.extraContentForView){
@@ -59,7 +61,7 @@ class AddCacheButtonSubscriber
59
61
  {
60
62
  let cacheButton = await this.generateCacheCleanButton(event);
61
63
  if(!cacheButton){
62
- Logger.warning('Missing cache button contents on AddCacheButtonSubscriber.');
64
+ //Logger.info('Missing cache button contents on AddCacheButtonSubscriber.');
63
65
  return false;
64
66
  }
65
67
  if(!event.renderedEditProperties.extraContentForEdit){
@@ -70,12 +72,30 @@ class AddCacheButtonSubscriber
70
72
  return true;
71
73
  }
72
74
 
75
+ async populateListFields(event)
76
+ {
77
+ let clearAllButton = await this.generateClearAllCacheButton(event);
78
+ if(!clearAllButton){
79
+ return false;
80
+ }
81
+ if(!event.listProperties.extraContentForList){
82
+ event.listProperties.extraContentForList = '';
83
+ }
84
+ event.listProperties.extraContentForList += clearAllButton;
85
+ return true;
86
+ }
87
+
73
88
  async generateCacheCleanButton(event)
74
89
  {
75
90
  if('routes' !== event.driverResource.id()){
76
91
  return false;
77
92
  }
78
- if(!event.loadedEntity || !event.loadedEntity.id){
93
+ if(!event.loadedEntity){
94
+ Logger.error('Missing loaded entity on AddCacheButtonSubscriber.');
95
+ return false;
96
+ }
97
+ if(!event.loadedEntity.id){
98
+ Logger.error('Missing loaded entity ID on AddCacheButtonSubscriber.');
79
99
  return false;
80
100
  }
81
101
  if(!this.cacheCleanButton){
@@ -91,7 +111,35 @@ class AddCacheButtonSubscriber
91
111
  {
92
112
  cacheCleanRoute: this.cacheCleanRoute,
93
113
  routeId: event.loadedEntity.id,
94
- buttonText: sc.get(this.translations, 'cleanCache', 'Clean Cache')
114
+ buttonText: sc.get(this.translations.labels, 'cleanCache', 'cleanCache')
115
+ }
116
+ );
117
+ }
118
+
119
+ async generateClearAllCacheButton(event)
120
+ {
121
+ if('routes' !== event.driverResource.id()){
122
+ return false;
123
+ }
124
+ if(!this.clearAllCacheButton){
125
+ Logger.error('Clear all cache button template content not found');
126
+ return '';
127
+ }
128
+ if(!this.renderCallback){
129
+ Logger.error('Render callback not available for clear all cache button');
130
+ return '';
131
+ }
132
+ return await this.renderCallback(
133
+ this.clearAllCacheButton,
134
+ {
135
+ buttonText: sc.get(this.translations.labels, 'clearAllCache', 'clearAllCache'),
136
+ clearAllCacheRoute: this.clearAllCacheRoute,
137
+ confirmTitle: sc.get(this.translations.labels, 'confirmClearCache', 'confirmClearCache'),
138
+ confirmMessage: sc.get(this.translations.messages, 'confirmClearCacheMessage', 'confirmClearCacheMessage'),
139
+ warningText: sc.get(this.translations.labels, 'warning', 'warning'),
140
+ warningMessage: sc.get(this.translations.messages, 'clearCacheWarning', 'clearCacheWarning'),
141
+ cancelText: sc.get(this.translations.labels, 'cancel', 'cancel'),
142
+ confirmText: sc.get(this.translations.labels, 'confirm', 'confirm')
95
143
  }
96
144
  );
97
145
  }
@@ -67,17 +67,53 @@ class CacheManager
67
67
  return true;
68
68
  }
69
69
 
70
+ findAllCacheFilesForPath(domain, path)
71
+ {
72
+ let cacheInfo = this.generateCacheKey(domain, path);
73
+ if(!FileHandler.exists(cacheInfo.folderPath)){
74
+ return [];
75
+ }
76
+ let allFiles = FileHandler.readFolder(cacheInfo.folderPath);
77
+ if(0 === allFiles.length){
78
+ return [];
79
+ }
80
+ let baseFileName = cacheInfo.fileName.replace('.html', '');
81
+ let matchingFiles = [];
82
+ for(let file of allFiles){
83
+ if(file === cacheInfo.fileName){
84
+ matchingFiles.push(FileHandler.joinPaths(cacheInfo.folderPath, file));
85
+ continue;
86
+ }
87
+ if(file.startsWith(baseFileName + '_') && file.endsWith('.html')){
88
+ matchingFiles.push(FileHandler.joinPaths(cacheInfo.folderPath, file));
89
+ }
90
+ }
91
+ return matchingFiles;
92
+ }
93
+
70
94
  async delete(domain, path)
71
95
  {
72
96
  let cacheByDomains = this.fetchCachePathsByDomain(domain, path);
73
97
  for(let cacheInfo of cacheByDomains){
74
- if(!FileHandler.exists(cacheInfo.fullPath)){
75
- Logger.debug('File does not exist: '+cacheInfo.fullPath);
76
- return true;
98
+ let allCacheFiles = this.findAllCacheFilesForPath(cacheInfo.domain || domain, path);
99
+ if(0 === allCacheFiles.length){
100
+ let singleCacheInfo = this.generateCacheKey(cacheInfo.domain || domain, path);
101
+ if(!FileHandler.exists(singleCacheInfo.fullPath)){
102
+ Logger.debug('No cache files found for: '+path);
103
+ continue;
104
+ }
105
+ allCacheFiles = [singleCacheInfo.fullPath];
77
106
  }
78
- if(!FileHandler.remove(cacheInfo.fullPath)){
79
- Logger.error('Failed to delete cache file: '+cacheInfo.fullPath);
80
- return false;
107
+ for(let cacheFilePath of allCacheFiles){
108
+ if(!FileHandler.exists(cacheFilePath)){
109
+ Logger.debug('File does not exist: '+cacheFilePath);
110
+ continue;
111
+ }
112
+ if(!FileHandler.remove(cacheFilePath)){
113
+ Logger.error('Failed to delete cache file: '+cacheFilePath);
114
+ return false;
115
+ }
116
+ Logger.debug('Deleted cache file: '+cacheFilePath);
81
117
  }
82
118
  }
83
119
  return true;
@@ -87,12 +123,15 @@ class CacheManager
87
123
  {
88
124
  let cacheByDomains = [];
89
125
  if(domain && 'default' !== domain){
90
- cacheByDomains.push(this.generateCacheKey(domain, path));
126
+ cacheByDomains.push({domain: domain});
127
+ return cacheByDomains;
128
+ }
129
+ if(!FileHandler.exists(this.cacheBasePath)){
91
130
  return cacheByDomains;
92
131
  }
93
132
  let cachedDomainFolders = FileHandler.readFolder(this.cacheBasePath);
94
133
  for(let cachedDomainFolder of cachedDomainFolders) {
95
- cacheByDomains.push(this.generateCacheKey(cachedDomainFolder, path));
134
+ cacheByDomains.push({domain: cachedDomainFolder});
96
135
  }
97
136
  return cacheByDomains;
98
137
  }
@@ -18,6 +18,8 @@ class CacheRoutesHandler
18
18
  this.rootPath = sc.get(props, 'rootPath', '');
19
19
  this.cacheCleanPath = '/cache-clean';
20
20
  this.cacheCleanRoute = this.rootPath+this.cacheCleanPath;
21
+ this.clearAllCachePath = '/cache-clear-all';
22
+ this.clearAllCacheRoute = this.rootPath+this.clearAllCachePath;
21
23
  this.setupRoutes();
22
24
  }
23
25
 
@@ -42,6 +44,13 @@ class CacheRoutesHandler
42
44
  return await this.processCacheClean(req, res);
43
45
  }
44
46
  );
47
+ this.router.adminRouter.post(
48
+ this.clearAllCachePath,
49
+ this.router.isAuthenticated.bind(this.router),
50
+ async (req, res) => {
51
+ return await this.processClearAllCache(req, res);
52
+ }
53
+ );
45
54
  return true;
46
55
  }
47
56
 
@@ -71,6 +80,20 @@ class CacheRoutesHandler
71
80
  return res.redirect(this.rootPath+'/routes/view'+'?id='+routeId+'&result=success');
72
81
  }
73
82
 
83
+ async processClearAllCache(req, res)
84
+ {
85
+ if(!this.cacheManager){
86
+ return res.json({error: 'Cache manager not available'});
87
+ }
88
+ let clearResult = await this.cacheManager.clear();
89
+ if(!clearResult){
90
+ Logger.error('Failed to clear all cache');
91
+ return res.redirect(this.rootPath+'/routes?result=errorClearAllCache');
92
+ }
93
+ Logger.info('All cache cleared successfully');
94
+ return res.redirect(this.rootPath+'/routes?result=success');
95
+ }
96
+
74
97
  }
75
98
 
76
99
  module.exports.CacheRoutesHandler = CacheRoutesHandler;
@@ -76,9 +76,14 @@ class CmsPagesRouteManager
76
76
  Logger.error('Routes repository not found.');
77
77
  return;
78
78
  }
79
+ let path = sc.get(req.body, 'routePath', this.generateDefaultRoutePath(entityData));
80
+ if(!path || '' === path.trim()){
81
+ Logger.debug('No valid path available for CMS page route creation. Skipping route management.');
82
+ return;
83
+ }
79
84
  let cmsPageId = Number(entityData.id);
80
85
  let routePatchData = {
81
- path: sc.get(req.body, 'routePath', this.generateDefaultRoutePath(entityData)),
86
+ path,
82
87
  router: sc.get(req.body, 'routeRouter', 'cmsPages'),
83
88
  cache_ttl_seconds: Number(sc.get(req.body, 'routeCacheTtl', 3600)),
84
89
  enabled: Number(sc.get(req.body, 'routeEnabled', 1)),
@@ -91,9 +96,12 @@ class CmsPagesRouteManager
91
96
  if(!routeResult){
92
97
  routeResult = await routesRepository.create(routePatchData);
93
98
  if(routeResult){
94
- let pagesRepository = this.dataServer.getEntity('cms_pages');
99
+ let pagesRepository = this.dataServer.getEntity('cmsPages');
95
100
  if(pagesRepository){
96
- await pagesRepository.updateById(cmsPageId, {route_id: routeResult.id});
101
+ let pageResult = await pagesRepository.updateById(cmsPageId, {route_id: routeResult.id});
102
+ if(!pageResult){
103
+ Logger.error('Page could not be updated with route ID.', routeResult);
104
+ }
97
105
  }
98
106
  }
99
107
  }
@@ -105,7 +113,11 @@ class CmsPagesRouteManager
105
113
 
106
114
  generateDefaultRoutePath(pageData)
107
115
  {
108
- return '/' + sc.get(pageData, 'title', 'page').toLowerCase()
116
+ let title = sc.get(pageData, 'title', '');
117
+ if(!title || '' === title.trim()){
118
+ return '';
119
+ }
120
+ return '/' + title.toLowerCase()
109
121
  .replace(/[^a-z0-9\s-]/g, '')
110
122
  .replace(/\s+/g, '-')
111
123
  .replace(/-+/g, '-')