@reldens/cms 0.28.0 → 0.29.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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - PrismaSubprocessWorker
4
+ *
5
+ */
6
+
7
+ const { DriversMap } = require('@reldens/storage');
8
+ const { MySQLInstaller } = require('./mysql-installer');
9
+ const { Logger } = require('@reldens/utils');
10
+ const { sc } = require('@reldens/utils');
11
+
12
+ class PrismaSubprocessWorker
13
+ {
14
+
15
+ constructor()
16
+ {
17
+ this.setupProcessHandlers();
18
+ }
19
+
20
+ setupProcessHandlers()
21
+ {
22
+ process.on('message', async (message) => {
23
+ try {
24
+ await this.processIncomingMessage(message);
25
+ } catch(error) {
26
+ Logger.error('PrismaSubprocessWorker error: '+error.message);
27
+ this.sendErrorResponse(error.message);
28
+ setTimeout(() => process.exit(1), 100);
29
+ }
30
+ });
31
+ process.on('uncaughtException', (error) => {
32
+ Logger.error('PrismaSubprocessWorker uncaught exception: '+error.message);
33
+ this.sendErrorResponse(error.message);
34
+ setTimeout(() => process.exit(1), 100);
35
+ });
36
+ process.on('unhandledRejection', (error) => {
37
+ Logger.error('PrismaSubprocessWorker unhandled rejection: '+error.message);
38
+ this.sendErrorResponse(error.message);
39
+ setTimeout(() => process.exit(1), 100);
40
+ });
41
+ }
42
+
43
+ async processIncomingMessage(message)
44
+ {
45
+ let dbConfig = sc.get(message, 'dbConfig', {});
46
+ let templateVariables = sc.get(message, 'templateVariables', {});
47
+ let migrationsPath = sc.get(message, 'migrationsPath', './migrations');
48
+ let projectRoot = sc.get(message, 'projectRoot', './');
49
+ let generatedClient = await MySQLInstaller.generateMinimalPrismaClient(dbConfig, projectRoot);
50
+ if(!generatedClient){
51
+ this.sendErrorResponse('Failed to generate Prisma client.');
52
+ return;
53
+ }
54
+ dbConfig.prismaClient = generatedClient;
55
+ let driverClass = DriversMap['prisma'];
56
+ if(!driverClass){
57
+ this.sendErrorResponse('Prisma driver class not found.');
58
+ return;
59
+ }
60
+ let dbDriver = new driverClass(dbConfig);
61
+ if(!await dbDriver.connect()){
62
+ this.sendErrorResponse('Database connection failed.');
63
+ return;
64
+ }
65
+ let migrationFiles = MySQLInstaller.migrationFiles();
66
+ for(let checkboxName of Object.keys(migrationFiles)){
67
+ let fileName = migrationFiles[checkboxName];
68
+ let redirectError = await MySQLInstaller.executeQueryFile(
69
+ sc.get(templateVariables, checkboxName, 'off'),
70
+ fileName,
71
+ dbDriver,
72
+ migrationsPath
73
+ );
74
+ if('' !== redirectError){
75
+ this.sendErrorResponse('Migration failed: '+fileName);
76
+ return;
77
+ }
78
+ }
79
+ await generatedClient.$disconnect();
80
+ this.sendSuccessResponse('Subprocess installation completed.');
81
+ }
82
+
83
+ sendSuccessResponse(message)
84
+ {
85
+ process.send({success: true, message: message});
86
+ }
87
+
88
+ sendErrorResponse(errorMessage)
89
+ {
90
+ process.send({success: false, error: errorMessage});
91
+ }
92
+
93
+ }
94
+
95
+ module.exports.PrismaSubprocessWorker = PrismaSubprocessWorker;
96
+
97
+ new PrismaSubprocessWorker();
@@ -38,12 +38,11 @@ class DateTransformer
38
38
  {
39
39
  let date;
40
40
  if(!dateValue || '' === dateValue || 'now' === dateValue.toLowerCase()){
41
+ return sc.formatDate(new Date(), format);
42
+ }
43
+ date = new Date(dateValue);
44
+ if(isNaN(date.getTime())){
41
45
  date = new Date();
42
- } else {
43
- date = new Date(dateValue);
44
- if(isNaN(date.getTime())){
45
- date = new Date();
46
- }
47
46
  }
48
47
  return sc.formatDate(date, format);
49
48
  }
@@ -1,7 +1,7 @@
1
1
  -- Default homepage:
2
2
 
