@reldens/cms 0.15.0 → 0.16.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.
@@ -13,6 +13,24 @@ class TemplateEngine
13
13
  {
14
14
  this.renderEngine = sc.get(props, 'renderEngine', false);
15
15
  this.dataServer = sc.get(props, 'dataServer', false);
16
+ this.getPartials = sc.get(props, 'getPartials', false);
17
+ this.currentDomain = '';
18
+ }
19
+
20
+ setCurrentDomain(domain)
21
+ {
22
+ this.currentDomain = domain;
23
+ }
24
+
25
+ async processAllTemplateFunctions(template)
26
+ {
27
+ return await this.processCustomPartials(
28
+ await this.processLoopCollections(
29
+ await this.processSingleFieldCollections(
30
+ await this.processEntityFunctions(template)
31
+ )
32
+ )
33
+ );
16
34
  }
17
35
 
18
36
  async render(template, data, partials)
@@ -26,37 +44,43 @@ class TemplateEngine
26
44
  return '';
27
45
  }
28
46
  return this.renderEngine.render(
29
- await this.processLoopCollections(
30
- await this.processSingleFieldCollections(
31
- await this.processEntityFunctions(template)
32
- )
33
- ),
47
+ this.unescapeHtml(await this.processAllTemplateFunctions(template)),
34
48
  data,
35
49
  partials
36
50
  );
37
51
  }
38
52
 
