@reldens/cms 0.16.0 → 0.19.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 (38) hide show
  1. package/README.md +92 -9
  2. package/admin/reldens-admin-client.css +55 -0
  3. package/admin/reldens-admin-client.js +24 -0
  4. package/admin/templates/cache-clean-button.html +4 -0
  5. package/admin/templates/clear-all-cache-button.html +18 -0
  6. package/admin/templates/fields/view/textarea.html +1 -1
  7. package/bin/reldens-cms-generate-entities.js +85 -18
  8. package/bin/reldens-cms.js +6 -6
  9. package/lib/admin-manager/contents-builder.js +257 -0
  10. package/lib/admin-manager/router-contents.js +618 -0
  11. package/lib/admin-manager/router.js +208 -0
  12. package/lib/admin-manager-validator.js +2 -1
  13. package/lib/admin-manager.js +116 -990
  14. package/lib/admin-translations.js +9 -1
  15. package/lib/cache/add-cache-button-subscriber.js +149 -0
  16. package/lib/cache/cache-manager.js +168 -0
  17. package/lib/cache/cache-routes-handler.js +99 -0
  18. package/lib/cms-pages-route-manager.js +45 -21
  19. package/lib/frontend.js +288 -71
  20. package/lib/installer.js +5 -2
  21. package/lib/json-fields-parser.js +74 -0
  22. package/lib/manager.js +49 -4
  23. package/lib/pagination-handler.js +243 -0
  24. package/lib/search-renderer.js +116 -0
  25. package/lib/search.js +344 -0
  26. package/lib/template-engine/collections-single-transformer.js +53 -0
  27. package/lib/template-engine/collections-transformer-base.js +84 -0
  28. package/lib/template-engine/collections-transformer.js +353 -0
  29. package/lib/template-engine/entities-transformer.js +65 -0
  30. package/lib/template-engine/partials-transformer.js +171 -0
  31. package/lib/template-engine.js +53 -387
  32. package/lib/templates-list.js +2 -0
  33. package/migrations/default-homepage.sql +6 -6
  34. package/migrations/install.sql +21 -20
  35. package/package.json +4 -4
  36. package/templates/page.html +19 -2
  37. package/templates/partials/entriesListView.html +14 -0
  38. package/templates/partials/pagedCollection.html +33 -0
package/lib/frontend.js CHANGED
@@ -4,9 +4,11 @@
4
4
  *
5
5
  */
6
6
 
7
+ const { TemplateEngine } = require('./template-engine');
8
+ const { Search } = require('./search');
9
+ const { SearchRenderer } = require('./search-renderer');
7
10
  const { FileHandler } = require('@reldens/server-utils');
8
11
  const { Logger, sc } = require('@reldens/utils');
9
- const { TemplateEngine } = require('./template-engine');
10
12
 
11
13
  class Frontend
12
14
  {
@@ -28,7 +30,27 @@ class Frontend
28
30
  this.domainPartialsCache = new Map();
29
31
  this.domainTemplatesMap = new Map();
30
32
  this.entityAccessCache = new Map();
33
+ this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
31
34
  this.templateEngine = false;
35
+ this.cacheManager = sc.get(props, 'cacheManager', false);
36
+ this.searchPath = sc.get(props, 'searchPath', '/search');
37
+ this.searchSets = sc.get(props, 'searchSets', false);
38
+ this.searchConfig = {dataServer: this.dataServer};
39
+ if(this.searchSets){
40
+ this.searchConfig.searchSets = this.searchSets;
41
+ }
42
+ this.search = new Search(this.searchConfig);
43
+ this.searchRenderer = new SearchRenderer({
44
+ renderEngine: this.renderEngine,
45
+ getPartials: this.getPartialsForDomain.bind(this)
46
+ });
47
+ this.metaDefaults = sc.get(props, 'metaDefaults', {
48
+ locale: 'en',
49
+ viewport: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover',
50
+ meta_robots: 'index,follow',
51
+ meta_theme_color: '#000000',
52
+ meta_twitter_card_type: 'summary'
53
+ });
32
54
  }
33
55
 
34
56
  async initialize()
@@ -56,12 +78,17 @@ class Frontend
56
78
  this.templateEngine = new TemplateEngine({
57
79
  renderEngine: this.renderEngine,
58
80
  dataServer: this.dataServer,
59
- getPartials: this.getPartialsForDomain.bind(this)
81
+ getPartials: this.getPartialsForDomain.bind(this),
82
+ entitiesConfig: this.entitiesConfig
60
83
  });
