@reldens/cms 0.20.0 → 0.21.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 (50) hide show
  1. package/README.md +236 -11
  2. package/admin/reldens-admin-client.css +75 -21
  3. package/admin/reldens-admin-client.js +108 -133
  4. package/admin/templates/clear-all-cache-button.html +7 -7
  5. package/admin/templates/edit.html +7 -0
  6. package/admin/templates/layout.html +15 -9
  7. package/admin/templates/list-content.html +4 -2
  8. package/admin/templates/list.html +24 -8
  9. package/admin/templates/view.html +21 -0
  10. package/lib/admin-manager/admin-filters-manager.js +177 -0
  11. package/lib/admin-manager/contents-builder.js +1 -0
  12. package/lib/admin-manager/default-translations.js +38 -0
  13. package/lib/admin-manager/router-contents.js +50 -45
  14. package/lib/admin-manager/router.js +19 -0
  15. package/lib/frontend/content-renderer.js +178 -0
  16. package/lib/frontend/entity-access-manager.js +63 -0
  17. package/lib/frontend/request-processor.js +128 -0
  18. package/lib/frontend/response-manager.js +54 -0
  19. package/lib/frontend/template-cache.js +102 -0
  20. package/lib/frontend/template-resolver.js +111 -0
  21. package/lib/frontend.js +89 -630
  22. package/lib/manager.js +25 -12
  23. package/lib/search-renderer.js +15 -7
  24. package/lib/search-request-handler.js +67 -0
  25. package/lib/search.js +13 -1
  26. package/lib/template-engine/collections-single-transformer.js +11 -5
  27. package/lib/template-engine/collections-transformer.js +47 -34
  28. package/lib/template-engine/entities-transformer.js +3 -2
  29. package/lib/template-engine/partials-transformer.js +5 -6
  30. package/lib/template-engine/system-variables-provider.js +4 -1
  31. package/lib/template-engine.js +11 -5
  32. package/lib/template-reloader.js +307 -0
  33. package/package.json +4 -4
  34. package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
  35. package/templates/assets/favicons/default/favicon.ico +0 -0
  36. package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
  37. package/templates/js/functions.js +144 -0
  38. package/templates/js/scripts.js +5 -0
  39. package/templates/page.html +11 -5
  40. package/templates/partials/pagedCollection.html +1 -1
  41. package/lib/admin-translations.js +0 -56
  42. package/templates/favicon.ico +0 -0
  43. /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
  44. /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
  45. /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
  46. /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
  47. /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
  48. /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
  49. /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
  50. /package/templates/assets/favicons/{safari-pinned-tab.svg → default/safari-pinned-tab.svg} +0 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ *
3
+ * Reldens - DefaultTranslations
4
+ *
5
+ */
6
+
7
+ module.exports.DefaultTranslations = {
8
+ messages: {
9
+ loginWelcome: 'CMS - Login',
10
+ reldensTitle: 'Reldens - CMS',
11
+ reldensSlogan: 'You can do it',
12
+ reldensDiscordTitle: 'Join our Discord server!',
13
+ reldensDiscordText: 'Talk with the creators and other Reldens users',
14
+ reldensGithubTitle: 'Find us on GitHub!',
15
+ reldensGithubText: 'Need a new feature?'
16
+ +' Would you like to contribute with code?'
17
+ +' Find the source code or create an issue in GitHub',
18
+ reldensLoading: 'Loading...',
19
+ confirmClearCacheMessage: 'Are you sure you want to clear all cache? This action cannot be undone.',
20
+ clearCacheWarning: 'This will clear all cached routes and may impact site performance temporarily.'
21
+ },
22
+ labels: {
23
+ navigation: 'Reldens - CMS',
24
+ adminVersion: 'Admin: {{version}}',
25
+ loginWelcome: 'Reldens',
26
+ pages: 'Server Management',
27
+ management: 'Management',
28
+ shuttingDown: 'Server is shutting down in:',
29
+ submitShutdownLabel: 'Shutdown Server',
30
+ submitCancelLabel: 'Cancel Server Shutdown',
31
+ cleanCache: 'Clean Cache',
32
+ clearAllCache: 'Clear All Cache',
33
+ confirmClearCache: 'Confirm Clear Cache',
34
+ warning: 'Warning:',
35
+ cancel: 'Cancel',
36
+ confirm: 'Continue',
37
+ }
38
+ };
@@ -6,6 +6,7 @@
6
6
 