53
+ unescapeHtml(text)
54
+ {
55
+ return text
56
+ .replace(/"/g, '"')
57
+ .replace(/'/g, "'")
58
+ .replace(/'/g, "'")
59
+ .replace(/=/g, '=')
60
+ .replace(///g, '/')
61
+ .replace(/&lt;/g, '<')
62
+ .replace(/&gt;/g, '>')
63
+ .replace(/&amp;/g, '&');
64
+ }
65
+
39
66
  getEntityRegex()
40
67
  {
41
- return /\{\{\s*entity\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?\)\s*\}\}/g;
68
+ return /<entity\s+name="([^"]+)"(?:\s+field="([^"]+)"\s+value="([^"]+)"|\s+id="([^"]+)")?\s*\/?>/g;
42
69
  }
43
70
 
44
71
  getSingleFieldCollectionRegex()
45
72
  {
46
- return /\{\{\s*collection\(\s*['"]([^'"]+)['"]\s*,\s*(\{[^}]*\})\s*,\s*['"]([^'"]+)['"]\s*\)\s*\}\}/g;
73
+ return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?\s+field=(['"])([^'"]+)\5(?:\s+data=(['"])([^'"]*)\7)?\s*\/>/g;
47
74
  }
48
75
 
49
76
  getLoopCollectionStartRegex()
50
77
  {
51
- return /\{\{\s*#collection\(\s*['"]([^'"]+)['"]\s*,\s*(\{[^}]*\})\s*\)\s*\}\}/g;
78
+ return /<collection\s+name=(['"])([^'"]+)\1(?:\s+filters=(['"])([^'"]*)\3)?(?:\s+data=(['"])([^'"]*)\5)?\s*>/g;
52
79
  }
53
80
 
54
- getLoopCollectionEndRegex(tableName)
81
+ getLoopCollectionEndRegex()
55
82
  {
56
- return new RegExp('\\{\\{\\s*\\/collection\\(\\s*[\'"]'
57
- +this.escapeRegex(tableName)
58
- +'[\'"]\\s*\\)\\s*\\}\\}'
59
- );
83
+ return new RegExp('<\\/collection>');
60
84
  }
61
85
 
62
86
  async processEntityFunctions(template)
@@ -64,10 +88,18 @@ class TemplateEngine
64
88
  let processedTemplate = template;
65
89
  for(let match of template.matchAll(this.getEntityRegex())){
66
90
  let tableName = match[1];
67
- let identifier = match[2];
68
- let identifierField = match[3] || 'id';
69
- let entityData = await this.fetchEntityForTemplate(tableName, identifier, identifierField);
70
- processedTemplate = processedTemplate.replace(match[0], sc.get(entityData, 'content', ''));
91
+ let field = sc.get(match, '2', 'id');
92
+ let value = sc.get(match, '3', sc.get(match, '4', ''));
93
+ if(!value){
94
+ Logger.warning('Entity tag missing value: '+match[0]);
95
+ continue;
96
+ }
97
+ processedTemplate = processedTemplate.replace(
98
+ match[0],
99
+ await this.processAllTemplateFunctions(
100
+ sc.get(await this.fetchEntityForTemplate(tableName, value, field), 'content', '')
101
+ )
102
+ );
71
103
  }
72
104
  return processedTemplate;
73
105
  }
@@ -76,13 +108,14 @@ class TemplateEngine
76
108
  {
77
109
  let processedTemplate = template;
78
110
  for(let match of template.matchAll(this.getSingleFieldCollectionRegex())){
79
- let tableName = match[1];
80
- let filtersJson = match[2];
81
- let fieldName = match[3];
111
+ let tableName = match[2];
112
+ let filtersJson = sc.get(match, '4', '{}');
113
+ let fieldName = match[6];
114
+ let queryOptionsJson = sc.get(match, '8', '{}');
82
115
  processedTemplate = processedTemplate.replace(
83
116
  match[0],
84
117
  this.extractFieldValues(
85
- await this.fetchCollectionForTemplate(tableName, filtersJson),
118
+ await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson),
86
119
  fieldName
87
120
  )
88
121
  );
@@ -96,17 +129,172 @@ class TemplateEngine
96
129
  let matches = [...template.matchAll(this.getLoopCollectionStartRegex())];
97
130
  for(let i = matches.length - 1; i >= 0; i--){
98
131
  let startMatch = matches[i];
99
- let tableName = startMatch[1];
100
- let filtersJson = startMatch[2];
101
- let loopResult = await this.processLoopCollection(processedTemplate, startMatch, tableName, filtersJson);
102
- if(false !== loopResult){
132
+ let loopResult = await this.processLoopCollection(
133
+ processedTemplate,
134
+ startMatch,
135
+ startMatch[2],
136
+ sc.get(startMatch, '4', '{}'),
137
+ sc.get(startMatch, '6', '{}')
138
+ );
139
+ if(loopResult){
103
140
  processedTemplate = loopResult;
104
141
  }
105
142
  }
106
143
  return processedTemplate;
107
144
  }
108
145
 
109
- async processLoopCollection(template, startMatch, tableName, filtersJson)
146
+ async processCustomPartials(template)
147
+ {
148
+ let processedTemplate = template;
149
+ let partialTags = this.findAllPartialTags(template);
150
+ for(let i = partialTags.length - 1; i >= 0; i--){
151
+ let tag = partialTags[i];
152
+ let partialContent = this.loadPartialTemplate(tag.name);
153
+ if(!partialContent){
154
+ Logger.warning('Partial template not found: ' + tag.name);
155
+ processedTemplate = processedTemplate.substring(0, tag.start) + '' + processedTemplate.substring(tag.end);
156
+ continue;
157
+ }
158
+ let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
159
+ let renderData = { vars: tag.attributes };
160
+ let partials = {[tag.name]: partialContent};
161
+ processedTemplate = processedTemplate.substring(0, tag.start) +
162
+ this.renderEngine.render(wrapperTemplate, renderData, partials) +
163
+ processedTemplate.substring(tag.end);
164
+ }
165
+ return processedTemplate;
166
+ }
167
+
168
+ findAllPartialTags(template)
169
+ {
170
+ let partialTags = [];
171
+ let searchPos = 0;
172
+ let partialTagName = '<partial';
173
+ for(let tagStart = template.indexOf(partialTagName, searchPos); -1 !== tagStart; tagStart = template.indexOf(partialTagName, searchPos)){
174
+ let tagEnd = this.findPartialTagEnd(template, tagStart);
175
+ if(-1 === tagEnd){
176
+ searchPos = tagStart + partialTagName.length;
177
+ continue;
178
+ }
179
+ let fullTag = template.substring(tagStart, tagEnd);
180
+ let nameMatch = fullTag.match(/name=["']([^"']+)["']/);
181
+ if(!nameMatch){
182
+ searchPos = tagStart + partialTagName.length;
183
+ continue;
184
+ }
185
+ let partialName = nameMatch[1];
186
+ let attributes = this.parsePartialAttributes(fullTag, partialName);
187
+ partialTags.push({
188
+ start: tagStart,
189
+ end: tagEnd,
190
+ name: partialName,
191
+ attributes: attributes,
192
+ fullTag: fullTag
193
+ });
194
+ searchPos = tagEnd;
195
+ }
196
+ return partialTags;
197
+ }
198
+
199
+ findPartialTagEnd(template, tagStart)
200
+ {
201
+ let inQuotes = false;
202
+ let quoteChar = '';
203
+ let selfCloseTag = '/>';
204
+ let openCloseTag = '</partial>';
205
+ for(let i = tagStart; i < template.length; i++){
206
+ let char = template[i];
207
+ if(!inQuotes && ('"' === char || "'" === char)){
208
+ inQuotes = true;
209
+ quoteChar = char;
210
+ continue;
211
+ }
212
+ if(inQuotes && char === quoteChar && '\\' !== template[i - 1]){
213
+ inQuotes = false;
214
+ quoteChar = '';
215
+ continue;
216
+ }
217
+ if(!inQuotes){
218
+ if(template.substring(i, i + selfCloseTag.length) === selfCloseTag){
219
+ return i + selfCloseTag.length;
220
+ }
221
+ if('>' === char){
222
+ let closeIndex = template.indexOf(openCloseTag, i);
223
+ if(-1 !== closeIndex){
224
+ return closeIndex + openCloseTag.length;
225
+ }
226
+ return i + 1;
227
+ }
228
+ }
229
+ }
230
+ return -1;
231
+ }
232
+
233
+ parsePartialAttributes(fullTag, partialName)
234
+ {
235
+ let namePattern = 'name=' + this.getQuotePattern(fullTag, partialName);
236
+ let nameIndex = fullTag.indexOf(namePattern);
237
+ if(-1 === nameIndex){
238
+ return {};
239
+ }
240
+ let attributesStart = nameIndex + namePattern.length;
241
+ let attributesEnd = fullTag.lastIndexOf('/>');
242
+ if(-1 === attributesEnd){
243
+ attributesEnd = fullTag.lastIndexOf('</partial>');
244
+ }
245
+ if(-1 === attributesEnd){
246
+ attributesEnd = fullTag.lastIndexOf('>');
247
+ }
248
+ if(-1 === attributesEnd || attributesEnd <= attributesStart){
249
+ return {};
250
+ }
251
+ let attributesString = fullTag.substring(attributesStart, attributesEnd).trim();
252
+ return this.extractAttributesObject(attributesString);
253
+ }
254
+
255
+ getQuotePattern(fullTag, partialName)
256
+ {
257
+ if(fullTag.includes('name="' + partialName + '"')){
258
+ return '"' + partialName + '"';
259
+ }
260
+ if(fullTag.includes("name='" + partialName + "'")){
261
+ return "'" + partialName + "'";
262
+ }
263
+ return '"' + partialName + '"';
264
+ }
265
+
266
+ extractAttributesObject(attributesString)
267
+ {
268
+ if(!attributesString){
269
+ return {};
270
+ }
271
+ let attributes = {};
272
+ let valueRegex = /(\w+)=(['"])((?:(?!\2)[^\\]|\\.)*)(\2)/g;
273
+ for(let match of attributesString.matchAll(valueRegex)){
274
+ attributes[match[1]] = match[3];
275
+ }
276
+ let booleanRegex = /\b(\w+)(?!\s*=)/g;
277
+ for(let match of attributesString.matchAll(booleanRegex)){
278
+ if(!sc.hasOwn(attributes, match[1])){
279
+ attributes[match[1]] = true;
280
+ }
281
+ }
282
+ return attributes;
283
+ }
284
+
285
+ loadPartialTemplate(partialName)
286
+ {
287
+ if(!this.getPartials){
288
+ return false;
289
+ }
290
+ let partials = this.getPartials(this.currentDomain);
291
+ if(sc.hasOwn(partials, partialName)){
292
+ return partials[partialName];
293
+ }
294
+ return false;
295
+ }
296
+
297
+ async processLoopCollection(template, startMatch, tableName, filtersJson, queryOptionsJson)
110
298
  {
111
299
  let startPos = startMatch.index;
112
300
  let startEnd = startPos + startMatch[0].length;
@@ -116,10 +304,9 @@ class TemplateEngine
116
304
  return false;
117
305
  }
118
306
  let endPos = startEnd + endMatch.index;
119
- let renderedContent = await this.renderCollectionLoop(
120
- template.substring(startEnd, endPos),
121
- await this.fetchCollectionForTemplate(tableName, filtersJson)
122
- );
307
+ let loopContent = template.substring(startEnd, endPos);
308
+ let collectionData = await this.fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson);
309
+ let renderedContent = await this.renderCollectionLoop(loopContent, collectionData);
123
310
  return template.substring(0, startPos) + renderedContent + template.substring(endPos + endMatch[0].length);
124
311
  }
125
312
 
@@ -127,11 +314,38 @@ class TemplateEngine
127
314
  {
128
315
  let renderedContent = '';
129
316
  for(let row of collectionData){
130
- renderedContent += this.renderEngine.render(loopContent, {row}, {});
317
+ renderedContent += await this.processPartialsInLoop(loopContent, row);
131
318
  }
132
319
  return renderedContent;
133
320
  }
134
321
 
322
+ async processPartialsInLoop(content, rowData)
323
+ {
324
+ let processedContent = content;
325
+ let partialTags = this.findAllPartialTags(content);
326
+ for(let i = partialTags.length - 1; i >= 0; i--){
327
+ let tag = partialTags[i];
328
+ let partialContent = this.loadPartialTemplate(tag.name);
329
+ if(!partialContent){
330
+ Logger.warning('Partial template not found: ' + tag.name);
331
+ processedContent = processedContent.substring(0, tag.start) + '' + processedContent.substring(tag.end);
332
+ continue;
333
+ }
334
+ if(sc.hasOwn(tag.attributes, 'row')){
335
+ let renderedPartial = this.renderEngine.render(partialContent, {row: rowData}, this.getPartials(this.currentDomain));
336
+ processedContent = processedContent.substring(0, tag.start) + renderedPartial + processedContent.substring(tag.end);
337
+ continue;
338
+ }
339
+ let wrapperTemplate = '{{#vars}}{{> ' + tag.name + '}}{{/vars}}';
340
+ let renderData = { vars: tag.attributes };
341
+ let partials = {[tag.name]: partialContent};
342
+ processedContent = processedContent.substring(0, tag.start) +
343
+ this.renderEngine.render(wrapperTemplate, renderData, partials) +
344
+ processedContent.substring(tag.end);
345
+ }
346
+ return this.renderEngine.render(processedContent, {row: rowData}, this.getPartials(this.currentDomain));
347
+ }
348
+
135
349
  extractFieldValues(collectionData, fieldName)
136
350
  {
137
351
  let fieldValues = '';
@@ -141,11 +355,6 @@ class TemplateEngine
141
355
  return fieldValues;
142
356
  }
143
357
 
144
- escapeRegex(string)
145
- {
146
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
147
- }
148
-
149
358
  async fetchEntityForTemplate(tableName, identifier, identifierField)
150
359
  {
151
360
  let entity = this.dataServer.getEntity(tableName);
@@ -156,20 +365,79 @@ class TemplateEngine
156
365
  return await entity.loadOneBy(identifierField, identifier);
157
366
  }
158
367
 
159
- async fetchCollectionForTemplate(tableName, filtersJson)
368
+ convertJsObjectToJson(jsObjectString)
369
+ {
370
+ if(!jsObjectString || '' === jsObjectString.trim()){
371
+ return '';
372
+ }
373
+ return jsObjectString.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
374
+ }
375
+
376
+ async fetchCollectionForTemplate(tableName, filtersJson, queryOptionsJson)
160
377
  {
161
378
  let entity = this.dataServer.getEntity(tableName);
162
379
  if(!entity){
163
380
  Logger.warning('Entity not found in dataServer: '+tableName);
164
381
  return [];
165
382
  }
166
- let filters = sc.toJson(filtersJson);
167
- if(!filters){
168
- return await entity.loadAll();
383
+ let filters = false;
384
+ if(filtersJson && '' !== filtersJson.trim()){
385
+ let convertedFiltersJson = this.convertJsObjectToJson(filtersJson);
386
+ filters = sc.parseJson(convertedFiltersJson, false);
387
+ if(!filters){
388
+ Logger.warning('Invalid filters JSON: '+filtersJson);
389
+ }
390
+ }
391
+ let originalState = this.preserveEntityState(entity);
392
+ let queryOptions = {};
393
+ if(queryOptionsJson && '' !== queryOptionsJson.trim()){
394
+ let convertedOptionsJson = this.convertJsObjectToJson(queryOptionsJson);
395
+ queryOptions = sc.parseJson(convertedOptionsJson, {});
396
+ if(!queryOptions){
397
+ Logger.warning('Invalid query options JSON: '+queryOptionsJson);
398
+ queryOptions = {};
399
+ }
400
+ }
401
+ this.applyQueryOptions(entity, queryOptions);
402
+ let result = filters ? await entity.load(filters) : await entity.loadAll();
403
+ this.restoreEntityState(entity, originalState);
404
+ return result;
405
+ }
406
+
407
+ preserveEntityState(entity)
408
+ {
409
+ return {
410
+ limit: entity.limit,
411
+ offset: entity.offset,
412
+ sortBy: entity.sortBy,
413
+ sortDirection: entity.sortDirection
414
+ };
415
+ }
416
+
417
+ restoreEntityState(entity, originalState)
418
+ {
419
+ entity.limit = originalState.limit;
420
+ entity.offset = originalState.offset;
421
+ entity.sortBy = originalState.sortBy;
422
+ entity.sortDirection = originalState.sortDirection;
423
+ }
424
+
425
+ applyQueryOptions(entity, queryOptions)
426
+ {
427
+ if(sc.hasOwn(queryOptions, 'limit')){
428
+ entity.limit = queryOptions.limit;
429
+ }
430
+ if(sc.hasOwn(queryOptions, 'offset')){
431
+ entity.offset = queryOptions.offset;
432
+ }
433
+ if(sc.hasOwn(queryOptions, 'sortBy')){
434
+ entity.sortBy = queryOptions.sortBy;
435
+ }
436
+ if(sc.hasOwn(queryOptions, 'sortDirection')){
437
+ entity.sortDirection = queryOptions.sortDirection;
169
438
  }
170
- return await entity.load(filters);
171
439
  }
172
440
 
173
441
  }
174
442
 
175
- module.exports.TemplateEngine = TemplateEngine;
443
+ module.exports.TemplateEngine = TemplateEngine;
@@ -21,6 +21,7 @@ module.exports.TemplatesList = {
21
21
  fields: {
22
22
  view: {
23
23
  text: 'text.html',
24
+ textarea: 'textarea.html',
24
25
  image: 'image.html',
25
26
  images: 'images.html',
26
27
  link: 'link.html',
@@ -37,5 +38,13 @@ module.exports.TemplatesList = {
37
38
  button: 'button.html',
38
39
  file: 'file.html'
39
40
  }
41
+ },
42
+ sections: {
43
+ viewForm: {
44
+ 'cms-pages': 'cms-pages.html',
45
+ },
46
+ editForm: {
47
+ 'cms-pages': 'cms-pages.html',
48
+ }
40
49
  }
41
50
  };
@@ -1,7 +1,7 @@
1
1
 
2
2
  -- Default CMS blocks:
3
3
 
4
- REPLACE INTO `cms_blocks` (`name`, `title`, `content`, `is_active`) VALUES
4
+ REPLACE INTO `cms_blocks` (`name`, `title`, `content`, `enabled`) VALUES
5
5
  ('header-main', 'Main Header', '{{>header}}', 1),
6
6
  ('sidebar-left', 'Left Sidebar', '<aside class="sidebar-left">{{>sidebar}}</aside>', 1),
7
7
  ('sidebar-right', 'Right Sidebar', '<aside class="sidebar-right">{{>sidebar}}</aside>', 1),
@@ -1,9 +1,9 @@
1
1
 
2
2
  -- Default entity access rules:
3
3
 
4
- REPLACE INTO `cms_entity_access` (`entity_name`, `is_public`, `allowed_operations`) VALUES
4
+ REPLACE INTO `entities_access` (`entity_name`, `is_public`, `allowed_operations`) VALUES
5
5
  ('cms_pages', TRUE, '["read"]'),
6
6
  ('routes', FALSE, '[]'),
7
7
  ('users', FALSE, '[]'),
8
8
  ('cms_blocks', FALSE, '[]'),
9
- ('cms_entity_access', FALSE, '[]');
9
+ ('entities_access', FALSE, '[]');
@@ -2,9 +2,29 @@
2
2
  -- Default homepage:
3
3
 
4
4
  -- Create a default homepage route if not exists
5
- REPLACE INTO `cms_pages` (`id`, `title`, `content`, `template`, `created_at`) VALUES
6
- (1, 'Home', '<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>', NULL, NOW());
5
+ REPLACE INTO `cms_pages` (
6
+ `id`, `title`, `content`, `template`, `meta_title`, `meta_description`,
7
+ `canonical_url`, `meta_robots`, `meta_og_title`, `meta_og_description`,
8
+ `meta_og_image`, `meta_twitter_card_type`, `status`, `locale`, `publish_date`, `expire_date`, `created_at`
9
+ ) VALUES (
10
+ 1,
11
+ 'Home',
12
+ '<h1>Welcome to Reldens CMS</h1><p>This is your homepage. Edit this content in the admin panel.</p>',
13
+ NULL,
14
+ 'Home - Reldens CMS',
15
+ 'Welcome to Reldens CMS',
16
+ NULL,
17
+ 'index,follow',
18
+ 'Home - Reldens CMS',
19
+ 'Welcome to the Reldens CMS homepage',
20
+ NULL,
21
+ 'summary',
22
+ 'published',
23
+ 'en',
24
+ NOW(),
25
+ NULL,
26
+ NOW()
27
+ );
7
28
 
8
29
  -- Create a default route to the homepage
9
- REPLACE INTO `routes` (`id`, `path`, `router`, `content_id`, `title`, `meta_description`, `status`, `created_at`) VALUES
10
- (1, '/home', 'cmsPages', 1, 'Home', 'Welcome to Reldens CMS', 'published', NOW());
30
+ REPLACE INTO `routes` (`id`, `path`, `router`, `cms_page_id`, `cache_ttl_seconds`, `enabled`, `created_at`) VALUES (1, '/home', 'cmsPages', 1, 3600, 1, NOW());
@@ -5,34 +5,27 @@ CREATE TABLE IF NOT EXISTS `routes` (
5
5
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
6
6
  `path` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
7
7
  `router` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
8
- `content_id` INT UNSIGNED NOT NULL,
9
- `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
10
- `meta_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
11
- `canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
12
- `meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
13
- `og_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
14
- `og_description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
15
- `og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
16
- `twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
17
- `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
18
- `publish_at` TIMESTAMP NULL,
19
- `locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
8
+ `cms_page_id` INT UNSIGNED NOT NULL,
20
9
  `cache_ttl_seconds` INT UNSIGNED NULL DEFAULT 3600,
10
+ `enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
11
+ `domain` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
21
12
  `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
22
13
  `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
23
14
  PRIMARY KEY (`id`) USING BTREE,
24
- UNIQUE KEY `path` (`path`) USING BTREE
15
+ UNIQUE KEY `path_domain` (`path`, `domain`) USING BTREE
25
16
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
26
17
 
27
- CREATE TABLE IF NOT EXISTS `cms_pages_meta` (
18
+ CREATE TABLE IF NOT EXISTS `cms_categories` (
28
19
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
29
- `page_id` INT UNSIGNED NOT NULL,
30
- `publish_date` TIMESTAMP NULL,
31
- `expire_date` TIMESTAMP NULL,
20
+ `name` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
21
+ `title` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci',
22
+ `enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
23
+ `parent_id` INT UNSIGNED NULL DEFAULT NULL,
32
24
  `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
33
25
  `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
34
26
  PRIMARY KEY (`id`) USING BTREE,
35
- KEY `page_id` (`page_id`) USING BTREE
27
+ INDEX `FK_cms_categories_cms_categories` (`parent_id`) USING BTREE,
28
+ CONSTRAINT `FK_cms_categories_cms_categories` FOREIGN KEY (`parent_id`) REFERENCES `cms_categories` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION
36
29
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
37
30
 
38
31
  CREATE TABLE IF NOT EXISTS `cms_pages` (
@@ -43,21 +36,26 @@ CREATE TABLE IF NOT EXISTS `cms_pages` (
43
36
  `json_data` JSON NULL,
44
37
  `template` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
45
38
  `layout` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'default',
46
- `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
47
- `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
48
- PRIMARY KEY (`id`) USING BTREE
49
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
50
-
51
- CREATE TABLE IF NOT EXISTS `entities_meta` (
52
- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
53
- `entity_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
54
- `entity_id` INT UNSIGNED NOT NULL,
55
- `meta_key` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
56
- `meta_value` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
39
+ `category_id` INT UNSIGNED NULL DEFAULT NULL,
40
+ `enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
41
+ `meta_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
42
+ `meta_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
43
+ `meta_robots` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'index,follow',
44
+ `meta_theme_color` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
45
+ `meta_og_title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
46
+ `meta_og_description` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
47
+ `meta_og_image` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
48
+ `meta_twitter_card_type` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'summary',
49
+ `canonical_url` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
50
+ `status` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'published',
51
+ `locale` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'en',
52
+ `publish_date` TIMESTAMP NULL,
53
+ `expire_date` TIMESTAMP NULL,
57
54
  `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
58
55
  `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
59
56
  PRIMARY KEY (`id`) USING BTREE,
60
- UNIQUE KEY `entity_meta` (`entity_name`, `entity_id`, `meta_key`) USING BTREE
57
+ INDEX `FK_cms_pages_cms_categories` (`category_id`) USING BTREE,
58
+ CONSTRAINT `FK_cms_pages_cms_categories` FOREIGN KEY (`category_id`) REFERENCES `cms_categories` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION
61
59
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
62
60
 
63
61
  CREATE TABLE IF NOT EXISTS `cms_blocks` (
@@ -65,19 +63,17 @@ CREATE TABLE IF NOT EXISTS `cms_blocks` (
65
63
  `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
66
64
  `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
67
65
  `content` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
68
- `template_file` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
69
- `variables` JSON NULL,
70
- `is_active` BOOLEAN NOT NULL DEFAULT TRUE,
66
+ `enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1',
71
67
  `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
72
68
  `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
73
69
  PRIMARY KEY (`id`) USING BTREE,
74
70
  UNIQUE KEY `name` (`name`) USING BTREE
75
71
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
76
72
 
77
- CREATE TABLE IF NOT EXISTS `cms_entity_access` (
73
+ CREATE TABLE IF NOT EXISTS `entities_access` (
78
74
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
79
75
  `entity_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
80
- `is_public` BOOLEAN NOT NULL DEFAULT FALSE,
76
+ `is_public` TINYINT UNSIGNED NOT NULL DEFAULT '0',
81
77
  `allowed_operations` JSON NULL DEFAULT ('["read"]'),
82
78
  `access_rules` JSON NULL,
83
79
  `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.15.0",
4
+ "version": "0.16.0",
5
5
  "description": "Reldens - CMS",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -33,8 +33,8 @@
33
33
  "url": "https://github.com/damian-pastorini/reldens-cms/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@reldens/server-utils": "^0.18.0",
37
- "@reldens/storage": "^0.51.0",
36
+ "@reldens/server-utils": "^0.19.0",
37
+ "@reldens/storage": "^0.56.0",
38
38
  "@reldens/utils": "^0.50.0",
39
39
  "dotenv": "^16.5.0",
40
40
  "mustache": "^4.2.0"