@reldens/cms 0.7.0 → 0.9.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.
@@ -86,7 +86,6 @@
86
86
  }
87
87
 
88
88
  & .button {
89
-
90
89
  padding: 0.5rem 1rem;
91
90
  border: none;
92
91
  border-radius: 4px;
@@ -94,44 +93,44 @@
94
93
  cursor: pointer;
95
94
  text-decoration: none;
96
95
 
97
- &-primary {
98
- color: var(--white);
99
- background-color: var(--lightBlue);
100
-
101
- &:hover {
102
- background-color: var(--lightBlue);
103
- }
96
+ &:disabled {
97
+ background-color: var(--grey) !important;
104
98
  }
99
+ }
105
100
 
106
- &-secondary {
107
- color: var(--white);
108
- background-color: var(--grey);
101
+ & .button-primary {
102
+ color: var(--white);
103
+ background-color: var(--lightBlue);
109
104
 
110
- &:hover {
111
- background-color: var(--grey);
112
- }
105
+ &:hover {
106
+ background-color: var(--lightBlue);
113
107
  }
108
+ }
114
109
 
115
- &-warning {
116
- color: var(--white);
117
- background-color: var(--orange);
110
+ & .button-secondary {
111
+ color: var(--white);
112
+ background-color: var(--grey);
118
113
 
119
- &:hover {
120
- background-color: var(--orange);
121
- }
114
+ &:hover {
115
+ background-color: var(--grey);
122
116
  }
117
+ }
123
118
 
124
- &-danger {
125
- color: var(--white);
126
- background-color: var(--red);
119
+ & .button-warning {
120
+ color: var(--white);
121
+ background-color: var(--orange);
127
122
 
128
- &:hover {
129
- background-color: var(--red);
130
- }
123
+ &:hover {
124
+ background-color: var(--orange);
131
125
  }
126
+ }
132
127
 
133
- &:disabled {
134
- background-color: var(--grey) !important;
128
+ & .button-danger {
129
+ color: var(--white);
130
+ background-color: var(--red);
131
+
132
+ &:hover {
133
+ background-color: var(--red);
135
134
  }
136
135
  }
137
136
 
@@ -556,7 +555,7 @@
556
555
  padding: 0.5rem;
557
556
  border: 1px solid #ccc;
558
557
  border-radius: 4px;
559
- min-width: 150px; /* Minimum width for input fields */
558
+ min-width: 150px;
560
559
  }
561
560
  }
562
561
 
@@ -17,7 +17,7 @@
17
17
  <div class="forms-container">
18
18
  <div class="row">
19
19
  <form name="install-form" id="install-form" class="install-form" action="/install" method="post">
20
- <h3 class="form-title">- Server Configuration -</h3>
20
+ <h3 class="form-title">Server Configuration</h3>
21
21
  <div class="input-box app-host">
22
22
  <label for="app-host">Host</label>
23
23
  <input type="text" name="app-host" id="app-host" value="{{app-host}}" class="required" required/>
@@ -30,10 +30,14 @@
30
30
  <label for="app-admin-path">Admin Panel Path</label>
31
31
  <input type="text" name="app-admin-path" id="app-admin-path" value="{{app-admin-path}}"/>
32
32
  </div>
33
+ <div class="input-box app-admin-secret">
34
+ <label for="app-admin-secret">Admin Panel Secret</label>
35
+ <input type="text" name="app-admin-secret" id="app-admin-secret" value="{{app-admin-secret}}"/>
36
+ </div>
33
37
  <div class="input-box app-error">
34
38
  <p class="error installation-process-failed">There was an error during the installation process.</p>
35
39
  </div>
36
- <h3 class="form-title">- Database Configuration -</h3>
40
+ <h3 class="form-title">Database Configuration</h3>
37
41
  <div class="input-box db-storage-driver">
38
42
  <label for="db-storage-driver">Storage Driver</label>
39
43
  <select name="db-storage-driver" id="db-storage-driver" class="required" required>
@@ -72,6 +76,23 @@
72
76
  <p class="error sql-file-not-found">SQL installation file not found.</p>
73
77
  <p class="error db-installation-process-failed">There was an error during the installation process.</p>
74
78
  </div>
79
+ <h3 class="form-title">Installation Options</h3>
80
+ <div class="input-box install-cms-tables">
81
+ <input type="checkbox" name="install-cms-tables" id="install-cms-tables" checked/>
82
+ <label for="install-cms-tables">Install default CMS pages tables</label>
83
+ </div>
84
+ <div class="input-box install-user-auth">
85
+ <input type="checkbox" name="install-user-auth" id="install-user-auth" checked/>
86
+ <label for="install-user-auth">Install users authentication</label>
87
+ </div>
88
+ <div class="input-box install-default-user">
89
+ <input type="checkbox" name="install-default-user" id="install-default-user" checked/>
90
+ <label for="install-default-user">Install default admin user (root@cms-admin.com / root)</label>
91
+ </div>
92
+ <div class="input-box install-default-homepage">
93
+ <input type="checkbox" name="install-default-homepage" id="install-default-homepage" checked/>
94
+ <label for="install-default-homepage">Install default homepage</label>
95
+ </div>
75
96
  <div class="input-box submit-container">
76
97
  <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
77
98
  <input id="install-submit-button" type="submit" value="Install"/>
@@ -17,6 +17,12 @@
17
17
  <div class="forms-container">
18
18
  <div class="row">
19
19
  Installation completed!
20
+ <p>
21
+ Default data<br/>
22
+ username: root
23
+ email: root@cms-admin.com
24
+ password: root
25
+ </p>
20
26
  </div>
21
27
  </div>
22
28
  </div>
@@ -380,6 +380,9 @@ class AdminManager
380
380
  this.adminRouter = this.applicationFramework.Router();
381
381
  // apply session middleware only to /admin routes:
382
382
  if(this.session){
383
+ if(!this.secret){
384
+ Logger.warning('Admin Manager "secret" key was not provided.');
385
+ }
383
386
  this.adminRouter.use(this.session({secret: this.secret, resave: false, saveUninitialized: true}));
384
387
  }
385
388
  this.adminRouter.use(this.bodyParser.json());
@@ -636,6 +639,26 @@ class AdminManager
636
639
  return fileNames.join(property.isArray);
637
640
  }
638
641
 
642
+ formatDateTimeForEdit(dateValue, isDisabled)
643
+ {
644
+ if(!dateValue || '' === dateValue){
645
+ return '';
646
+ }
647
+ let date = new Date(dateValue);
648
+ if(isNaN(date.getTime())){
649
+ return '';
650
+ }
651
+ if(isDisabled){
652
+ return sc.formatDate(date);
653
+ }
654
+ let year = date.getFullYear();
655
+ let month = String(date.getMonth() + 1).padStart(2, '0');
656
+ let day = String(date.getDate()).padStart(2, '0');
657
+ let hours = String(date.getHours()).padStart(2, '0');
658
+ let minutes = String(date.getMinutes()).padStart(2, '0');
659
+ return year+'-'+month+'-'+day+'T'+hours+':'+minutes;
660
+ }
661
+
639
662
  async generateEditRouteContent(req, driverResource, entityPath)
640
663
  {
641
664
  let idProperty = this.fetchEntityIdPropertyKey(driverResource);
@@ -660,6 +683,7 @@ class AdminManager
660
683
  if(resourceProperty.isUpload && loadedEntity){
661
684
  isRequired = '';
662
685
  }
686
+ let inputType = this.getInputType(resourceProperty, fieldDisabled);
663
687
  renderedEditProperties[propertyKey] = await this.render(
664
688
  this.adminFilesContents.fields.edit[this.propertyType(resourceProperty, 'edit')],
665
689
  {
@@ -667,11 +691,13 @@ class AdminManager
667
691
  fieldValue: await this.generatePropertyEditRenderedValue(
668
692
  loadedEntity,
669
693
  propertyKey,
670
- resourceProperty
694
+ resourceProperty,
695
+ fieldDisabled
671
696
  ),
672
697
  fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
673
698
  required: isRequired,
674
- multiple: resourceProperty.isArray ? ' multiple="multiple"' : ''
699
+ multiple: resourceProperty.isArray ? ' multiple="multiple"' : '',
700
+ inputType
675
701
  }
676
702
  );
677
703
  }
@@ -679,6 +705,14 @@ class AdminManager
679
705
  return await this.renderRoute(renderedView, this.adminContents.sideBar);
680
706
  }
681
707
 
708
+ getInputType(resourceProperty, fieldDisabled)
709
+ {
710
+ if('datetime' === resourceProperty.type && !fieldDisabled){
711
+ return 'datetime-local';
712
+ }
713
+ return 'text';
714
+ }
715
+
682
716
  async loadEntityById(driverResource, id)
683
717
  {
684
718
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
@@ -936,13 +970,16 @@ class AdminManager
936
970
  return {fieldValue, fieldName};
937
971
  }
938
972
 
939
- async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty)
973
+ async generatePropertyEditRenderedValue(entity, propertyKey, resourceProperty, isFieldDisabled)
940
974
  {
941
975
  let entityPropertyValue = sc.get(entity, propertyKey, null);
942
976
  let fieldValue = (0 === entityPropertyValue ? '0' : entityPropertyValue || '').toString();
943
977
  if('boolean' === resourceProperty.type){
944
978
  fieldValue = '1' === fieldValue || 'true' === fieldValue ? ' checked="checked"' : '';
945
979
  }
980
+ if('datetime' === resourceProperty.type){
981
+ fieldValue = this.formatDateTimeForEdit(entityPropertyValue, isFieldDisabled);
982
+ }
946
983
  if('reference' === resourceProperty.type){
947
984
  let relationDriverResource = this.resourcesByReference[resourceProperty.reference];
948
985
  let relation = this.relations[resourceProperty.reference];
@@ -4,7 +4,6 @@
4
4
  *
5
5
  */
6
6
 
7
- const { LoadedEntitiesProcessor } = require('./loaded-entities-processor');
8
7
  const { FileHandler } = require('@reldens/server-utils');
9
8
  const { Logger, sc } = require('@reldens/utils');
10
9
 
@@ -30,7 +29,7 @@ class EntitiesLoader
30
29
  }
31
30
  try {
32
31
  let {rawRegisteredEntities, entitiesConfig, entitiesTranslations} = require(entitiesPath);
33
- return LoadedEntitiesProcessor.process(rawRegisteredEntities, entitiesTranslations, entitiesConfig);
32
+ return {rawRegisteredEntities, entitiesTranslations, entitiesConfig};
34
33
  } catch(error){
35
34
  Logger.error('Failed to load generated entities: ' + error.message);
36
35
  }
package/lib/frontend.js CHANGED
@@ -19,6 +19,10 @@ class Frontend
19
19
  this.projectRoot = sc.get(props, 'projectRoot', './');
20
20
  this.templatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
21
21
  this.publicPath = FileHandler.joinPaths(this.projectRoot, 'public');
22
+ this.partialsExtensions = sc.get(props, 'partialsExtensions', ['.html', '.mustache', '.template']);
23
+ this.partialsCache = {};
24
+ this.domainPartialsCache = new Map();
25
+ this.domainTemplatesMap = new Map();
22
26
  this.error = false;
23
27
  }
