@reldens/cms 0.16.0 → 0.18.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/admin/reldens-admin-client.css +11 -0
- package/admin/templates/cache-clean-button.html +4 -0
- package/admin/templates/fields/view/textarea.html +1 -1
- package/bin/reldens-cms-generate-entities.js +85 -18
- package/bin/reldens-cms.js +6 -6
- package/lib/admin-manager/contents-builder.js +256 -0
- package/lib/admin-manager/router-contents.js +576 -0
- package/lib/admin-manager/router.js +208 -0
- package/lib/admin-manager.js +114 -990
- package/lib/cache/add-cache-button-subscriber.js +101 -0
- package/lib/cache/cache-manager.js +129 -0
- package/lib/cache/cache-routes-handler.js +76 -0
- package/lib/cms-pages-route-manager.js +31 -19
- package/lib/frontend.js +192 -52
- package/lib/installer.js +5 -2
- package/lib/json-fields-parser.js +74 -0
- package/lib/manager.js +7 -1
- package/lib/template-engine.js +79 -27
- package/lib/templates-list.js +1 -0
- package/migrations/default-homepage.sql +6 -6
- package/migrations/install.sql +4 -3
- package/package.json +2 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - AddCacheButtonSubscriber
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class AddCacheButtonSubscriber
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props = {})
|
|
13
|
+
{
|
|
14
|
+
this.events = sc.get(props, 'events', false);
|
|
15
|
+
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
16
|
+
this.renderCallback = sc.get(props, 'renderCallback', false);
|
|
17
|
+
this.cacheCleanButton = sc.get(props, 'cacheCleanButton', '');
|
|
18
|
+
this.translations = sc.get(props, 'translations', {});
|
|
19
|
+
this.cacheCleanRoute = sc.get(props, 'cacheCleanRoute', '');
|
|
20
|
+
this.setupEvents();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setupEvents()
|
|
24
|
+
{
|
|
25
|
+
if(!this.cacheManager){
|
|
26
|
+
Logger.error('Cache Manager not found on AddCacheButtonSubscriber.');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if(!this.cacheManager.isEnabled()){
|
|
30
|
+
Logger.debug('Cache Manager not enabled.');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if(!this.events){
|
|
34
|
+
Logger.error('Events Manager not found on AddCacheButtonSubscriber.');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
//Logger.debug('Listening events PropertiesPopulation.');
|
|
38
|
+
this.events.on('reldens.adminViewPropertiesPopulation', this.populateViewFields.bind(this));
|
|
39
|
+
this.events.on('reldens.adminEditPropertiesPopulation', this.populateEditFields.bind(this));
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async populateViewFields(event)
|
|
44
|
+
{
|
|
45
|
+
let cacheButton = await this.generateCacheCleanButton(event);
|
|
46
|
+
if(!cacheButton){
|
|
47
|
+
Logger.warning('Missing cache button contents on AddCacheButtonSubscriber.');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if(!event.renderedViewProperties.extraContentForView){
|
|
51
|
+
event.renderedViewProperties.extraContentForView = '';
|
|
52
|
+
}
|
|
53
|
+
event.renderedViewProperties.extraContentForView += cacheButton;
|
|
54
|
+
//Logger.debug('Clean cache button ready on view.');
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async populateEditFields(event)
|
|
59
|
+
{
|
|
60
|
+
let cacheButton = await this.generateCacheCleanButton(event);
|
|
61
|
+
if(!cacheButton){
|
|
62
|
+
Logger.warning('Missing cache button contents on AddCacheButtonSubscriber.');
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if(!event.renderedEditProperties.extraContentForEdit){
|
|
66
|
+
event.renderedEditProperties.extraContentForEdit = '';
|
|
67
|
+
}
|
|
68
|
+
event.renderedEditProperties.extraContentForEdit += cacheButton;
|
|
69
|
+
//Logger.debug('Clean cache button ready on edit.');
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async generateCacheCleanButton(event)
|
|
74
|
+
{
|
|
75
|
+
if('routes' !== event.driverResource.id()){
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if(!event.loadedEntity || !event.loadedEntity.id){
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if(!this.cacheCleanButton){
|
|
82
|
+
Logger.error('Cache clean button template content not found');
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
if(!this.renderCallback){
|
|
86
|
+
Logger.error('Render callback not available for cache button');
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
return await this.renderCallback(
|
|
90
|
+
this.cacheCleanButton,
|
|
91
|
+
{
|
|
92
|
+
cacheCleanRoute: this.cacheCleanRoute,
|
|
93
|
+
routeId: event.loadedEntity.id,
|
|
94
|
+
buttonText: sc.get(this.translations, 'cleanCache', 'Clean Cache')
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports.AddCacheButtonSubscriber = AddCacheButtonSubscriber;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - CacheManager
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
8
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
9
|
+
|
|
10
|
+
class CacheManager
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor(props = {})
|
|
14
|
+
{
|
|
15
|
+
this.projectRoot = sc.get(props, 'projectRoot', './');
|
|
16
|
+
this.cacheBasePath = FileHandler.joinPaths(this.projectRoot, '.reldens_cms_cache');
|
|
17
|
+
this.enabled = sc.get(props, 'enabled', true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
generateCacheKey(domain, path)
|
|
21
|
+
{
|
|
22
|
+
let domainFolder = domain || 'default';
|
|
23
|
+
let pathSegments = path.split('/').filter(segment => '' !== segment);
|
|
24
|
+
if(0 === pathSegments.length){
|
|
25
|
+
pathSegments = ['index'];
|
|
26
|
+
}
|
|
27
|
+
let fileName = pathSegments.pop() + '.html';
|
|
28
|
+
let folderPath = FileHandler.joinPaths(this.cacheBasePath, domainFolder, ...pathSegments);
|
|
29
|
+
return {
|
|
30
|
+
folderPath,
|
|
31
|
+
fileName,
|
|
32
|
+
fullPath: FileHandler.joinPaths(folderPath, fileName)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async get(domain, path)
|
|
37
|
+
{
|
|
38
|
+
if(!this.enabled){
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
let cacheInfo = this.generateCacheKey(domain, path);
|
|
42
|
+
if(!FileHandler.exists(cacheInfo.fullPath)){
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
let cachedContent = FileHandler.readFile(cacheInfo.fullPath);
|
|
46
|
+
if(!cachedContent){
|
|
47
|
+
Logger.debug('Failed to read cached file: '+cacheInfo.fullPath);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return cachedContent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async set(domain, path, content)
|
|
54
|
+
{
|
|
55
|
+
if(!this.enabled){
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
let cacheInfo = this.generateCacheKey(domain, path);
|
|
59
|
+
if(!FileHandler.createFolder(cacheInfo.folderPath)){
|
|
60
|
+
Logger.error('Failed to create cache folder: '+cacheInfo.folderPath);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if(!FileHandler.writeFile(cacheInfo.fullPath, content)){
|
|
64
|
+
Logger.error('Failed to write cache file: '+cacheInfo.fullPath);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async delete(domain, path)
|
|
71
|
+
{
|
|
72
|
+
let cacheByDomains = this.fetchCachePathsByDomain(domain, path);
|
|
73
|
+
for(let cacheInfo of cacheByDomains){
|
|
74
|
+
if(!FileHandler.exists(cacheInfo.fullPath)){
|
|
75
|
+
Logger.debug('File does not exist: '+cacheInfo.fullPath);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if(!FileHandler.remove(cacheInfo.fullPath)){
|
|
79
|
+
Logger.error('Failed to delete cache file: '+cacheInfo.fullPath);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fetchCachePathsByDomain(domain, path)
|
|
87
|
+
{
|
|
88
|
+
let cacheByDomains = [];
|
|
89
|
+
if(domain && 'default' !== domain){
|
|
90
|
+
cacheByDomains.push(this.generateCacheKey(domain, path));
|
|
91
|
+
return cacheByDomains;
|
|
92
|
+
}
|
|
93
|
+
let cachedDomainFolders = FileHandler.readFolder(this.cacheBasePath);
|
|
94
|
+
for(let cachedDomainFolder of cachedDomainFolders) {
|
|
95
|
+
cacheByDomains.push(this.generateCacheKey(cachedDomainFolder, path));
|
|
96
|
+
}
|
|
97
|
+
return cacheByDomains;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async clear()
|
|
101
|
+
{
|
|
102
|
+
if(!FileHandler.exists(this.cacheBasePath)){
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if(!FileHandler.remove(this.cacheBasePath)){
|
|
106
|
+
Logger.error('Failed to clear cache folder: '+this.cacheBasePath);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
isEnabled()
|
|
113
|
+
{
|
|
114
|
+
return this.enabled;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
enable()
|
|
118
|
+
{
|
|
119
|
+
this.enabled = true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
disable()
|
|
123
|
+
{
|
|
124
|
+
this.enabled = false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports.CacheManager = CacheManager;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - CMS - CacheRoutesHandler
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class CacheRoutesHandler
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
constructor(props)
|
|
13
|
+
{
|
|
14
|
+
this.router = sc.get(props, 'router', false);
|
|
15
|
+
this.dataServer = sc.get(props, 'dataServer', false);
|
|
16
|
+
/** @type {CacheManager} **/
|
|
17
|
+
this.cacheManager = sc.get(props, 'cacheManager', false);
|
|
18
|
+
this.rootPath = sc.get(props, 'rootPath', '');
|
|
19
|
+
this.cacheCleanPath = '/cache-clean';
|
|
20
|
+
this.cacheCleanRoute = this.rootPath+this.cacheCleanPath;
|
|
21
|
+
this.setupRoutes();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setupRoutes()
|
|
25
|
+
{
|
|
26
|
+
if(!this.cacheManager){
|
|
27
|
+
Logger.error('Cache Manager not found on CacheRoutesHandler.');
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if(!this.cacheManager.isEnabled()){
|
|
31
|
+
Logger.debug('Cache Manager not enabled.');
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if(!this.router){
|
|
35
|
+
Logger.error('Router not found on CacheRoutesHandler.');
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
this.router.adminRouter.post(
|
|
39
|
+
this.cacheCleanPath,
|
|
40
|
+
this.router.isAuthenticated.bind(this.router),
|
|
41
|
+
async (req, res) => {
|
|
42
|
+
return await this.processCacheClean(req, res);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async processCacheClean(req, res)
|
|
49
|
+
{
|
|
50
|
+
if(!this.cacheManager){
|
|
51
|
+
return res.json({error: 'Cache manager not available'});
|
|
52
|
+
}
|
|
53
|
+
let routeId = sc.get(req.body, 'routeId', '');
|
|
54
|
+
if(!routeId){
|
|
55
|
+
return res.json({error: 'Route ID is required'});
|
|
56
|
+
}
|
|
57
|
+
let routesEntity = this.dataServer.getEntity('routes');
|
|
58
|
+
if(!routesEntity){
|
|
59
|
+
return res.json({error: 'Routes entity not found'});
|
|
60
|
+
}
|
|
61
|
+
let route = await routesEntity.loadById(routeId);
|
|
62
|
+
if(!route){
|
|
63
|
+
return res.json({error: 'Route not found'});
|
|
64
|
+
}
|
|
65
|
+
let domain = sc.get(route, 'domain', '');
|
|
66
|
+
let path = route.path;
|
|
67
|
+
let cleanResult = await this.cacheManager.delete(domain, path);
|
|
68
|
+
if(!cleanResult){
|
|
69
|
+
return res.json({error: 'Failed to clean cache'});
|
|
70
|
+
}
|
|
71
|
+
return res.redirect(this.rootPath+'/routes/view'+'?id='+routeId+'&result=success');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports.CacheRoutesHandler = CacheRoutesHandler;
|
|
@@ -26,17 +26,19 @@ class CmsPagesRouteManager
|
|
|
26
26
|
async populateViewFields(event)
|
|
27
27
|
{
|
|
28
28
|
if('cms_pages' !== event.driverResource.id()){
|
|
29
|
-
return;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if(!event.loadedEntity || !event.loadedEntity.route_id){
|
|
32
|
+
event.renderedViewProperties.routePath = '';
|
|
33
|
+
event.renderedViewProperties.routeDomain = '';
|
|
34
|
+
return true;
|
|
30
35
|
}
|
|
31
36
|
let routesRepository = this.dataServer.getEntity('routes');
|
|
32
37
|
if(!routesRepository){
|
|
33
38
|
Logger.error('Routes repository not found.');
|
|
34
39
|
return false;
|
|
35
40
|
}
|
|
36
|
-
let existingRoute = await routesRepository.
|
|
37
|
-
router: 'cmsPages',
|
|
38
|
-
cms_page_id: Number(event.renderedViewProperties.id)
|
|
39
|
-
});
|
|
41
|
+
let existingRoute = await routesRepository.loadById(event.loadedEntity.route_id);
|
|
40
42
|
event.renderedViewProperties.routePath = sc.get(existingRoute, 'path', '');
|
|
41
43
|
event.renderedViewProperties.routeDomain = sc.get(existingRoute, 'domain', '');
|
|
42
44
|
return true;
|
|
@@ -45,17 +47,19 @@ class CmsPagesRouteManager
|
|
|
45
47
|
async populateEditFields(event)
|
|
46
48
|
{
|
|
47
49
|
if('cms_pages' !== event.driverResource.id()){
|
|
48
|
-
return;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if(!event.loadedEntity || !event.loadedEntity.route_id){
|
|
53
|
+
event.renderedEditProperties.routePath = '';
|
|
54
|
+
event.renderedEditProperties.routeDomain = '';
|
|
55
|
+
return true;
|
|
49
56
|
}
|
|
50
57
|
let routesRepository = this.dataServer.getEntity('routes');
|
|
51
58
|
if(!routesRepository){
|
|
52
59
|
Logger.error('Routes repository not found.');
|
|
53
60
|
return false;
|
|
54
61
|
}
|
|
55
|
-
let existingRoute = await routesRepository.
|
|
56
|
-
router: 'cmsPages',
|
|
57
|
-
cms_page_id: Number(event.renderedEditProperties.idValue)
|
|
58
|
-
});
|
|
62
|
+
let existingRoute = await routesRepository.loadById(event.loadedEntity.route_id);
|
|
59
63
|
event.renderedEditProperties.routePath = sc.get(existingRoute, 'path', '');
|
|
60
64
|
event.renderedEditProperties.routeDomain = sc.get(existingRoute, 'domain', '');
|
|
61
65
|
return true;
|
|
@@ -73,22 +77,30 @@ class CmsPagesRouteManager
|
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
79
|
let cmsPageId = Number(entityData.id);
|
|
76
|
-
let
|
|
77
|
-
let patchData = {
|
|
80
|
+
let routePatchData = {
|
|
78
81
|
path: sc.get(req.body, 'routePath', this.generateDefaultRoutePath(entityData)),
|
|
79
82
|
router: sc.get(req.body, 'routeRouter', 'cmsPages'),
|
|
80
|
-
cms_page_id: cmsPageId,
|
|
81
83
|
cache_ttl_seconds: Number(sc.get(req.body, 'routeCacheTtl', 3600)),
|
|
82
84
|
enabled: Number(sc.get(req.body, 'routeEnabled', 1)),
|
|
83
85
|
domain: sc.get(req.body, 'routeDomain', null)
|
|
84
86
|
};
|
|
85
|
-
let
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
let routeResult = false;
|
|
88
|
+
if(entityData.route_id){
|
|
89
|
+
routeResult = await routesRepository.updateById(entityData.route_id, routePatchData);
|
|
90
|
+
}
|
|
91
|
+
if(!routeResult){
|
|
92
|
+
routeResult = await routesRepository.create(routePatchData);
|
|
93
|
+
if(routeResult){
|
|
94
|
+
let pagesRepository = this.dataServer.getEntity('cms_pages');
|
|
95
|
+
if(pagesRepository){
|
|
96
|
+
await pagesRepository.updateById(cmsPageId, {route_id: routeResult.id});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if(!routeResult){
|
|
101
|
+
Logger.error('Route could not be saved.', routePatchData);
|
|
90
102
|
}
|
|
91
|
-
return
|
|
103
|
+
return routeResult;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
generateDefaultRoutePath(pageData)
|