7
7
  const { PageRangeProvider, Logger, sc } = require('@reldens/utils');
8
8
  const { FileHandler } = require('@reldens/server-utils');
9
+ const { AdminFiltersManager } = require('./admin-filters-manager');
9
10
 
10
11
  class RouterContents
11
12
  {
@@ -30,29 +31,64 @@ class RouterContents
30
31
  this.fetchTranslation = props.fetchTranslation;
31
32
  this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
32
33
  this.fetchUploadProperties = props.fetchUploadProperties;
34
+ this.filtersManager = new AdminFiltersManager();
33
35
  }
34
36
 
35
37
  async generateListRouteContent(req, driverResource, entityPath)
36
38
  {
37
39
  let currentPage = Number(req?.query?.page || 1);
38
40
  let pageSize = Number(req?.query?.pageSize || 25);
39
- let filtersFromParams = req?.body?.filters || {};
40
- let filters = this.prepareFilters(filtersFromParams, driverResource);
41
- let totalEntities = await this.countTotalEntities(driverResource, filters);
41
+ let shouldClearFilters = 'true' === req?.query?.clearFilters;
42
+ let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
43
+ let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
44
+ let entityFilterTerm = shouldClearFilters ? '' : req?.body?.entityFilterTerm || req?.query?.entityFilterTerm || '';
45
+ if(shouldClearFilters){
46
+ this.filtersManager.clearFiltersFromSession(req, entityPath);
47
+ }
48
+ let hasNewEntityFilterTerm = entityFilterTerm && '' !== entityFilterTerm;
49
+ let hasNewToggleFilters = Object.keys(filtersFromParams).length > 0;
50
+ if(hasNewEntityFilterTerm && hasNewToggleFilters){
51
+ filtersFromParams = {};
52
+ hasNewToggleFilters = false;
53
+ }
54
+ if(hasNewEntityFilterTerm || hasNewToggleFilters){
55
+ let filtersToSave = {
56
+ regular: hasNewToggleFilters ? filtersFromParams : {},
57
+ entityFilterTerm: hasNewEntityFilterTerm ? entityFilterTerm : ''
58
+ };
59
+ this.filtersManager.saveFiltersToSession(req, entityPath, filtersToSave);
60
+ sessionFilters = filtersToSave;
61
+ }
62
+ let mergedFilters = shouldClearFilters ? {} : Object.assign({}, sessionFilters.regular || {});
63
+ let finalEntityFilterTerm = shouldClearFilters ? '' : sessionFilters.entityFilterTerm || '';
64
+ let filters = this.filtersManager.prepareFilters(mergedFilters, driverResource);
65
+ let textFilters = this.filtersManager.prepareTextFilters(finalEntityFilterTerm, driverResource);
66
+ let combinedFilters = this.filtersManager.combineFilters(filters, textFilters);
67
+ let totalEntities = await this.countTotalEntities(driverResource, combinedFilters);
42
68
  let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
43
69
  let renderedPagination = '';
44
70
  for(let paginationItem of PageRangeProvider.fetch(currentPage, totalPages)){
71
+ let paginationUrl = this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value;
72
+ if(finalEntityFilterTerm){
73
+ paginationUrl += '&entityFilterTerm='+encodeURIComponent(finalEntityFilterTerm);
74
+ }
75
+ for(let filterKey of Object.keys(mergedFilters)){
76
+ if(mergedFilters[filterKey]){
77
+ paginationUrl += '&filters['+filterKey+']='+encodeURIComponent(mergedFilters[filterKey]);
78
+ }
79
+ }
45
80
  renderedPagination += await this.adminContentsRender(
46
81
  this.adminFilesContents.fields.view['link'],
47
82
  {
48
83
  fieldName: paginationItem.label,
49
- fieldValue: this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value,
84
+ fieldValue: paginationUrl,
50
85
  fieldOriginalValue: paginationItem.value,
51
86
  }
52
87
  );
53
88
  }
54
89
  let listProperties = {
55
- entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath
90
+ entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
91
+ entityFilterTermValue: finalEntityFilterTerm || ''
56
92
  };
57
93
  await this.emitEvent('reldens.adminListPropertiesPopulation', {
58
94
  req,
@@ -73,12 +109,13 @@ class RouterContents
73
109
  );
74
110
  return {name: property, value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle};
75
111
  }),