24
28
 
@@ -36,6 +40,8 @@ class Frontend
36
40
  Logger.error('Public folder not found: '+this.publicPath);
37
41
  return false;
38
42
  }
43
+ await this.loadPartials();
44
+ await this.setupDomainTemplates();
39
45
  this.setupStaticAssets();
40
46
  this.app.get('*', async (req, res) => {
41
47
  return await this.handleRequest(req, res);
@@ -43,6 +49,101 @@ class Frontend
43
49
  return true;
44
50
  }
45
51
 
52
+ async loadPartials()
53
+ {
54
+ let partialsPath = FileHandler.joinPaths(this.templatesPath, 'partials');
55
+ FileHandler.createFolder(partialsPath);
56
+ let partialFiles = FileHandler.getFilesInFolder(partialsPath, this.partialsExtensions);
57
+ for(let file of partialFiles){
58
+ let partialName = '';
59
+ for(let extension of this.partialsExtensions){
60
+ if(file.endsWith(extension)){
61
+ partialName = file.replace(extension, '');
62
+ break;
63
+ }
64
+ }
65
+ let partialPath = FileHandler.joinPaths(partialsPath, file);
66
+ let partialContent = FileHandler.readFile(partialPath);
67
+ this.partialsCache[partialName] = partialContent.toString();
68
+ }
69
+ }
70
+
71
+ async loadDomainPartials(domain, domainPath)
72
+ {
73
+ let domainPartialsPath = FileHandler.joinPaths(domainPath, 'partials');
74
+ if(!FileHandler.exists(domainPartialsPath)){
75
+ return;
76
+ }
77
+ let domainPartials = {};
78
+ let partialFiles = FileHandler.getFilesInFolder(domainPartialsPath, this.partialsExtensions);
79
+ for(let file of partialFiles){
80
+ let partialName = '';
81
+ for(let extension of this.partialsExtensions){
82
+ if(file.endsWith(extension)){
83
+ partialName = file.replace(extension, '');
84
+ break;
85
+ }
86
+ }
87
+ let partialPath = FileHandler.joinPaths(domainPartialsPath, file);
88
+ let partialContent = FileHandler.readFile(partialPath);
89
+ domainPartials[partialName] = partialContent.toString();
90
+ }
91
+ this.domainPartialsCache.set(domain, domainPartials);
92
+ }
93
+
94
+ async setupDomainTemplates()
95
+ {
96
+ let domainsPath = FileHandler.joinPaths(this.templatesPath, 'domains');
97
+ if(!FileHandler.exists(domainsPath)){
98
+ return;
99
+ }
100
+ let domainFolders = FileHandler.fetchSubFoldersList(domainsPath);
101
+ for(let domain of domainFolders){
102
+ let domainPath = FileHandler.joinPaths(domainsPath, domain);
103
+ this.domainTemplatesMap.set(domain, domainPath);
104
+ await this.loadDomainPartials(domain, domainPath);
105
+ }
106
+ }
107
+
108
+ getDomainFromRequest(req)
109
+ {
110
+ let host = req.get('host');
111
+ if(!host){
112
+ return false;
113
+ }
114
+ return host.split(':')[0];
115
+ }
116
+
117
+ getPartialsForDomain(domain)
118
+ {
119
+ let domainPartials = this.domainPartialsCache.get(domain);
120
+ if(!domainPartials){
121
+ return this.partialsCache;
122
+ }
123
+ return Object.assign({}, this.partialsCache, domainPartials);
124
+ }
125
+
126
+ findTemplatePathForDomain(templateName, domain)
127
+ {
128
+ if(!domain){
129
+ return this.getDefaultTemplatePath(templateName);
130
+ }
131
+ let domainPath = this.domainTemplatesMap.get(domain);
132
+ if(!domainPath){
133
+ return this.getDefaultTemplatePath(templateName);
134
+ }
135
+ let domainTemplatePath = FileHandler.joinPaths(domainPath, templateName + '.html');
136
+ if(!FileHandler.exists(domainTemplatePath)){
137
+ return this.getDefaultTemplatePath(templateName);
138
+ }
139
+ return domainTemplatePath;
140
+ }
141
+
142
+ getDefaultTemplatePath(templateName)
143
+ {
144
+ return FileHandler.joinPaths(this.templatesPath, templateName + '.html');
145
+ }
146
+
46
147
  setupStaticAssets()
47
148
  {
48
149
  if(!this.app || !this.appServerFactory || !this.publicPath){
@@ -62,26 +163,32 @@ class Frontend
62
163
  return res.status(404).send('');
63
164
  }
64
165
  try {
166
+ let domain = this.getDomainFromRequest(req);
65
167
  let route = await this.findRouteByPath(path);
66
168
  if(route){
67
- return await this.renderContentFromRoute(res, route);
169
+ Logger.debug('Found route for path: '+path, route);
170
+ return await this.renderContentFromRoute(res, route, domain);
68
171
  }
69
172
  let pathSegments = path.split('/').filter(segment => segment !== '');
70
173
  if(0 < pathSegments.length){
71
174
  let entityResult = await this.findEntityByPath(pathSegments);
72
175
  if(entityResult){
176
+ Logger.debug('Found entity for path segments: '+pathSegments.join('/'));
73
177
  return await this.renderContentFromEntity(
74
178
  res,
75
179
  entityResult.entity,
76
- entityResult.entityName
180
+ entityResult.entityName,
181
+ domain
77
182
  );
78
183
  }
79
184
  }
80
- let templatePath = this.findTemplateByPath(path);
185
+ let templatePath = this.findTemplateByPath(path, domain);
81
186
  if(templatePath){
82
- return await this.renderTemplateOnly(res, templatePath);
187
+ Logger.debug('Found template for path: '+path+' at: '+templatePath);
188
+ return await this.renderTemplateOnly(res, templatePath, domain);
83
189
  }
84
- return await this.renderNotFoundPage(res);
190
+ Logger.debug('No template found for path: '+path+', rendering 404');
191
+ return await this.renderNotFoundPage(res, domain);
85
192
  } catch (error) {
86
193
  Logger.error('Request handling error: '+error.message);
87
194
  return res.status(500).send('Internal server error');
@@ -90,32 +197,41 @@ class Frontend
90
197
 
91
198
  async findRouteByPath(path)
92
199
  {
93
- if('/' === path){
94
- path = '/home';
95
- }
96
200
  let routesEntity = this.dataServer.getEntity('routes');
97
201
  if(!routesEntity){
202
+ Logger.error('Routes entity not found in dataServer');
98
203
  return false;
99
204
  }
100
- return await routesEntity.loadOneBy('path', path);
205
+ let route = await routesEntity.loadOneBy('path', path);
206
+ if(route){
207
+ return route;
208
+ }
209
+ if('/' === path){
210
+ return await routesEntity.loadOneBy('path', '/home');
211
+ }
212
+ return false;
101
213
  }
102
214
 
103
215
  async findEntityByPath(pathSegments)
104
216
  {
105
217
  if(1 > pathSegments.length){
218
+ Logger.debug('No path segments provided');
106
219
  return false;
107
220
  }
108
221
  let entityName = pathSegments[0];
109
222
  let entityId = 2 > pathSegments.length ? false : pathSegments[1];
110
223
  if(!entityId){
224
+ Logger.debug('No entity ID in path segments');
111
225
  return false;
112
226
  }
113
227
  let entity = this.dataServer.getEntity(entityName);
114
228
  if(!entity){
229
+ Logger.debug('Entity not found: '+entityName);
115
230
  return false;
116
231
  }
117
232
  let loadedEntity = await entity.loadById(entityId);
118
233
  if(!loadedEntity){
234
+ Logger.debug('Entity not loaded by ID: '+entityId);
119
235
  return false;
120
236
  }
121
237
  return {
@@ -124,7 +240,7 @@ class Frontend
124
240
  };
125
241
  }
126
242
 
127
- findTemplateByPath(path)
243
+ findTemplateByPath(path, domain)
128
244
  {
129
245
  if('/' === path){
130
246
  path = '/index';
@@ -135,32 +251,36 @@ class Frontend
135
251
  templatePath = templatePath.startsWith('/')
136
252
  ? templatePath.substring(1)
137
253
  : templatePath;
138
- let fullPath = FileHandler.joinPaths(this.templatesPath, templatePath + '.html');
254
+ let fullPath = this.findTemplatePathForDomain(templatePath, domain);
139
255
  if(FileHandler.exists(fullPath)){
140
256
  return fullPath;
141
257
  }
142
258
  return false;
143
259
  }
144
260
 
145
- async renderContentFromRoute(res, route)
261
+ async renderContentFromRoute(res, route, domain)
146
262
  {
147
263
  if(!route.router || !route.content_id){
148
- return await this.renderNotFoundPage(res);
264
+ Logger.debug('Route missing router or content_id');
265
+ return await this.renderNotFoundPage(res, domain);
149
266
  }
150
267
  let entity = this.dataServer.getEntity(route.router);
151
268
  if(!entity){
152
- return await this.renderNotFoundPage(res);
269
+ Logger.debug('Entity not found: '+route.router);
270
+ return await this.renderNotFoundPage(res, domain);
153
271
  }
154
272
  let content = await entity.loadById(route.content_id);
155
273
  if(!content){
156
- return await this.renderNotFoundPage(res);
274
+ Logger.debug('Content not found for ID: '+route.content_id+' in entity: '+route.router);
275
+ return await this.renderNotFoundPage(res, domain);
157
276
  }
158
277
  let templateName = content.template || route.router;
159
- let templatePath = FileHandler.joinPaths(this.templatesPath, templateName + '.html');
278
+ let templatePath = this.findTemplatePathForDomain(templateName, domain);
160
279
  if(!FileHandler.exists(templatePath)){
161
- templatePath = FileHandler.joinPaths(this.templatesPath, 'page.html');
280
+ templatePath = this.findTemplatePathForDomain('page', domain);
162
281
  if(!FileHandler.exists(templatePath)){
163
- return await this.renderNotFoundPage(res);
282
+ Logger.debug('Neither template found: '+templateName+'.html nor page.html');
283
+ return await this.renderNotFoundPage(res, domain);
164
284
  }
165
285
  }
166
286
  let template = FileHandler.readFile(templatePath).toString();
@@ -169,17 +289,18 @@ class Frontend
169
289
  ...content,
170
290
  current_year: new Date().getFullYear()
171
291
  };
172
- let rendered = mustache.render(template, data);
292
+ let partials = this.getPartialsForDomain(domain);
293
+ let rendered = mustache.render(template, data, partials);
173
294
  return res.send(rendered);
174
295
  }
175
296
 
176
- async renderContentFromEntity(res, entity, entityName)
297
+ async renderContentFromEntity(res, entity, entityName, domain)
177
298
  {
178
- let templatePath = FileHandler.joinPaths(this.templatesPath, entityName + '.html');
299
+ let templatePath = this.findTemplatePathForDomain(entityName, domain);
179
300
  if(!FileHandler.exists(templatePath)){
180
- templatePath = FileHandler.joinPaths(this.templatesPath, 'page.html');
301
+ templatePath = this.findTemplatePathForDomain('page', domain);
181
302
  if(!FileHandler.exists(templatePath)){
182
- return await this.renderNotFoundPage(res);
303
+ return await this.renderNotFoundPage(res, domain);
183
304
  }
184
305
  }
185
306
  let template = FileHandler.readFile(templatePath).toString();
@@ -188,24 +309,26 @@ class Frontend
188
309
  title: entity.title || entity.name || entityName,
189
310
  current_year: new Date().getFullYear()
190
311
  };
191
- let rendered = mustache.render(template, data);
312
+ let partials = this.getPartialsForDomain(domain);
313
+ let rendered = mustache.render(template, data, partials);
192
314
  return res.send(rendered);
193
315
  }
194
316
 
195
- async renderTemplateOnly(res, templatePath)
317
+ async renderTemplateOnly(res, templatePath, domain)
196
318
  {
197
319
  let template = FileHandler.readFile(templatePath).toString();
198
320
  let data = {
199
321
  title: 'Page Title',
200
322
  current_year: new Date().getFullYear()
201
323
  };
202
- let rendered = mustache.render(template, data);
324
+ let partials = this.getPartialsForDomain(domain);
325
+ let rendered = mustache.render(template, data, partials);
203
326
  return res.send(rendered);
204
327
  }
205
328
 
206
- async renderNotFoundPage(res)
329
+ async renderNotFoundPage(res, domain)
207
330
  {
208
- let templatePath = FileHandler.joinPaths(this.templatesPath, '404.html');
331
+ let templatePath = this.findTemplatePathForDomain('404', domain);
209
332
  if(!FileHandler.exists(templatePath)){
210
333
  return res.status(404).send('Page not found');
211
334
  }
@@ -214,7 +337,8 @@ class Frontend
214
337
  title: '404 - Page Not Found',
215
338
  current_year: new Date().getFullYear()
216
339
  };
217
- let rendered = mustache.render(template, data);
340
+ let partials = this.getPartialsForDomain(domain);
341
+ let rendered = mustache.render(template, data, partials);
218
342
  return res.status(404).send(rendered);
219
343
  }
220
344