84
+ this.searchConfig.jsonFieldsParser = this.templateEngine.jsonFieldsParser;
61
85
  await this.loadPartials();
62
86
  await this.setupDomainTemplates();
63
87
  await this.loadEntityAccessRules();
64
88
  this.setupStaticAssets();
89
+ this.app.get(this.searchPath, async (req, res) => {
90
+ return await this.handleSearchRequest(req, res);
91
+ });
65
92
  this.app.get('*', async (req, res) => {
66
93
  return await this.handleRequest(req, res);
67
94
  });
@@ -246,26 +273,114 @@ class Frontend
246
273
  return false;
247
274
  }
248
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
+
249
360
  async handleRequest(req, res)
250
361
  {
251
362
  try {
252
363
  let path = req.path;
253
364
  let domain = this.getDomainFromRequest(req);
254
- console.log('---------------> ', {path, domain});
255
- this.templateEngine.setCurrentDomain(domain);
365
+ if(this.cacheManager && this.cacheManager.isEnabled()){
366
+ let cachedContent = await this.cacheManager.get(domain, path);
367
+ if(cachedContent){
368
+ return res.send(cachedContent);
369
+ }
370
+ }
256
371
  let route = await this.findRouteByPath(path, domain);
257
372
  if(route){
258
- return await this.renderRoute(route, domain, res);
373
+ return await this.renderRouteWithCache(route, domain, res, path, req);
259
374
  }
260
375
  let entityResult = await this.findEntityByPath(path);
261
376
  if(entityResult){
262
- return await this.renderEntity(entityResult, domain, res);
377
+ return await this.renderEntityWithCache(entityResult, domain, res, path, req);
263
378
  }
264
379
  let templatePath = this.findTemplateByPath(path, domain);
265
380
  if(templatePath){
266
- return await this.renderTemplate(templatePath, domain, res);
381
+ return await this.renderTemplateWithCache(templatePath, domain, res, path, req);
267
382
  }
268
- return await this.renderNotFound(domain, res);
383
+ return await this.renderNotFound(domain, res, req);
269
384
  } catch (error) {
270
385
  Logger.error('Request handling error: '+error.message);
271
386
  return res.status(500).send('Internal server error');
@@ -276,23 +391,27 @@ class Frontend
276
391
  {
277
392
  let routesEntity = this.dataServer.getEntity('routes');
278
393
  if(!routesEntity){
279
- Logger.error('Routes entity not found in dataServer');
394
+ Logger.error('Routes entity not found in dataServer.');
280
395
  return false;
281
396
  }
282
397
  let domainFilter = domain || null;
283
398
  let routeFilters = {path, enabled: 1};
284
399
  let routes = await routesEntity.load(routeFilters);
285
400
  let matchingRoute = false;
401
+ let nullDomain = false;
286
402
  for(let route of routes){
287
- if(!route.domain || route.domain === domainFilter){
403
+ if(route.domain === domainFilter){
288
404
  matchingRoute = route;
289
405
  break;
290
406
  }
407
+ if(!route.domain){
408
+ nullDomain = route;
409
+ }
291
410
  }
292
411
  if(matchingRoute){
293
412
  return matchingRoute;
294
413
  }
295
- return false;
414
+ return nullDomain;
296
415
  }
297
416
 
298
417
  async isEntityAccessible(entityName)
@@ -338,107 +457,205 @@ class Frontend
338
457
  return this.findTemplatePath(templatePath, domain);
339
458
  }
340
459
 
341
- async renderRoute(route, domain, res)
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)
342
500
  {
343
- if(!route.router || !route.cms_page_id){
344
- return await this.renderNotFound(domain, res);
501
+ if(!route.router){
502
+ return false;
345
503
  }
346
504
  let entity = this.dataServer.getEntity(route.router);
347
505
  if(!entity){
348
- return await this.renderNotFound(domain, res);
506
+ return false;
349
507
  }
350
- let content = await entity.loadById(route.cms_page_id);
508
+ let content = await entity.loadOne({route_id: route.id});
351
509
  if(!content){
352
- return await this.renderNotFound(domain, res);
510
+ return false;
353
511
  }
354
- return await this.renderWithLayout(
355
- content,
356
- Object.assign({}, route, content),
357
- sc.get(content, 'layout', 'default'),
358
- domain,
359
- res
360
- );
512
+ return await this.renderWithTemplateContent(content, Object.assign({}, route, content), domain, req);
361
513
  }
362
514
 
363
- async renderEntity(entityResult, domain, res)
515
+ async generateEntityContent(entityResult, domain, req)
364
516
  {
365
- return await this.renderWithLayout(
366
- entityResult.entity,
367
- Object.assign({}, entityResult.entity),
368
- sc.get(entityResult.entity, 'layout', 'default'),
369
- domain,
370
- res
371
- );
517
+ return await this.renderWithTemplateContent(entityResult.entity, entityResult.entity, domain, req);
372
518
  }
373
519
 
374
- async renderTemplate(templatePath, domain, res)
520
+ async generateTemplateContent(templatePath, domain, req)
375
521
  {
376
- let content = await this.renderContentWithTemplate(templatePath, {}, domain);
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
+ async renderRoute(route, domain, res, req)
531
+ {
532
+ if(!route.router){
533
+ return await this.renderNotFound(domain, res, req);
534
+ }
535
+ let entity = this.dataServer.getEntity(route.router);
536
+ if(!entity){
537
+ return await this.renderNotFound(domain, res, req);
538
+ }
539
+ let content = await entity.loadOne({route_id: route.id});
377
540
  if(!content){
378
- return res.status(500).send('Template error: '+templatePath);
541
+ return await this.renderNotFound(domain, res, req);
379
542
  }
380
- return await this.renderWithLayout({content}, {}, 'default', domain, res);
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));
381
549
  }
382
550
 
383
- async renderContentWithTemplate(templatePath, data, domain)
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;
569
+ }
570
+ return await this.templateEngine.render(
571
+ pageTemplate,
572
+ Object.assign({}, this.fetchMetaFields(data), {content: layoutContent, siteHandle: this.resolveDomainToSiteKey(domain)}),
573
+ this.getPartialsForDomain(domain),
574
+ domain,
575
+ req
576
+ );
577
+ }
578
+
579
+ async renderContentWithTemplate(templatePath, data, domain, req)
384
580
  {
385
581
  let template = FileHandler.readFile(templatePath);
386
582
  if(!template){
387
583
  Logger.error('Failed to read template: ' + templatePath);
388
584
  return false;
389
585
  }
390
- return await this.templateEngine.render(template, data, this.getPartialsForDomain(domain));
586
+ return await this.templateEngine.render(template, data, this.getPartialsForDomain(domain), domain, req);
587
+ }
588
+
589
+ async renderWithLayout(content, data, layoutName, domain, res, req)
590
+ {
591
+ return res.send(await this.renderWithTemplateContent(content, data, domain, req));
391
592
  }
