@reldens/cms 0.19.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.
- package/README.md +399 -17
- package/admin/reldens-admin-client.css +156 -99
- package/admin/reldens-admin-client.js +108 -133
- package/admin/templates/clear-all-cache-button.html +7 -7
- package/admin/templates/edit.html +7 -0
- package/admin/templates/layout.html +15 -9
- package/admin/templates/list-content.html +4 -2
- package/admin/templates/list.html +24 -8
- package/admin/templates/view.html +21 -0
- package/lib/admin-manager/admin-filters-manager.js +177 -0
- package/lib/admin-manager/contents-builder.js +1 -0
- package/lib/admin-manager/default-translations.js +38 -0
- package/lib/admin-manager/router-contents.js +50 -45
- package/lib/admin-manager/router.js +19 -0
- package/lib/frontend/content-renderer.js +178 -0
- package/lib/frontend/entity-access-manager.js +63 -0
- package/lib/frontend/request-processor.js +128 -0
- package/lib/frontend/response-manager.js +54 -0
- package/lib/frontend/template-cache.js +102 -0
- package/lib/frontend/template-resolver.js +111 -0
- package/lib/frontend.js +111 -538
- package/lib/manager.js +26 -12
- package/lib/search-renderer.js +15 -7
- package/lib/search-request-handler.js +67 -0
- package/lib/search.js +13 -1
- package/lib/template-engine/asset-transformer.js +41 -0
- package/lib/template-engine/collections-single-transformer.js +28 -5
- package/lib/template-engine/collections-transformer.js +66 -32
- package/lib/template-engine/date-transformer.js +53 -0
- package/lib/template-engine/entities-transformer.js +5 -2
- package/lib/template-engine/partials-transformer.js +8 -5
- package/lib/template-engine/system-variables-provider.js +108 -0
- package/lib/template-engine/translate-transformer.js +98 -0
- package/lib/template-engine/translation-service.js +104 -0
- package/lib/template-engine/url-transformer.js +41 -0
- package/lib/template-engine.js +99 -12
- package/lib/template-reloader.js +307 -0
- package/package.json +4 -4
- package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
- package/templates/assets/favicons/default/favicon.ico +0 -0
- package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
- package/templates/js/functions.js +144 -0
- package/templates/js/scripts.js +5 -0
- package/templates/page.html +11 -5
- package/templates/partials/pagedCollection.html +1 -1
- package/lib/admin-translations.js +0 -56
- package/templates/favicon.ico +0 -0
- /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
- /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
- /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
- /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
- /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
- /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
- /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
- /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
|
|
40
|
-
let
|
|
41
|
-
let
|
|
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:
|
|
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,
|
|
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 = (
|
|
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
|
-
|
|
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;
|