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