392
593
 
393
- async renderWithLayout(content, data, layoutName, domain, res)
594
+ async processContentWithLayout(content, data, layoutName, domain, req)
394
595
  {
596
+ let processedContent = await this.processContent(content, data, domain, req);
395
597
  let layoutPath = this.findLayoutPath(layoutName, domain);
396
- let layoutContent = '';
397
- if(layoutPath){
398
- let layoutTemplate = FileHandler.readFile(layoutPath);
399
- if(layoutTemplate){
400
- let processedContent = sc.get(content, 'content', '');
401
- if(processedContent){
402
- processedContent = await this.templateEngine.render(
403
- processedContent,
404
- data,
405
- this.getPartialsForDomain(domain)
406
- );
407
- }
408
- layoutContent = await this.templateEngine.render(
409
- layoutTemplate,
410
- Object.assign({}, data, {content: processedContent}),
411
- this.getPartialsForDomain(domain)
412
- );
413
- }
598
+ if(!layoutPath){
599
+ return processedContent;
414
600
  }
415
- if('' === layoutContent){
416
- let processedContent = sc.get(content, 'content', '');
417
- if(processedContent){
418
- layoutContent = await this.templateEngine.render(
419
- processedContent,
420
- data,
421
- this.getPartialsForDomain(domain)
422
- );
423
- }
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 '';
424
619
  }
425
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
426
- return res.send(layoutContent);
620
+ return await this.templateEngine.render(contentText, data, this.getPartialsForDomain(domain), domain, req);
427
621
  }
428
622
 
429
- async renderNotFound(domain, res)
623
+ async renderNotFound(domain, res, req)
430
624
  {
431
625
  let notFoundPath = this.findTemplatePath('404', domain);
432
626
  if(notFoundPath){
433
- let content = await this.renderContentWithTemplate(notFoundPath, {}, domain);
627
+ let content = await this.renderContentWithTemplate(notFoundPath, {}, domain, req);
434
628
  if(content){
435
629
  res.status(404);
436
- return await this.renderWithLayout({content}, {}, 'default', domain, res);
630
+ return await this.renderWithLayout({content}, {}, 'default', domain, res, req);
437
631
  }
438
632
  }
439
633
  return res.status(404).send('Page not found');
440
634
  }
441
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
+ }
655
+ }
656
+ return path;
657
+ }
658
+
442
659
  }