76
- rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, filters)
112
+ rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, combinedFilters)
77
113
  }),
78
114
  pagination: renderedPagination,
79
- extraContentForList: sc.get(listProperties, 'extraContentForList', '')
115
+ extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
116
+ entityFilterTermValue: listProperties.entityFilterTermValue
80
117
  }, ...driverResource.options.filterProperties.map((property) => {
81
- let filterValue = (filtersFromParams[property] || '');
118
+ let filterValue = (mergedFilters[property] || '');
82
119
  return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
83
120
  }))
84
121
  ),
@@ -237,9 +274,13 @@ class RouterContents
237
274
  }
238
275
  }
239
276
  }
240
- if('saveAndContinue' === sc.get(req.body, 'saveAction', 'save')){
277
+ let saveAction = sc.get(req.body, 'saveAction', 'save');
278
+ if('saveAndContinue' === saveAction){
241
279
  return this.generateEntityRoute('editPath', driverResource, idProperty, saveResult) +'&result=success';
242
280
  }
281
+ if('saveAndGoBack' === saveAction){
282
+ return this.rootPath+'/'+entityPath+'?result=success';
283
+ }
243
284
  return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
244
285
  } catch (error) {
245
286
  Logger.error('Save entity error.', error);
@@ -577,42 +618,6 @@ class RouterContents
577
618
  return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
578
619
  }
579
620
 
580
- prepareFilters(filtersList, driverResource)
581
- {
582
- if(0 === Object.keys(filtersList).length){
583
- return {};
584
- }
585
- let filters = {};
586
- for(let i of Object.keys(filtersList)){
587
- let filter = filtersList[i];
588
- if('' === filter || null === filter || undefined === filter){
589
- continue;
590
- }
591
- let rawConfigFilterProperties = driverResource.options.properties[i];
592
- if(!rawConfigFilterProperties){
593
- Logger.critical('Could not found property by key.', i);
594
- continue;
595
- }
596
- if(rawConfigFilterProperties.isUpload){
597
- continue;
598
- }
599
- if('reference' === rawConfigFilterProperties.type){
600
- filters[i] = Number(filter);
601
- continue;
602
- }
603
- if('boolean' === rawConfigFilterProperties.type){
604
- filters[i] = ('true' === filter || '1' === filter || 1 === filter);
605
- continue;
606
- }
607
- if('number' === rawConfigFilterProperties.type || rawConfigFilterProperties.isId){
608
- filters[i] = Number(filter);
609
- continue;
610
- }
611
- filters[i] = {operator: 'LIKE', value: '%'+filter+'%'};
612
- }
613
- return filters;
614
- }
615
-
616
621
  }
617
622
 
618
623
  module.exports.RouterContents = RouterContents;
@@ -37,6 +37,7 @@ class Router
37
37
  this.generateEditRouteContent = props.generateEditRouteContent;
38
38
  this.processDeleteEntities = props.processDeleteEntities;
39
39
  this.processSaveEntity = props.processSaveEntity;
40
+ this.checkAndReloadAdminTemplates = props.checkAndReloadAdminTemplates;
40
41
  this.setupAdminRouter();
41
42
  }
42
43
 
@@ -61,12 +62,22 @@ class Router
61
62
  return true;
62
63
  }
63
64
 
