@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
package/lib/frontend.js
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
const { TemplateEngine } = require('./template-engine');
|
|
8
8
|
const { Search } = require('./search');
|
|
9
9
|
const { SearchRenderer } = require('./search-renderer');
|
|
10
|
+
const { SearchRequestHandler } = require('./search-request-handler');
|
|
11
|
+
const { TemplateResolver } = require('./frontend/template-resolver');
|
|
12
|
+
const { TemplateCache } = require('./frontend/template-cache');
|
|
13
|
+
const { RequestProcessor } = require('./frontend/request-processor');
|
|
14
|
+
const { EntityAccessManager } = require('./frontend/entity-access-manager');
|
|
15
|
+
const { ContentRenderer } = require('./frontend/content-renderer');
|
|
16
|
+
const { ResponseManager } = require('./frontend/response-manager');
|
|
10
17
|
const { FileHandler } = require('@reldens/server-utils');
|
|
11
18
|
const { Logger, sc } = require('@reldens/utils');
|
|
12
19
|
|
|
@@ -19,6 +26,7 @@ class Frontend
|
|
|
19
26
|
this.appServerFactory = sc.get(props, 'appServerFactory', false);
|
|
20
27
|
this.dataServer = sc.get(props, 'dataServer', false);
|
|
21
28
|
this.renderEngine = sc.get(props, 'renderEngine', false);
|
|
29
|
+
this.events = sc.get(props, 'events', false);
|
|
22
30
|
this.projectRoot = sc.get(props, 'projectRoot', './');
|
|
23
31
|
this.templatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
|
|
24
32
|
this.publicPath = FileHandler.joinPaths(this.projectRoot, 'public');
|
|
@@ -26,13 +34,10 @@ class Frontend
|
|
|
26
34
|
this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
|
|
27
35
|
this.domainMapping = sc.get(props, 'domainMapping', {});
|
|
28
36
|
this.siteKeyMapping = sc.get(props, 'siteKeyMapping', {});
|
|
29
|
-
this.partialsCache = {};
|
|
30
|
-
this.domainPartialsCache = new Map();
|
|
31
|
-
this.domainTemplatesMap = new Map();
|
|
32
|
-
this.entityAccessCache = new Map();
|
|
33
37
|
this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
|
|
34
38
|
this.templateEngine = false;
|
|
35
39
|
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
40
|
+
this.handleFrontendTemplateReload = sc.get(props, 'handleFrontendTemplateReload', false);
|
|
36
41
|
this.searchPath = sc.get(props, 'searchPath', '/search');
|
|
37
42
|
this.searchSets = sc.get(props, 'searchSets', false);
|
|
38
43
|
this.searchConfig = {dataServer: this.dataServer};
|
|
@@ -40,10 +45,6 @@ class Frontend
|
|
|
40
45
|
this.searchConfig.searchSets = this.searchSets;
|
|
41
46
|
}
|
|
42
47
|
this.search = new Search(this.searchConfig);
|
|
43
|
-
this.searchRenderer = new SearchRenderer({
|
|
44
|
-
renderEngine: this.renderEngine,
|
|
45
|
-
getPartials: this.getPartialsForDomain.bind(this)
|
|
46
|
-
});
|
|
47
48
|
this.metaDefaults = sc.get(props, 'metaDefaults', {
|
|
48
49
|
locale: 'en',
|
|
49
50
|
viewport: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover',
|
|
@@ -51,6 +52,17 @@ class Frontend
|
|
|
51
52
|
meta_theme_color: '#000000',
|
|
52
53
|
meta_twitter_card_type: 'summary'
|
|
53
54
|
});
|
|
55
|
+
this.templateResolver = new TemplateResolver(this);
|
|
56
|
+
this.templateCache = new TemplateCache(this);
|
|
57
|
+
this.requestProcessor = new RequestProcessor(this);
|
|
58
|
+
this.entityAccessManager = new EntityAccessManager(this);
|
|
59
|
+
this.contentRenderer = new ContentRenderer(this);
|
|
60
|
+
this.responseManager = new ResponseManager(this);
|
|
61
|
+
this.searchRenderer = new SearchRenderer({
|
|
62
|
+
renderEngine: this.renderEngine,
|
|
63
|
+
getPartials: this.templateCache.getPartialsForDomain.bind(this.templateCache)
|
|
64
|
+
});
|
|
65
|
+
this.searchRequestHandler = new SearchRequestHandler(this);
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
async initialize()
|
|
@@ -75,19 +87,25 @@ class Frontend
|
|
|
75
87
|
Logger.error('Public folder not found: '+this.publicPath);
|
|
76
88
|
return false;
|
|
77
89
|
}
|
|
90
|
+
this.templateResolver.domainTemplatesMap = this.templateCache.getDomainTemplatesMap();
|
|
78
91
|
this.templateEngine = new TemplateEngine({
|
|
79
92
|
renderEngine: this.renderEngine,
|
|
80
93
|
dataServer: this.dataServer,
|
|
81
|
-
getPartials: this.getPartialsForDomain.bind(this),
|
|
82
|
-
entitiesConfig: this.entitiesConfig
|
|
94
|
+
getPartials: this.templateCache.getPartialsForDomain.bind(this.templateCache),
|
|
95
|
+
entitiesConfig: this.entitiesConfig,
|
|
96
|
+
events: this.events,
|
|
97
|
+
defaultDomain: this.defaultDomain,
|
|
98
|
+
projectRoot: this.projectRoot,
|
|
99
|
+
publicPath: this.publicPath
|
|
83
100
|
});
|
|
101
|
+
this.contentRenderer.templateEngine = this.templateEngine;
|
|
84
102
|
this.searchConfig.jsonFieldsParser = this.templateEngine.jsonFieldsParser;
|
|
85
|
-
await this.loadPartials();
|
|
86
|
-
await this.setupDomainTemplates();
|
|
87
|
-
await this.loadEntityAccessRules();
|
|
103
|
+
await this.templateCache.loadPartials();
|
|
104
|
+
await this.templateCache.setupDomainTemplates();
|
|
105
|
+
await this.entityAccessManager.loadEntityAccessRules();
|
|
88
106
|
this.setupStaticAssets();
|
|
89
107
|
this.app.get(this.searchPath, async (req, res) => {
|
|
90
|
-
return await this.handleSearchRequest(req, res);
|
|
108
|
+
return await this.searchRequestHandler.handleSearchRequest(req, res);
|
|
91
109
|
});
|
|
92
110
|
this.app.get('*', async (req, res) => {
|
|
93
111
|
return await this.handleRequest(req, res);
|
|
@@ -95,565 +113,120 @@ class Frontend
|
|
|
95
113
|
return true;
|
|
96
114
|
}
|
|
97
115
|
|
|
98
|
-
async loadEntityAccessRules()
|
|
99
|
-
{
|
|
100
|
-
let accessEntity = this.dataServer.getEntity('entitiesAccess');
|
|
101
|
-
if(!accessEntity){
|
|
102
|
-
Logger.warning('Entities Access not found.');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
let accessRules = await accessEntity.loadAll();
|
|
106
|
-
for(let rule of accessRules){
|
|
107
|
-
this.entityAccessCache.set(rule.entity_name, rule.is_public);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async loadPartials()
|
|
112
|
-
{
|
|
113
|
-
let partialsPath = FileHandler.joinPaths(this.templatesPath, 'partials');
|
|
114
|
-
FileHandler.createFolder(partialsPath);
|
|
115
|
-
let partialFiles = FileHandler.getFilesInFolder(partialsPath, this.templateExtensions);
|
|
116
|
-
for(let file of partialFiles){
|
|
117
|
-
let partialName = this.extractTemplateName(file);
|
|
118
|
-
if(!partialName){
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
let partialPath = FileHandler.joinPaths(partialsPath, file);
|
|
122
|
-
let partialContent = FileHandler.readFile(partialPath);
|
|
123
|
-
if(!partialContent){
|
|
124
|
-
Logger.error('Failed to read partial: '+partialPath);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
this.partialsCache[partialName] = partialContent;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async loadDomainPartials(domain, domainPath)
|
|
132
|
-
{
|
|
133
|
-
let domainPartialsPath = FileHandler.joinPaths(domainPath, 'partials');
|
|
134
|
-
if(!FileHandler.exists(domainPartialsPath)){
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
let domainPartials = {};
|
|
138
|
-
let partialFiles = FileHandler.getFilesInFolder(domainPartialsPath, this.templateExtensions);
|
|
139
|
-
for(let file of partialFiles){
|
|
140
|
-
let partialName = this.extractTemplateName(file);
|
|
141
|
-
if(!partialName){
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
let partialPath = FileHandler.joinPaths(domainPartialsPath, file);
|
|
145
|
-
let partialContent = FileHandler.readFile(partialPath);
|
|
146
|
-
if(!partialContent){
|
|
147
|
-
Logger.error('Failed to read domain partial: '+partialPath);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
domainPartials[partialName] = partialContent;
|
|
151
|
-
}
|
|
152
|
-
this.domainPartialsCache.set(domain, domainPartials);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async setupDomainTemplates()
|
|
156
|
-
{
|
|
157
|
-
let domainsPath = FileHandler.joinPaths(this.templatesPath, 'domains');
|
|
158
|
-
if(!FileHandler.exists(domainsPath)){
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
let domainFolders = FileHandler.fetchSubFoldersList(domainsPath);
|
|
162
|
-
for(let domain of domainFolders){
|
|
163
|
-
let domainPath = FileHandler.joinPaths(domainsPath, domain);
|
|
164
|
-
this.domainTemplatesMap.set(domain, domainPath);
|
|
165
|
-
await this.loadDomainPartials(domain, domainPath);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
extractTemplateName(filename)
|
|
170
|
-
{
|
|
171
|
-
for(let extension of this.templateExtensions){
|
|
172
|
-
if(filename.endsWith(extension)){
|
|
173
|
-
return filename.replace(extension, '');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
getDomainFromRequest(req)
|
|
180
|
-
{
|
|
181
|
-
let host = req.get('host');
|
|
182
|
-
if(!host){
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
return host.split(':')[0];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
resolveDomainToFolder(domain)
|
|
189
|
-
{
|
|
190
|
-
if(!domain){
|
|
191
|
-
domain = this.defaultDomain;
|
|
192
|
-
}
|
|
193
|
-
return sc.get(this.domainMapping, domain, domain);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
resolveDomainToSiteKey(domain)
|
|
197
|
-
{
|
|
198
|
-
return sc.get(this.siteKeyMapping, this.resolveDomainToFolder(domain), 'default');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
getPartialsForDomain(domain)
|
|
202
|
-
{
|
|
203
|
-
let resolvedDomain = this.resolveDomainToFolder(domain);
|
|
204
|
-
let domainPartials = this.domainPartialsCache.get(resolvedDomain);
|
|
205
|
-
if(!domainPartials && this.defaultDomain && resolvedDomain !== this.defaultDomain){
|
|
206
|
-
domainPartials = this.domainPartialsCache.get(this.defaultDomain);
|
|
207
|
-
}
|
|
208
|
-
if(!domainPartials){
|
|
209
|
-
return this.partialsCache;
|
|
210
|
-
}
|
|
211
|
-
return Object.assign({}, this.partialsCache, domainPartials);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
findTemplatePath(templateName, domain)
|
|
215
|
-
{
|
|
216
|
-
let resolvedDomain = this.resolveDomainToFolder(domain);
|
|
217
|
-
if(resolvedDomain){
|
|
218
|
-
let domainPath = this.domainTemplatesMap.get(resolvedDomain);
|
|
219
|
-
if(domainPath){
|
|
220
|
-
let domainTemplatePath = this.findTemplateInPath(templateName, domainPath);
|
|
221
|
-
if(domainTemplatePath){
|
|
222
|
-
return domainTemplatePath;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if(this.defaultDomain && resolvedDomain !== this.defaultDomain){
|
|
226
|
-
let defaultDomainPath = this.domainTemplatesMap.get(this.defaultDomain);
|
|
227
|
-
if(defaultDomainPath){
|
|
228
|
-
let defaultTemplatePath = this.findTemplateInPath(templateName, defaultDomainPath);
|
|
229
|
-
if(defaultTemplatePath){
|
|
230
|
-
return defaultTemplatePath;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return this.findTemplateInPath(templateName, this.templatesPath);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
findTemplateInPath(templateName, basePath)
|
|
239
|
-
{
|
|
240
|
-
for(let extension of this.templateExtensions){
|
|
241
|
-
let templatePath = FileHandler.joinPaths(basePath, templateName + extension);
|
|
242
|
-
if(FileHandler.exists(templatePath)){
|
|
243
|
-
return templatePath;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
findLayoutPath(layoutName, domain)
|
|
250
|
-
{
|
|
251
|
-
let resolvedDomain = this.resolveDomainToFolder(domain);
|
|
252
|
-
if(resolvedDomain){
|
|
253
|
-
let domainPath = this.domainTemplatesMap.get(resolvedDomain);
|
|
254
|
-
if(domainPath){
|
|
255
|
-
let domainLayoutPath = this.findTemplateInPath('layouts/' + layoutName, domainPath);
|
|
256
|
-
if(domainLayoutPath){
|
|
257
|
-
return domainLayoutPath;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return this.findTemplateInPath('layouts/' + layoutName, this.templatesPath);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
setupStaticAssets()
|
|
265
|
-
{
|
|
266
|
-
if(!this.app || !this.appServerFactory || !this.publicPath){
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
if(this.appServerFactory && this.appServerFactory.applicationFramework){
|
|
270
|
-
this.app.use(this.appServerFactory.applicationFramework.static(this.publicPath));
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
fetchMetaFields(data)
|
|
277
|
-
{
|
|
278
|
-
if(!sc.isObject(data) || 0 === Object.keys(data).length){
|
|
279
|
-
return this.metaDefaults;
|
|
280
|
-
}
|
|
281
|
-
let result = Object.assign({}, this.metaDefaults);
|
|
282
|
-
for(let key of Object.keys(data)){
|
|
283
|
-
let value = data[key];
|
|
284
|
-
if(null !== value && '' !== value && 'undefined' !== typeof value){
|
|
285
|
-
result[key] = value;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
let titleValue = sc.get(result, 'title', '');
|
|
289
|
-
let metaTitleValue = sc.get(result, 'meta_title', titleValue);
|
|
290
|
-
if(metaTitleValue && '' !== metaTitleValue){
|
|
291
|
-
result.meta_title = metaTitleValue;
|
|
292
|
-
}
|
|
293
|
-
let metaOgTitleValue = sc.get(result, 'meta_og_title', metaTitleValue);
|
|
294
|
-
if(metaOgTitleValue && '' !== metaOgTitleValue){
|
|
295
|
-
result.meta_og_title = metaOgTitleValue;
|
|
296
|
-
}
|
|
297
|
-
let metaDescValue = sc.get(result, 'meta_description', '');
|
|
298
|
-
let metaOgDescValue = sc.get(result, 'meta_og_description', metaDescValue);
|
|
299
|
-
if(metaOgDescValue && '' !== metaOgDescValue){
|
|
300
|
-
result.meta_og_description = metaOgDescValue;
|
|
301
|
-
}
|
|
302
|
-
let jsonData = sc.get(result, 'json_data', null);
|
|
303
|
-
if(sc.isString(jsonData)){
|
|
304
|
-
jsonData = sc.toJson(jsonData, {});
|
|
305
|
-
}
|
|
306
|
-
if(!sc.isObject(jsonData)){
|
|
307
|
-
jsonData = {};
|
|
308
|
-
}
|
|
309
|
-
let viewportValue = sc.get(jsonData, 'viewport', this.metaDefaults.viewport);
|
|
310
|
-
if(viewportValue && '' !== viewportValue){
|
|
311
|
-
jsonData.viewport = viewportValue;
|
|
312
|
-
}
|
|
313
|
-
result.json_data = jsonData;
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async handleSearchRequest(req, res)
|
|
318
|
-
{
|
|
319
|
-
try {
|
|
320
|
-
let domain = this.getDomainFromRequest(req);
|
|
321
|
-
let config = this.search.parseSearchParameters(req.query);
|
|
322
|
-
if(!config){
|
|
323
|
-
return res.redirect('/?error-message=searchInvalidParameters');
|
|
324
|
-
}
|
|
325
|
-
let cacheKey = this.buildCacheKey(req.path, req);
|
|
326
|
-
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
327
|
-
let cachedContent = await this.cacheManager.get(domain, cacheKey);
|
|
328
|
-
if(cachedContent){
|
|
329
|
-
return res.send(cachedContent);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
let searchResults = await this.search.executeSearch(config);
|
|
333
|
-
if(false === searchResults){
|
|
334
|
-
return res.redirect('/?error-message=searchExecutionFailed');
|
|
335
|
-
}
|
|
336
|
-
let content = await this.renderWithTemplateContent(
|
|
337
|
-
{
|
|
338
|
-
template: config.render.page,
|
|
339
|
-
layout: config.render.layout,
|
|
340
|
-
content: await this.searchRenderer.renderSearchResults(searchResults, config, domain, req)
|
|
341
|
-
},
|
|
342
|
-
Object.assign({}, {
|
|
343
|
-
search_query: sc.get(req.query, 'search', ''),
|
|
344
|
-
searchConfig: config,
|
|
345
|
-
query: req.query
|
|
346
|
-
}),
|
|
347
|
-
domain,
|
|
348
|
-
req
|
|
349
|
-
);
|
|
350
|
-
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
351
|
-
await this.cacheManager.set(domain, cacheKey, content);
|
|
352
|
-
}
|
|
353
|
-
return res.send(content);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
Logger.error('Search request handling error: ' + error.message);
|
|
356
|
-
return res.redirect('/?error-message=searchError');
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
116
|
async handleRequest(req, res)
|
|
361
117
|
{
|
|
362
118
|
try {
|
|
363
|
-
|
|
364
|
-
|
|
119
|
+
if(this.handleFrontendTemplateReload){
|
|
120
|
+
await this.handleFrontendTemplateReload(this.templateCache, this.templateResolver);
|
|
121
|
+
}
|
|
122
|
+
let originalPath = req.path;
|
|
123
|
+
let domain = this.requestProcessor.getDomainFromRequest(req);
|
|
365
124
|
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
366
|
-
let cachedContent = await this.cacheManager.get(domain,
|
|
125
|
+
let cachedContent = await this.cacheManager.get(domain, originalPath);
|
|
367
126
|
if(cachedContent){
|
|
368
127
|
return res.send(cachedContent);
|
|
369
128
|
}
|
|
370
129
|
}
|
|
371
|
-
let route = await this.findRouteByPath(
|
|
130
|
+
let route = await this.requestProcessor.findRouteByPath(originalPath, domain);
|
|
372
131
|
if(route){
|
|
373
|
-
|
|
132
|
+
let redirectResult = await this.requestProcessor.handleRouteRedirect(route, res);
|
|
133
|
+
if(redirectResult){
|
|
134
|
+
return redirectResult;
|
|
135
|
+
}
|
|
136
|
+
let normalizedPath = this.requestProcessor.normalizePathForRouteSearch(originalPath);
|
|
137
|
+
let routeFoundWithSlash = originalPath !== normalizedPath && route.path === normalizedPath + '/';
|
|
138
|
+
if(!originalPath.endsWith('/') && routeFoundWithSlash){
|
|
139
|
+
return res.redirect(301, originalPath + '/');
|
|
140
|
+
}
|
|
141
|
+
return await this.responseManager.renderWithCacheHandler(
|
|
142
|
+
async () => await this.contentRenderer.generateRouteContent(route, domain, req),
|
|
143
|
+
async () => await this.responseManager.renderNotFound(domain, res, req),
|
|
144
|
+
async (content) => res.send(content),
|
|
145
|
+
domain,
|
|
146
|
+
res,
|
|
147
|
+
originalPath,
|
|
148
|
+
req
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if(!originalPath.endsWith('/') && '/' !== originalPath){
|
|
152
|
+
let routeWithSlash = await this.requestProcessor.findRouteByPath(originalPath + '/', domain);
|
|
153
|
+
if(routeWithSlash){
|
|
154
|
+
let redirectResult = await this.requestProcessor.handleRouteRedirect(routeWithSlash, res);
|
|
155
|
+
if(redirectResult){
|
|
156
|
+
return redirectResult;
|
|
157
|
+
}
|
|
158
|
+
return res.redirect(301, originalPath + '/');
|
|
159
|
+
}
|
|
374
160
|
}
|
|
375
|
-
let entityResult = await this.findEntityByPath(
|
|
161
|
+
let entityResult = await this.entityAccessManager.findEntityByPath(originalPath);
|
|
376
162
|
if(entityResult){
|
|
377
|
-
return await this.
|
|
378
|
-
|
|
379
|
-
|
|
163
|
+
return await this.responseManager.renderWithCacheHandler(
|
|
164
|
+
async () => await this.contentRenderer.renderWithTemplateContent(
|
|
165
|
+
entityResult.entity,
|
|
166
|
+
{},
|
|
167
|
+
domain,
|
|
168
|
+
req,
|
|
169
|
+
null
|
|
170
|
+
),
|
|
171
|
+
async () => await this.responseManager.renderNotFound(domain, res, req),
|
|
172
|
+
async (content) => res.send(content),
|
|
173
|
+
domain,
|
|
174
|
+
res,
|
|
175
|
+
originalPath,
|
|
176
|
+
req
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
let templatePath = this.templateResolver.findTemplateByPath(originalPath, domain);
|
|
380
180
|
if(templatePath){
|
|
381
|
-
return await this.
|
|
382
|
-
|
|
383
|
-
|
|
181
|
+
return await this.responseManager.renderWithCacheHandler(
|
|
182
|
+
async () => await this.contentRenderer.generateTemplateContent(templatePath, domain, req),
|
|
183
|
+
async () => res.status(500).send('Template error: '+templatePath),
|
|
184
|
+
async (content) => res.send(await this.contentRenderer.renderWithTemplateContent({content}, {}, domain, req, null)),
|
|
185
|
+
domain,
|
|
186
|
+
res,
|
|
187
|
+
originalPath,
|
|
188
|
+
req
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return await this.responseManager.renderNotFound(domain, res, req);
|
|
384
192
|
} catch (error) {
|
|
385
193
|
Logger.error('Request handling error: '+error.message);
|
|
386
194
|
return res.status(500).send('Internal server error');
|
|
387
195
|
}
|
|
388
196
|
}
|
|
389
197
|
|
|
390
|
-
async findRouteByPath(path, domain)
|
|
391
|
-
{
|
|
392
|
-
let routesEntity = this.dataServer.getEntity('routes');
|
|
393
|
-
if(!routesEntity){
|
|
394
|
-
Logger.error('Routes entity not found in dataServer.');
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
let domainFilter = domain || null;
|
|
398
|
-
let routeFilters = {path, enabled: 1};
|
|
399
|
-
let routes = await routesEntity.load(routeFilters);
|
|
400
|
-
let matchingRoute = false;
|
|
401
|
-
let nullDomain = false;
|
|
402
|
-
for(let route of routes){
|
|
403
|
-
if(route.domain === domainFilter){
|
|
404
|
-
matchingRoute = route;
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
if(!route.domain){
|
|
408
|
-
nullDomain = route;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if(matchingRoute){
|
|
412
|
-
return matchingRoute;
|
|
413
|
-
}
|
|
414
|
-
return nullDomain;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
async isEntityAccessible(entityName)
|
|
418
|
-
{
|
|
419
|
-
if(this.entityAccessCache.has(entityName)){
|
|
420
|
-
return this.entityAccessCache.get(entityName);
|
|
421
|
-
}
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async findEntityByPath(path)
|
|
426
|
-
{
|
|
427
|
-
let pathSegments = path.split('/').filter(segment => '' !== segment);
|
|
428
|
-
if(2 > pathSegments.length){
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
let entityName = pathSegments[0];
|
|
432
|
-
if(!await this.isEntityAccessible(entityName)){
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
let entityId = pathSegments[1];
|
|
436
|
-
let entity = this.dataServer.getEntity(entityName);
|
|
437
|
-
if(!entity){
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
let loadedEntity = await entity.loadById(entityId);
|
|
441
|
-
if(!loadedEntity){
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
|
-
return {entity: loadedEntity, entityName};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
findTemplateByPath(path, domain)
|
|
448
|
-
{
|
|
449
|
-
if('/' === path){
|
|
450
|
-
path = '/index';
|
|
451
|
-
}
|
|
452
|
-
let templatePath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
453
|
-
templatePath = templatePath.startsWith('/') ? templatePath.substring(1) : templatePath;
|
|
454
|
-
if('page' === templatePath){
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
return this.findTemplatePath(templatePath, domain);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async renderRouteWithCache(route, domain, res, path, req)
|
|
461
|
-
{
|
|
462
|
-
let renderedContent = await this.generateRouteContent(route, domain, req);
|
|
463
|
-
if(!renderedContent){
|
|
464
|
-
return await this.renderNotFound(domain, res, req);
|
|
465
|
-
}
|
|
466
|
-
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
467
|
-
let cacheKey = this.buildCacheKey(path, req);
|
|
468
|
-
await this.cacheManager.set(domain, cacheKey, renderedContent);
|
|
469
|
-
}
|
|
470
|
-
return res.send(renderedContent);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
async renderEntityWithCache(entityResult, domain, res, path, req)
|
|
474
|
-
{
|
|
475
|
-
let renderedContent = await this.generateEntityContent(entityResult, domain, req);
|
|
476
|
-
if(!renderedContent){
|
|
477
|
-
return await this.renderNotFound(domain, res, req);
|
|
478
|
-
}
|
|
479
|
-
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
480
|
-
let cacheKey = this.buildCacheKey(path, req);
|
|
481
|
-
await this.cacheManager.set(domain, cacheKey, renderedContent);
|
|
482
|
-
}
|
|
483
|
-
return res.send(renderedContent);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
async renderTemplateWithCache(templatePath, domain, res, path, req)
|
|
487
|
-
{
|
|
488
|
-
let renderedContent = await this.generateTemplateContent(templatePath, domain, req);
|
|
489
|
-
if(!renderedContent){
|
|
490
|
-
return res.status(500).send('Template error: '+templatePath);
|
|
491
|
-
}
|
|
492
|
-
if(this.cacheManager && this.cacheManager.isEnabled()){
|
|
493
|
-
let cacheKey = this.buildCacheKey(path, req);
|
|
494
|
-
await this.cacheManager.set(domain, cacheKey, renderedContent);
|
|
495
|
-
}
|
|
496
|
-
return await this.renderWithLayout({content: renderedContent}, {}, 'default', domain, res, req);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
async generateRouteContent(route, domain, req)
|
|
500
|
-
{
|
|
501
|
-
if(!route.router){
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
let entity = this.dataServer.getEntity(route.router);
|
|
505
|
-
if(!entity){
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
let content = await entity.loadOne({route_id: route.id});
|
|
509
|
-
if(!content){
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain, req);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async generateEntityContent(entityResult, domain, req)
|
|
516
|
-
{
|
|
517
|
-
return await this.renderWithTemplateContent(entityResult.entity, entityResult.entity, domain, req);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async generateTemplateContent(templatePath, domain, req)
|
|
521
|
-
{
|
|
522
|
-
let template = FileHandler.readFile(templatePath);
|
|
523
|
-
if(!template){
|
|
524
|
-
Logger.error('Failed to read template: ' + templatePath);
|
|
525
|
-
return false;
|
|
526
|
-
}
|
|
527
|
-
return await this.templateEngine.render(template, {}, this.getPartialsForDomain(domain), domain, req);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
198
|
async renderRoute(route, domain, res, req)
|
|
531
199
|
{
|
|
532
200
|
if(!route.router){
|
|
533
|
-
return await this.renderNotFound(domain, res, req);
|
|
201
|
+
return await this.responseManager.renderNotFound(domain, res, req);
|
|
534
202
|
}
|
|
535
203
|
let entity = this.dataServer.getEntity(route.router);
|
|
536
204
|
if(!entity){
|
|
537
|
-
return await this.renderNotFound(domain, res, req);
|
|
205
|
+
return await this.responseManager.renderNotFound(domain, res, req);
|
|
538
206
|
}
|
|
539
207
|
let content = await entity.loadOne({route_id: route.id});
|
|
540
208
|
if(!content){
|
|
541
|
-
return await this.renderNotFound(domain, res, req);
|
|
542
|
-
}
|
|
543
|
-
return await this.renderWithTemplate(content, Object.assign({}, route, content), domain, res, req);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async renderWithTemplate(content, data, domain, res, req)
|
|
547
|
-
{
|
|
548
|
-
return res.send(await this.renderWithTemplateContent(content, data, domain, req));
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
async renderWithTemplateContent(content, data, domain, req)
|
|
552
|
-
{
|
|
553
|
-
let templateName = sc.get(content, 'template', 'page');
|
|
554
|
-
if(!templateName){
|
|
555
|
-
templateName = 'page';
|
|
556
|
-
}
|
|
557
|
-
let layoutName = sc.get(content, 'layout', '');
|
|
558
|
-
if(!layoutName){
|
|
559
|
-
layoutName = 'default';
|
|
560
|
-
}
|
|
561
|
-
let layoutContent = await this.processContentWithLayout(content, data, layoutName, domain, req);
|
|
562
|
-
let templatePath = this.findTemplatePath(templateName, domain);
|
|
563
|
-
if(!templatePath){
|
|
564
|
-
return layoutContent;
|
|
565
|
-
}
|
|
566
|
-
let pageTemplate = FileHandler.readFile(templatePath);
|
|
567
|
-
if(!pageTemplate){
|
|
568
|
-
return layoutContent;
|
|
209
|
+
return await this.responseManager.renderNotFound(domain, res, req);
|
|
569
210
|
}
|
|
570
|
-
return await this.
|
|
571
|
-
|
|
572
|
-
Object.assign({},
|
|
573
|
-
this.getPartialsForDomain(domain),
|
|
211
|
+
return res.send(await this.contentRenderer.renderWithTemplateContent(
|
|
212
|
+
content,
|
|
213
|
+
Object.assign({}, route, content),
|
|
574
214
|
domain,
|
|
575
|
-
req
|
|
576
|
-
|
|
215
|
+
req,
|
|
216
|
+
null
|
|
217
|
+
));
|
|
577
218
|
}
|
|
578
219
|
|
|
579
|
-
|
|
220
|
+
setupStaticAssets()
|
|
580
221
|
{
|
|
581
|
-
|
|
582
|
-
if(!template){
|
|
583
|
-
Logger.error('Failed to read template: ' + templatePath);
|
|
222
|
+
if(!this.app || !this.appServerFactory || !this.publicPath){
|
|
584
223
|
return false;
|
|
585
224
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
async renderWithLayout(content, data, layoutName, domain, res, req)
|
|
590
|
-
{
|
|
591
|
-
return res.send(await this.renderWithTemplateContent(content, data, domain, req));
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
async processContentWithLayout(content, data, layoutName, domain, req)
|
|
595
|
-
{
|
|
596
|
-
let processedContent = await this.processContent(content, data, domain, req);
|
|
597
|
-
let layoutPath = this.findLayoutPath(layoutName, domain);
|
|
598
|
-
if(!layoutPath){
|
|
599
|
-
return processedContent;
|
|
600
|
-
}
|
|
601
|
-
let layoutTemplate = FileHandler.readFile(layoutPath);
|
|
602
|
-
if(!layoutTemplate){
|
|
603
|
-
return processedContent;
|
|
604
|
-
}
|
|
605
|
-
return await this.templateEngine.render(
|
|
606
|
-
layoutTemplate,
|
|
607
|
-
Object.assign({}, data, {content: processedContent}),
|
|
608
|
-
this.getPartialsForDomain(domain),
|
|
609
|
-
domain,
|
|
610
|
-
req
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
async processContent(content, data, domain, req)
|
|
615
|
-
{
|
|
616
|
-
let contentText = sc.get(content, 'content', '');
|
|
617
|
-
if(!contentText){
|
|
618
|
-
return '';
|
|
619
|
-
}
|
|
620
|
-
return await this.templateEngine.render(contentText, data, this.getPartialsForDomain(domain), domain, req);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
async renderNotFound(domain, res, req)
|
|
624
|
-
{
|
|
625
|
-
let notFoundPath = this.findTemplatePath('404', domain);
|
|
626
|
-
if(notFoundPath){
|
|
627
|
-
let content = await this.renderContentWithTemplate(notFoundPath, {}, domain, req);
|
|
628
|
-
if(content){
|
|
629
|
-
res.status(404);
|
|
630
|
-
return await this.renderWithLayout({content}, {}, 'default', domain, res, req);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return res.status(404).send('Page not found');
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
buildCacheKey(path, req)
|
|
637
|
-
{
|
|
638
|
-
if(!req || !req.query){
|
|
639
|
-
return path;
|
|
640
|
-
}
|
|
641
|
-
for(let key of Object.keys(req.query)){
|
|
642
|
-
if(key.endsWith('-key')){
|
|
643
|
-
let queryString = '';
|
|
644
|
-
for(let qKey of Object.keys(req.query)){
|
|
645
|
-
queryString += (queryString ? '&' : '') + qKey + '=' + req.query[qKey];
|
|
646
|
-
}
|
|
647
|
-
let hash = 0;
|
|
648
|
-
for(let i = 0; i < queryString.length; i++){
|
|
649
|
-
let char = queryString.charCodeAt(i);
|
|
650
|
-
hash = ((hash << 5) - hash) + char;
|
|
651
|
-
hash = hash & hash;
|
|
652
|
-
}
|
|
653
|
-
return path + '_' + Math.abs(hash);
|
|
654
|
-
}
|
|
225
|
+
if(this.appServerFactory && this.appServerFactory.applicationFramework){
|
|
226
|
+
this.app.use(this.appServerFactory.applicationFramework.static(this.publicPath));
|
|
227
|
+
return true;
|
|
655
228
|
}
|
|
656
|
-
return
|
|
229
|
+
return false;
|
|
657
230
|
}
|
|
658
231
|
|
|
659
232
|
}
|