443
660
 
444
661
  module.exports.Frontend = Frontend;
package/lib/installer.js CHANGED
@@ -243,11 +243,11 @@ class Installer
243
243
  return '';
244
244
  }
245
245
 
246
- async generateEntities(server, isOverride = false, isInstallationMode = false)
246
+ async generateEntities(server, isOverride = false, isInstallationMode = false, isDryPrisma = false)
247
247
  {
248
248
  let driverType = sc.get(DriversClassMap, server.constructor.name, '');
249
249
  Logger.debug('Driver type detected: '+driverType+', Server constructor: '+server.constructor.name);
250
- if('prisma' === driverType && !isInstallationMode){
250
+ if('prisma' === driverType && !isInstallationMode && !isDryPrisma){
251
251
  Logger.info('Running prisma introspect "npx prisma db pull"...');
252
252
  let dbConfig = this.extractDbConfigFromServer(server);
253
253
  Logger.debug('Extracted DB config:', dbConfig);
@@ -260,6 +260,9 @@ class Installer
260
260
  Logger.info('Generated Prisma schema for entities generation.');
261
261
  }
262
262
  }
263
+ if('prisma' === driverType && isDryPrisma){
264
+ Logger.info('Skipping Prisma schema generation due to --dry-prisma flag.');
265
+ }
263
266
  let generatorConfig = {
264
267
  server,
265
268
  projectPath: this.projectRoot,
@@ -0,0 +1,74 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - JsonFieldsParser
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+
9
+ class JsonFieldsParser
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
15
+ this.jsonFieldsCache = new Map();
16
+ }
17
+
18
+ getJsonFieldsForEntity(entityName)
19
+ {
20
+ if(this.jsonFieldsCache.has(entityName)){
21
+ return this.jsonFieldsCache.get(entityName);
22
+ }
23
+ let jsonFields = [];
24
+ let entityConfig = sc.get(this.entitiesConfig, entityName);
25
+ if(!entityConfig){
26
+ Logger.error('Entity not found in configuration: '+entityName);
27
+ return jsonFields;
28
+ }
29
+ if(!sc.hasOwn(entityConfig, 'properties')){
30
+ Logger.error('Missing properties on entity configuration: '+entityName);
31
+ return jsonFields;
32
+ }
33
+ for(let fieldName of Object.keys(entityConfig.properties)){
34
+ if('json' === sc.get(entityConfig.properties[fieldName], 'dbType', '')){
35
+ jsonFields.push(fieldName);
36
+ }
37
+ }
38
+ this.jsonFieldsCache.set(entityName, jsonFields);
39
+ return jsonFields;
40
+ }
41
+
42
+ parseJsonFields(data, jsonFields)
43
+ {
44
+ if(!sc.isArray(jsonFields) || 0 === jsonFields.length){
45
+ return data;
46
+ }
47
+ if(sc.isArray(data)){
48
+ for(let item of data){
49
+ this.parseEntityJsonFields(item, jsonFields);
50
+ }
51
+ return data;
52
+ }
53
+ if(sc.isObject(data)){
54
+ this.parseEntityJsonFields(data, jsonFields);
55
+ }
56
+ return data;
57
+ }
58
+
59
+ parseEntityJsonFields(entity, jsonFields)
60
+ {
61
+ for(let fieldName of jsonFields){
62
+ if(!sc.hasOwn(entity, fieldName) || !sc.isString(entity[fieldName])){
63
+ continue;
64
+ }
65
+ let parsed = sc.parseJson(entity[fieldName], false);
66
+ if(false !== parsed){
67
+ entity[fieldName] = parsed;
68
+ }
69
+ }
70
+ }
71
+
72
+ }
73
+
74
+ module.exports.JsonFieldsParser = JsonFieldsParser;