65
+ async reloadTemplatesIfNeeded()
66
+ {
67
+ if(!this.checkAndReloadAdminTemplates){
68
+ return false;
69
+ }
70
+ return await this.checkAndReloadAdminTemplates();
71
+ }
72
+
64
73
  setupAdminRoutes()
65
74
  {
66
75
  this.adminRouter.get(this.loginPath, async (req, res) => {
76
+ await this.reloadTemplatesIfNeeded();
67
77
  return res.send(this.adminContents().login);
68
78
  });
69
79
  this.adminRouter.post(this.loginPath, async (req, res) => {
80
+ //await this.reloadTemplatesIfNeeded();
70
81
  let { email, password } = req.body;
71
82
  let loginResult = await this.authenticationCallback(email, password, this.adminRoleId);
72
83
  if(loginResult){
@@ -76,6 +87,7 @@ class Router
76
87
  return res.redirect(this.rootPath+this.loginPath+'?login-error=true');
77
88
  });
78
89
  this.adminRouter.get('/', this.isAuthenticated.bind(this), async (req, res) => {
90
+ await this.reloadTemplatesIfNeeded();
79
91
  return res.send(this.adminContents().dashboard);
80
92
  });
81
93
  this.adminRouter.get(this.logoutPath, (req, res) => {
@@ -95,12 +107,15 @@ class Router
95
107
  let entityPath = driverResource.entityPath;
96
108
  let entityRoute = '/'+entityPath;
97
109
  this.adminRouter.get(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
110
+ await this.reloadTemplatesIfNeeded();
98
111
  return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
99
112
  });
100
113
  this.adminRouter.post(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
114
+ await this.reloadTemplatesIfNeeded();
101
115
  return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
102
116
  });
103
117
  this.adminRouter.get(entityRoute+this.viewPath, this.isAuthenticated.bind(this), async (req, res) => {
118
+ await this.reloadTemplatesIfNeeded();
104
119
  let routeContents = await this.generateViewRouteContent(req, driverResource, entityPath);
105
120
  if('' === routeContents){
106
121
  return res.redirect(this.rootPath+'/'+entityPath+'?result=errorView');
@@ -108,6 +123,7 @@ class Router
108
123
  return res.send(routeContents);
109
124
  });
110
125
  this.adminRouter.get(entityRoute+this.editPath, this.isAuthenticated.bind(this), async (req, res) => {
126
+ await this.reloadTemplatesIfNeeded();
111
127
  await this.emitEvent('reldens.adminBeforeEntityEdit', {
112
128
  req,
113
129
  res,
@@ -122,6 +138,7 @@ class Router
122
138
  });
123
139
  this.setupSavePath(entityRoute, driverResource, entityPath);
124
140
  this.adminRouter.post(entityRoute+this.deletePath, this.isAuthenticated.bind(this), async (req, res) => {
141
+ //await this.reloadTemplatesIfNeeded();
125
142
  return res.redirect(await this.processDeleteEntities(req, res, driverResource, entityPath));
126
143
  });
127
144
  await this.emitEvent('reldens.setupEntitiesRoutes', {
@@ -140,6 +157,7 @@ class Router
140
157
  entityRoute+this.savePath,
141
158
  this.isAuthenticated.bind(this),
142
159
  async (req, res) => {
160
+ //await this.reloadTemplatesIfNeeded();
143
161
  await this.emitEvent('reldens.adminBeforeEntitySave', {
144
162
  req,
145
163
  res,
@@ -168,6 +186,7 @@ class Router
168
186
  this.isAuthenticated.bind(this),
169
187
  this.uploaderFactory.createUploader(fields, this.buckets, allowedFileTypes),
170
188
  async (req, res) => {
189
+ //await this.reloadTemplatesIfNeeded();
171
190
  await this.emitEvent('reldens.adminBeforeEntitySave', {
172
191
  req,
173
192
  res,
@@ -0,0 +1,178 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - Frontend - ContentRenderer
4
+ *
5
+ */
6
+
7
+ const { FileHandler } = require('@reldens/server-utils');
8
+ const { Logger, sc } = require('@reldens/utils');
9
+
10
+ class ContentRenderer
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ this.dataServer = sc.get(props, 'dataServer', false);
16
+ this.templateEngine = sc.get(props, 'templateEngine', false);
17
+ this.templateResolver = sc.get(props, 'templateResolver', false);
18
+ this.templateCache = sc.get(props, 'templateCache', false);
19
+ this.metaDefaults = sc.get(props, 'metaDefaults', {});
20
+ }
21
+
22
+ fetchMetaFields(data)
23
+ {
24
+ if(!sc.isObject(data) || 0 === Object.keys(data).length){
25
+ return this.metaDefaults;
26
+ }
27
+ let result = Object.assign({}, this.metaDefaults);
28
+ for(let key of Object.keys(data)){
29
+ let value = data[key];
30
+ if(null !== value && '' !== value && 'undefined' !== typeof value){
31
+ result[key] = value;
32
+ }
33
+ }
34
+ let titleValue = sc.get(result, 'title', '');
35
+ let metaTitleValue = sc.get(result, 'meta_title', titleValue);
36
+ if(metaTitleValue && '' !== metaTitleValue){
37
+ result.meta_title = metaTitleValue;
38
+ }
39
+ let metaOgTitleValue = sc.get(result, 'meta_og_title', metaTitleValue);
40
+ if(metaOgTitleValue && '' !== metaOgTitleValue){
41
+ result.meta_og_title = metaOgTitleValue;
42
+ }
43
+ let metaDescValue = sc.get(result, 'meta_description', sc.get(result, 'description', ''));
44
+ if(metaDescValue && '' !== metaDescValue){
45
+ result.meta_description = metaDescValue;
46
+ }
47
+ let metaOgDescValue = sc.get(result, 'meta_og_description', metaDescValue);
48
+ if(metaOgDescValue && '' !== metaOgDescValue){
49
+ result.meta_og_description = metaOgDescValue;
50
+ }
51
+ let jsonData = sc.get(result, 'json_data', null);
52
+ if(sc.isString(jsonData)){
53
+ jsonData = sc.toJson(jsonData, {});
54
+ }
55
+ if(!sc.isObject(jsonData)){
56
+ jsonData = {};
57
+ }
58
+ let viewportValue = sc.get(jsonData, 'viewport', this.metaDefaults.viewport);
59
+ if(viewportValue && '' !== viewportValue){
60
+ jsonData.viewport = viewportValue;
61
+ }
62
+ result.json_data = jsonData;
63
+ return result;
64
+ }
65
+
66
+ async generateRouteContent(route, domain, req)
67
+ {
68
+ if(!route.router){
69
+ return false;
70
+ }
71
+ let entity = this.dataServer.getEntity(route.router);
72
+ if(!entity){
73
+ return false;
74
+ }
75
+ let content = await entity.loadOneWithRelations({route_id: route.id});
76
+ if(!content){
77
+ return false;
78
+ }
79
+ return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain, req, route);
80
+ }
81
+
82
+ async generateTemplateContent(templatePath, domain, req, data = {})
83
+ {
84
+ let template = FileHandler.readFile(templatePath);
85
+ if(!template){
86
+ Logger.error('Failed to read template: ' + templatePath);
87
+ return false;
88
+ }
89
+ return await this.templateEngine.render(
90
+ template,
91
+ data,
92
+ this.templateCache.getPartialsForDomain(domain),
93
+ domain,
94
+ req,
95
+ null,
96
+ null
97
+ );
98
+ }
99
+
100
+ async renderWithTemplateContent(content, data, domain, req, route)
101
+ {
102
+ let templateName = sc.get(content, 'template', 'page');
103
+ if(!templateName){
104
+ templateName = 'page';
105
+ }
106
+ let layoutName = sc.get(content, 'layout', '');
107
+ if(!layoutName){
108
+ layoutName = 'default';
109
+ }
110
+ let currentEntityData = Object.assign({}, content, data);
111
+ let layoutContent = await this.processContentWithLayout(
112
+ content,
113
+ data,
114
+ layoutName,
115
+ domain,
116
+ req,
117
+ route,
118
+ currentEntityData
119
+ );
120
+ let templatePath = this.templateResolver.findTemplatePath(templateName, domain);
121
+ if(!templatePath){
122
+ return layoutContent;
123
+ }
124
+ let routerKey = route?.router;
125
+ let categoryName = currentEntityData?.cms_categories?.name;
126
+ let siteHandle = this.templateResolver.resolveDomainToSiteKey(domain)
127
+ + (routerKey ? ' '+routerKey : '')
128
+ + (categoryName ? ' cat-'+categoryName : '');
129
+ return await this.generateTemplateContent(
130
+ templatePath,
131
+ domain,
132
+ req,
133
+ Object.assign(
134
+ {},
135
+ this.fetchMetaFields(data),
136
+ {
137
+ content: layoutContent,
138
+ siteHandle
139
+ }
140
+ )
141
+ );
142
+ }
143
+
144
+ async processContentWithLayout(content, data, layoutName, domain, req, route, currentEntityData)
145
+ {
146
+ let processedContent = await this.processContent(content, data, domain, req, route, currentEntityData);
147
+ let layoutPath = this.templateResolver.findLayoutPath(layoutName, domain);
148
+ if(!layoutPath){
149
+ return processedContent;
150
+ }
151
+ return await this.generateTemplateContent(
152
+ layoutPath,
153
+ domain,
154
+ req,
155
+ Object.assign({}, data, {content: processedContent})
156
+ );
157
+ }
158
+
159
+ async processContent(content, data, domain, req, route, currentEntityData)
160
+ {
161
+ let contentText = sc.get(content, 'content', '');
162
+ if(!contentText){
163
+ return '';
164
+ }
165
+ return await this.templateEngine.render(
166
+ contentText,
167
+ data,
168
+ this.templateCache.getPartialsForDomain(domain),
169
+ domain,
170
+ req,
171
+ route,
172
+ currentEntityData
173
+ );
174
+ }
175
+
176
+ }
177
+
178
+ module.exports.ContentRenderer = ContentRenderer;
@@ -0,0 +1,63 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - Frontend - EntityAccessManager
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+
9
+ class EntityAccessManager
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.dataServer = sc.get(props, 'dataServer', false);
15
+ this.entityAccessCache = new Map();
16
+ }
17
+
18
+ async loadEntityAccessRules()
19
+ {
20
+ let accessEntity = this.dataServer.getEntity('entitiesAccess');
21
+ if(!accessEntity){
22
+ Logger.warning('Entities Access not found.');
23
+ return;
24
+ }
25
+ let accessRules = await accessEntity.loadAll();
26
+ for(let rule of accessRules){
27
+ this.entityAccessCache.set(rule.entity_name, rule.is_public);
28
+ }
29
+ }
30
+
31
+ async isEntityAccessible(entityName)
32
+ {
33
+ if(this.entityAccessCache.has(entityName)){
34
+ return this.entityAccessCache.get(entityName);
35
+ }
36
+ return false;
37
+ }
38
+
39
+ async findEntityByPath(path)
40
+ {
41
+ let pathSegments = path.split('/').filter(segment => '' !== segment);
42
+ if(2 > pathSegments.length){
43
+ return false;
44
+ }
45
+ let entityName = pathSegments[0];
46
+ if(!await this.isEntityAccessible(entityName)){
47
+ return false;
48
+ }
49
+ let entityId = pathSegments[1];
50
+ let entity = this.dataServer.getEntity(entityName);
51
+ if(!entity){
52
+ return false;
53
+ }
54
+ let loadedEntity = await entity.loadByIdWithRelations(entityId);
55
+ if(!loadedEntity){
56
+ return false;
57
+ }
58
+ return {entity: loadedEntity, entityName};
59
+ }
60
+
61
+ }
62
+
63
+ module.exports.EntityAccessManager = EntityAccessManager;
@@ -0,0 +1,128 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - Frontend - RequestProcessor
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+
9
+ class RequestProcessor
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.dataServer = sc.get(props, 'dataServer', false);
15
+ this.templateResolver = sc.get(props, 'templateResolver', false);
16
+ this.domainMapping = sc.get(props, 'domainMapping', {});
17
+ }
18
+
19
+ getDomainFromRequest(req)
20
+ {
21
+ let host = req.get('host');
22
+ if(!host){
23
+ return false;
24
+ }
25
+ return host.split(':')[0];
26
+ }
27
+
28
+ resolveDomainMapping(domain)
29
+ {
30
+ if(!domain){
31
+ return domain;
32
+ }
33
+ return sc.get(this.domainMapping, domain, domain);
34
+ }
35
+
36
+ normalizePathForRouteSearch(path)
37
+ {
38
+ if(!path || '/' === path){
39
+ return '/';
40
+ }
41
+ return path.endsWith('/') ? path.slice(0, -1) : path;
42
+ }
43
+
44
+ async handleRouteRedirect(route, res)
45
+ {
46
+ let redirectUrl = sc.get(route, 'redirect_url', null);
47
+ if(!redirectUrl){
48
+ return false;
49
+ }
50
+ let redirectType = sc.get(route, 'redirect_type', '301');
51
+ let statusCode = '301' === redirectType ? 301 : 302;
52
+ res.redirect(statusCode, redirectUrl);
53
+ return true;
54
+ }
55
+
56
+ async findRouteByPath(path, domain)
57
+ {
58
+ let routesEntity = this.dataServer.getEntity('routes');
59
+ if(!routesEntity){
60
+ Logger.error('Routes entity not found in dataServer.');
61
+ return false;
62
+ }
63
+ let normalizedPath = this.normalizePathForRouteSearch(path);
64
+ let resolvedDomain = this.resolveDomainMapping(domain);
65
+ let domainFilter = resolvedDomain || null;
66
+ let routeFilters = {path: normalizedPath, enabled: 1};
67
+ let routes = await routesEntity.load(routeFilters);
68
+ let matchingRoute = false;
69
+ let nullDomain = false;
70
+ for(let route of routes){
71
+ if(route.domain === domainFilter){
72
+ matchingRoute = route;
73
+ break;
74
+ }
75
+ if(!route.domain){
76
+ nullDomain = route;
77
+ }
78
+ }
79
+ if(matchingRoute){
80
+ return matchingRoute;
81
+ }
82
+ if(nullDomain){
83
+ return nullDomain;
84
+ }
85
+ if(normalizedPath !== path){
86
+ let routeFiltersWithSlash = {path: path, enabled: 1};
87
+ let routesWithSlash = await routesEntity.load(routeFiltersWithSlash);
88
+ for(let route of routesWithSlash){
89
+ if(route.domain === domainFilter){
90
+ return route;
91
+ }
92
+ if(!route.domain){
93
+ nullDomain = route;
94
+ }
95
+ }
96
+ if(nullDomain){
97
+ return nullDomain;
98
+ }
99
+ }
100
+ return false;
101
+ }
102
+
103
+ buildCacheKey(path, req)
104
+ {
105
+ if(!req || !req.query){
106
+ return path;
107
+ }
108
+ for(let key of Object.keys(req.query)){
109
+ if(key.endsWith('-key')){
110
+ let queryString = '';
111
+ for(let qKey of Object.keys(req.query)){
112
+ queryString += (queryString ? '&' : '') + qKey + '=' + req.query[qKey];
113
+ }
114
+ let hash = 0;
115
+ for(let i = 0; i < queryString.length; i++){
116
+ let char = queryString.charCodeAt(i);
117
+ hash = ((hash << 5) - hash) + char;
118
+ hash = hash & hash;
119
+ }
120
+ return path + '_' + Math.abs(hash);
121
+ }
122
+ }
123
+ return path;
124
+ }
125
+
126
+ }
127
+
128
+ module.exports.RequestProcessor = RequestProcessor;