3
3
  -- Create a default route first
4
- REPLACE INTO `routes` (`id`, `path`, `router`, `cache_ttl_seconds`, `enabled`, `created_at`) VALUES (1, '/home', 'cmsPages', 3600, 1, NOW());
4
+ REPLACE INTO `routes` (`id`, `path`, `router`, `cache_ttl_seconds`, `enabled`, `created_at`) VALUES (1, '/', 'cmsPages', 3600, 1, NOW());
5
5
 
6
6
  -- Create a default homepage with route_id reference
7
7
  REPLACE INTO `cms_pages` (
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.28.0",
4
+ "version": "0.29.0",
5
5
  "description": "Reldens - CMS",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -33,10 +33,10 @@
33
33
  "url": "https://github.com/damian-pastorini/reldens-cms/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@reldens/server-utils": "^0.26.0",
37
- "@reldens/storage": "^0.69.0",
36
+ "@reldens/server-utils": "^0.29.0",
37
+ "@reldens/storage": "^0.71.0",
38
38
  "@reldens/utils": "^0.53.0",
39
- "dotenv": "^17.2.2",
40
- "mustache": "^4.2.0"
39
+ "dotenv": "17.2.2",
40
+ "mustache": "4.2.0"
41
41
  }
42
42
  }
@@ -1,32 +1,30 @@
1
- /**
2
- *
3
- * Reldens - CMS
4
- *
5
- */
6
-
7
- const { Manager } = require('@reldens/cms');
8
- const { Logger } = require('@reldens/utils');
9
- const { rawRegisteredEntities, entitiesConfig, entitiesTranslations } = require('./generated-entities/models/{{driverKey}}/registered-models-{{driverKey}}');
10
- {{prismaClientImports}}
11
- let args = process.argv.slice(2);
12
- let projectRoot = args[0] || process.cwd();
13
-
14
- let manager = new Manager({
15
- projectRoot,
16
- rawRegisteredEntities,
17
- entitiesConfig,
18
- entitiesTranslations{{prismaClientParam}}
19
- });
20
- Logger.debug('Reldens CMS Manager instance created.', {configuration: manager.config});
21
-
22
- manager.start().then((result) => {
23
- if(!result){
24
- Logger.info('Reldens CMS started by command failed.');
25
- return false;
26
- }
27
- Logger.info('Reldens CMS started by command.');
28
- return true;
29
- }).catch((error) => {
30
- Logger.critical('Failed to start CMS: ' + error.message);
31
- process.exit();
32
- });
1
+ /**
2
+ *
3
+ * Reldens - CMS
4
+ *
5
+ */
6
+
7
+ const { Manager } = require('@reldens/cms');
8
+ const { Logger } = require('@reldens/utils');
9
+ const { rawRegisteredEntities, entitiesConfig, entitiesTranslations } = require('./generated-entities/models/{{driverKey}}/registered-models-{{driverKey}}');
10
+ {{&prismaClientImports}}
11
+
12
+ let manager = new Manager({
13
+ projectRoot: __dirname,
14
+ rawRegisteredEntities,
15
+ entitiesConfig,
16
+ entitiesTranslations{{&prismaClientParam}}
17
+ });
18
+ Logger.debug('Reldens CMS Manager instance created.', {configuration: manager.config});
19
+
20
+ manager.start().then((result) => {
21
+ if(!result){
22
+ Logger.info('Reldens CMS started by command failed.');
23
+ return false;
24
+ }
25
+ Logger.info('Reldens CMS started by command.');
26
+ return true;
27
+ }).catch((error) => {
28
+ Logger.critical('Failed to start CMS: ' + error.message);
29
+ process.exit();
30
+ });
@@ -0,0 +1,411 @@
1
+ /**
2
+ *
3
+ * Reldens CMS - Cookie Consent - GDPR-compliant with granular controls
4
+ *
5
+ */
6
+
7
+ window.gtmID = 'GTM-XXXXXXXX';
8
+
9
+ let CookieConsent = {
10
+ config: {
11
+ storageKey: 'reldens_cookie_consent',
12
+ preferencesKey: 'reldens_cookie_preferences',
13
+ gtmId: window.gtmID || null,
14
+ gaId: window.gaID || 'G-XXXXXXX',
15
+ privacyPolicyUrl: '/privacy',
16
+ waitForUpdate: 500,
17
+ useGTM: false
18
+ },
19
+
20
+ elements: {
21
+ banner: null,
22
+ acceptBtn: null,
23
+ rejectBtn: null,
24
+ customizeBtn: null,
25
+ manageBtn: null,
26
+ preferencesModal: null,
27
+ savePreferencesBtn: null,
28
+ cancelPreferencesBtn: null
29
+ },
30
+
31
+ categories: {
32
+ essential: {
33
+ name: 'Essential Cookies',
34
+ description: 'Required for basic site functionality and cannot be disabled.',
35
+ required: true,
36
+ enabled: true
37
+ },
38
+ analytics: {
39
+ name: 'Analytics Cookies',
40
+ description: 'Help us understand how visitors interact with our website.',
41
+ required: false,
42
+ enabled: false
43
+ },
44
+ marketing: {
45
+ name: 'Marketing Cookies',
46
+ description: 'Used to track visitors across websites for personalized advertising.',
47
+ required: false,
48
+ enabled: false
49
+ }
50
+ },
51
+
52
+ init()
53
+ {
54
+ this.detectTrackingMethod();
55
+ if('undefined' === typeof window.dataLayer){
56
+ window.dataLayer = [];
57
+ }
58
+ if('undefined' === typeof window.gtag){
59
+ window.gtag = function() { window.dataLayer.push(arguments); };
60
+ }
61
+ this.setDefaultConsent();
62
+ this.initElements();
63
+ this.bindEvents();
64
+ this.checkExistingConsent();
65
+ },
66
+
67
+ detectTrackingMethod()
68
+ {
69
+ if(this.config.gtmId){
70
+ this.config.useGTM = true;
71
+ return;
72
+ }
73
+ this.config.useGTM = false;
74
+ },
75
+
76
+ setDefaultConsent()
77
+ {
78
+ window.gtag('consent', 'default', {
79
+ ad_storage: 'denied',
80
+ analytics_storage: 'denied',
81
+ ad_user_data: 'denied',
82
+ ad_personalization: 'denied',
83
+ wait_for_update: this.config.waitForUpdate
84
+ });
85
+ },
86
+
87
+ initElements()
88
+ {
89
+ this.elements.banner = document.getElementById('cookie-banner');
90
+ this.elements.acceptBtn = document.getElementById('accept-cookies');
91
+ this.elements.rejectBtn = document.getElementById('reject-cookies');
92
+ this.elements.customizeBtn = document.getElementById('customize-cookies');
93
+ this.elements.manageBtn = document.getElementById('manage-cookies');
94
+ this.elements.preferencesModal = document.getElementById('cookie-preferences');
95
+ this.elements.savePreferencesBtn = document.getElementById('save-preferences');
96
+ this.elements.cancelPreferencesBtn = document.getElementById('cancel-preferences');
97
+ },
98
+
99
+ bindEvents()
100
+ {
101
+ if(this.elements.acceptBtn){
102
+ this.elements.acceptBtn.onclick = () => this.acceptAll();
103
+ }
104
+ if(this.elements.rejectBtn){
105
+ this.elements.rejectBtn.onclick = () => this.rejectAll();
106
+ }
107
+ if(this.elements.customizeBtn){
108
+ this.elements.customizeBtn.onclick = () => this.showPreferences();
109
+ }
110
+ if(this.elements.manageBtn){
111
+ this.elements.manageBtn.onclick = () => this.showPreferences();
112
+ }
113
+ if(this.elements.savePreferencesBtn){
114
+ this.elements.savePreferencesBtn.onclick = () => this.savePreferences();
115
+ }
116
+ if(this.elements.cancelPreferencesBtn){
117
+ this.elements.cancelPreferencesBtn.onclick = () => this.hidePreferences();
118
+ }
119
+ if(this.elements.preferencesModal){
120
+ this.elements.preferencesModal.onclick = (e) => {
121
+ if(e.target === this.elements.preferencesModal){
122
+ this.hidePreferences();
123
+ }
124
+ };
125
+ }
126
+ document.addEventListener('keydown', (e) => {
127
+ if(
128
+ 27 === e.keyCode && this.elements.preferencesModal
129
+ && !this.elements.preferencesModal.classList.contains('hidden')
130
+ ){
131
+ this.hidePreferences();
132
+ }
133
+ });
134
+ },
135
+
136
+ checkExistingConsent()
137
+ {
138
+ let savedConsent = this.getStoredConsent();
139
+ if(!savedConsent){
140
+ this.showBanner();
141
+ return;
142
+ }
143
+ this.loadSavedPreferences();
144
+ this.updateConsentMode();
145
+ if(this.shouldLoadAnalytics()){
146
+ this.loadAnalytics();
147
+ }
148
+ },
149
+
150
+ getStoredConsent()
151
+ {
152
+ try {
153
+ return localStorage.getItem(this.config.storageKey);
154
+ } catch(e) {
155
+ return null;
156
+ }
157
+ },
158
+
159
+ getStoredPreferences()
160
+ {
161
+ try {
162
+ let stored = localStorage.getItem(this.config.preferencesKey);
163
+ if(stored){
164
+ return JSON.parse(stored);
165
+ }
166
+ return null;
167
+ } catch(e) {
168
+ return null;
169
+ }
170
+ },
171
+
172
+ saveConsent(value)
173
+ {
174
+ try {
175
+ localStorage.setItem(this.config.storageKey, value);
176
+ return true;
177
+ } catch(e) {
178
+ return false;
179
+ }
180
+ },
181
+
182
+ savePreferencesToStorage(preferences)
183
+ {
184
+ try {
185
+ localStorage.setItem(this.config.preferencesKey, JSON.stringify(preferences));
186
+ return true;
187
+ } catch(e) {
188
+ return false;
189
+ }
190
+ },
191
+
192
+ loadSavedPreferences()
193
+ {
194
+ let saved = this.getStoredPreferences();
195
+ if(!saved){
196
+ return;
197
+ }
198
+ for(let categoryKey of Object.keys(this.categories)){
199
+ if(saved.hasOwnProperty(categoryKey)){
200
+ this.categories[categoryKey].enabled = saved[categoryKey];
201
+ }
202
+ }
203
+ },
204
+
205
+ showBanner()
206
+ {
207
+ if(!this.elements.banner){
208
+ return;
209
+ }
210
+ this.elements.banner.classList.remove('hidden');
211
+ this.elements.banner.classList.add('animate-in');
212
+ this.elements.banner.setAttribute('aria-hidden', 'false');
213
+ let firstButton = this.elements.banner.querySelector('button');
214
+ if(firstButton){
215
+ firstButton.focus();
216
+ }
217
+ },
218
+
219
+ hideBanner()
220
+ {
221
+ if(!this.elements.banner){
222
+ return;
223
+ }
224
+ this.elements.banner.classList.add('animate-out');
225
+ this.elements.banner.setAttribute('aria-hidden', 'true');
226
+ setTimeout(() => {
227
+ this.elements.banner.classList.add('hidden');
228
+ this.elements.banner.classList.remove('animate-in', 'animate-out');
229
+ }, 300);
230
+ },
231
+
232
+ showPreferences()
233
+ {
234
+ if(!this.elements.preferencesModal){
235
+ return;
236
+ }
237
+ this.updatePreferencesUI();
238
+ this.elements.preferencesModal.classList.remove('hidden');
239
+ this.elements.preferencesModal.setAttribute('aria-hidden', 'false');
240
+ let firstInput = this.elements.preferencesModal.querySelector('input, button');
241
+ if(firstInput){
242
+ firstInput.focus();
243
+ }
244
+ this.hideBanner();
245
+ },
246
+
247
+ hidePreferences()
248
+ {
249
+ if(!this.elements.preferencesModal){
250
+ return;
251
+ }
252
+ this.elements.preferencesModal.classList.add('hidden');
253
+ this.elements.preferencesModal.setAttribute('aria-hidden', 'true');
254
+ },
255
+
256
+ updatePreferencesUI()
257
+ {
258
+ for(let categoryKey of Object.keys(this.categories)){
259
+ let toggle = document.getElementById('cookie-' + categoryKey);
260
+ if(toggle){
261
+ toggle.checked = this.categories[categoryKey].enabled;
262
+ toggle.disabled = this.categories[categoryKey].required;
263
+ }
264
+ }
265
+ },
266
+
267
+ acceptAll()
268
+ {
269
+ for(let categoryKey of Object.keys(this.categories)){
270
+ this.categories[categoryKey].enabled = true;
271
+ }
272
+ this.saveConsent('granted');
273
+ this.saveCurrentPreferences();
274
+ this.updateConsentMode();
275
+ this.loadAnalytics();
276
+ this.hideBanner();
277
+ },
278
+
279
+ rejectAll()
280
+ {
281
+ for(let categoryKey of Object.keys(this.categories)){
282
+ this.categories[categoryKey].enabled = this.categories[categoryKey].required;
283
+ }
284
+ this.saveConsent('denied');
285
+ this.saveCurrentPreferences();
286
+ this.updateConsentMode();
287
+ this.hideBanner();
288
+ },
289
+
290
+ savePreferences()
291
+ {
292
+ this.readPreferencesFromUI();
293
+ this.saveCurrentPreferences();
294
+ let hasAnalytics = this.categories.analytics.enabled;
295
+ let hasMarketing = this.categories.marketing.enabled;
296
+ if(hasAnalytics || hasMarketing){
297
+ this.saveConsent('granted');
298
+ this.updateConsentMode();
299
+ if(hasAnalytics){
300
+ this.loadAnalytics();
301
+ }
302
+ } else {
303
+ this.saveConsent('denied');
304
+ this.updateConsentMode();
305
+ }
306
+ this.hidePreferences();
307
+ },
308
+
309
+ readPreferencesFromUI()
310
+ {
311
+ for(let categoryKey of Object.keys(this.categories)){
312
+ let toggle = document.getElementById('cookie-' + categoryKey);
313
+ if(toggle && !this.categories[categoryKey].required){
314
+ this.categories[categoryKey].enabled = toggle.checked;
315
+ }
316
+ }
317
+ },
318
+
319
+ saveCurrentPreferences()
320
+ {
321
+ let preferences = {};
322
+ for(let categoryKey of Object.keys(this.categories)){
323
+ preferences[categoryKey] = this.categories[categoryKey].enabled;
324
+ }
325
+ this.savePreferencesToStorage(preferences);
326
+ },
327
+
328
+ updateConsentMode()
329
+ {
330
+ let analyticsStorage = this.categories.analytics.enabled ? 'granted' : 'denied';
331
+ let adStorage = this.categories.marketing.enabled ? 'granted' : 'denied';
332
+ window.gtag('consent', 'update', {
333
+ analytics_storage: analyticsStorage,
334
+ ad_storage: adStorage,
335
+ ad_user_data: adStorage,
336
+ ad_personalization: adStorage
337
+ });
338
+ },
339
+
340
+ shouldLoadAnalytics()
341
+ {
342
+ return this.categories.analytics.enabled;
343
+ },
344
+
345
+ loadAnalytics()
346
+ {
347
+ if(this.config.useGTM){
348
+ this.loadGTM();
349
+ return;
350
+ }
351
+ this.loadGA();
352
+ },
353
+
354
+ loadGTM()
355
+ {
356
+ if(document.querySelector('script[src*="googletagmanager.com/gtm.js"]')){
357
+ return;
358
+ }
359
+ let script = document.createElement('script');
360
+ script.src = 'https://www.googletagmanager.com/gtm.js?id=' + this.config.gtmId;
361
+ script.async = true;
362
+ script.onload = () => {
363
+ window.gtag('js', new Date());
364
+ window.gtag('config', this.config.gtmId);
365
+ };
366
+ document.head.appendChild(script);
367
+ let noscript = document.createElement('noscript');
368
+ let iframe = document.createElement('iframe');
369
+ iframe.src = 'https://www.googletagmanager.com/ns.html?id=' + this.config.gtmId;
370
+ iframe.height = '0';
371
+ iframe.width = '0';
372
+ iframe.style.display = 'none';
373
+ iframe.style.visibility = 'hidden';
374
+ noscript.appendChild(iframe);
375
+ document.body.appendChild(noscript);
376
+ },
377
+
378
+ loadGA()
379
+ {
380
+ if(document.querySelector('script[src*="googletagmanager.com/gtag/js"]')){
381
+ return;
382
+ }
383
+ let script = document.createElement('script');
384
+ script.src = 'https://www.googletagmanager.com/gtag/js?id=' + this.config.gaId;
385
+ script.async = true;
386
+ script.onload = () => {
387
+ window.gtag('js', new Date());
388
+ window.gtag('config', this.config.gaId, {
389
+ anonymize_ip: true,
390
+ allow_google_signals: this.categories.marketing.enabled,
391
+ allow_ad_personalization_signals: this.categories.marketing.enabled
392
+ });
393
+ };
394
+ document.head.appendChild(script);
395
+ },
396
+
397
+ reset()
398
+ {
399
+ try {
400
+ localStorage.removeItem(this.config.storageKey);
401
+ localStorage.removeItem(this.config.preferencesKey);
402
+ location.reload();
403
+ } catch(e) {
404
+ location.reload();
405
+ }
406
+ }
407
+ };
408
+
409
+ document.addEventListener('DOMContentLoaded', () => {
410
+ CookieConsent.init();
411
+ });