@reldens/cms 0.23.0 → 0.25.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.
@@ -31,6 +31,7 @@ class RouterContents
31
31
  this.fetchTranslation = props.fetchTranslation;
32
32
  this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
33
33
  this.fetchUploadProperties = props.fetchUploadProperties;
34
+ this.uploaderFactory = props.uploaderFactory;
34
35
  this.filtersManager = new AdminFiltersManager();
35
36
  }
36
37
 
@@ -41,7 +42,7 @@ class RouterContents
41
42
  let shouldClearFilters = 'true' === req?.query?.clearFilters;
42
43
  let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
43
44
  let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
44
- let entityFilterTerm = shouldClearFilters ? '' : req?.body?.entityFilterTerm || req?.query?.entityFilterTerm || '';
45
+ let entityFilterTerm = shouldClearFilters ? '' : sc.get(req?.body, 'entityFilterTerm', '');
45
46
  if(shouldClearFilters){
46
47
  this.filtersManager.clearFiltersFromSession(req, entityPath);
47
48
  }
@@ -107,9 +108,18 @@ class RouterContents
107
108
  driverResource.options.properties[property]?.alias || '',
108
109
  driverResource.id()
109
110
  );
110
- return {name: property, value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle};
111
+ return {
112
+ name: property,
113
+ value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle
114
+ };
111
115
  }),
112
- rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, combinedFilters)
116
+ rows: await this.loadEntitiesForList(
117
+ driverResource,
118
+ pageSize,
119
+ currentPage,
120
+ req,
121
+ combinedFilters
122
+ )
113
123
  }),
114
124
  pagination: renderedPagination,
115
125
  extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
@@ -132,6 +142,10 @@ class RouterContents
132
142
  return '';
133
143
  }
134
144
  let loadedEntity = await this.loadEntityById(driverResource, id);
145
+ if(!loadedEntity){
146
+ Logger.critical('Entity not found on view route.', entityPath, id, idProperty);
147
+ return '';
148
+ }
135
149
  let renderedViewProperties = {
136
150
  entityEditRoute: this.generateEntityRoute('editPath', driverResource, idProperty, loadedEntity),
137
151
  entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
@@ -155,7 +169,8 @@ class RouterContents
155
169
  }
156
170
  let extraDataEvent = {entitySerializedData, entityId: driverResource.id(), entity: loadedEntity};
157
171
  await this.emitEvent('adminEntityExtraData', extraDataEvent);
158
- renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData).replace(/"/g, '"');
172
+ renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData)
173
+ .replace(/"/g, '"');
159
174
  await this.emitEvent('reldens.adminViewPropertiesPopulation', {
160
175
  idProperty,
161
176
  req,
@@ -240,10 +255,30 @@ class RouterContents
240
255
 
241
256
  async processSaveEntity(req, res, driverResource, entityPath)
242
257
  {
258
+ let uploadProperties = this.fetchUploadProperties(driverResource);
259
+ if(0 < Object.keys(uploadProperties).length){
260
+ if(this.uploaderFactory.error.message){
261
+ Logger.error('Upload factory error detected', this.uploaderFactory.error);
262
+ return this.rootPath+'/'+entityPath+'?result=uploadError&error='
263
+ +encodeURIComponent(this.uploaderFactory.error.message);
264
+ }
265
+ let uploadValidationResult = this.validateUploadedFiles(req, uploadProperties);
266
+ if(!uploadValidationResult.success){
267
+ Logger.error('Upload validation failed', uploadValidationResult.error);
268
+ return this.rootPath+'/'+entityPath+'?result=uploadValidationError&error='
269
+ +encodeURIComponent(uploadValidationResult.error.message);
270
+ }
271
+ }
243
272
  let idProperty = this.fetchEntityIdPropertyKey(driverResource);
244
273
  let id = (req?.body[idProperty] || '');
245
274
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
246
- let entityDataPatch = this.preparePatchData(driverResource, idProperty, req, driverResource.options.properties, id);
275
+ let entityDataPatch = this.preparePatchData(
276
+ driverResource,
277
+ idProperty,
278
+ req,
279
+ driverResource.options.properties,
280
+ id
281
+ );
247
282
  if(!entityDataPatch){
248
283
  Logger.error('Bad patch data.', entityDataPatch);
249
284
  return this.rootPath+'/'+entityPath+'?result=saveBadPatchData';
@@ -252,7 +287,8 @@ class RouterContents
252
287
  let saveResult = await this.saveEntity(id, entityRepository, entityDataPatch);
253
288
  if(!saveResult){
254
289
  Logger.error('Save result error.', saveResult, entityDataPatch);
255
- return this.generateEntityRoute('editPath', driverResource, idProperty, null, id)+'?result=saveEntityStorageError';
290
+ return this.generateEntityRoute('editPath', driverResource, idProperty, null, id)
291
+ +'?result=saveEntityStorageError';
256
292
  }
257
293
  await this.emitEvent('reldens.adminAfterEntitySave', {
258
294
  req,
@@ -288,6 +324,40 @@ class RouterContents
288
324
  }
289
325
  }
290
326
 
327
+ validateUploadedFiles(req, uploadProperties)
328
+ {
329
+ for(let uploadPropertyKey of Object.keys(uploadProperties)){
330
+ let property = uploadProperties[uploadPropertyKey];
331
+ let uploadedFiles = req.files?.[uploadPropertyKey];
332
+ if(!uploadedFiles || 0 === uploadedFiles.length){
333
+ if(property.isRequired){
334
+ return {success: false, error: {message: 'Required file upload missing: '+uploadPropertyKey}};
335
+ }
336
+ continue;
337
+ }
338
+ for(let file of uploadedFiles){
339
+ let filePath = file.path;
340
+ if(!FileHandler.exists(filePath)){
341
+ return {
342
+ success: false,
343
+ error: {
344
+ message: 'Uploaded file not found in bucket: '+file.filename,
345
+ filePath,
346
+ bucket: property.bucket
347
+ }
348
+ };
349
+ }
350
+ if(!FileHandler.isFile(filePath)){
351
+ return {
352
+ success: false,
353
+ error: {message: 'Uploaded file is not a valid file: '+file.filename, filePath}
354
+ };
355
+ }
356
+ }
357
+ }
358
+ return {success: true};
359
+ }
360
+
291
361
  async countTotalEntities(driverResource, filters)
292
362
  {
293
363
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
@@ -390,12 +460,19 @@ class RouterContents
390
460
  preparePatchData(driverResource, idProperty, req, resourceProperties, id)
391
461
  {
392
462
  let entityDataPatch = {};
393
- for(let i of driverResource.options.editProperties){
463
+ for(let i of Object.keys(resourceProperties)){
394
464
  if(i === idProperty){
395
465
  continue;
396
466
  }
397
- let propertyUpdateValue = sc.get(req.body, i, null);
398
467
  let property = resourceProperties[i];
468
+ let shouldProcess = driverResource.options.editProperties.includes(i);
469
+ if(!id && !shouldProcess && property.isRequired && 'reference' === property.type){
470
+ shouldProcess = true;
471
+ }
472
+ if(!shouldProcess){
473
+ continue;
474
+ }
475
+ let propertyUpdateValue = sc.get(req.body, i, null);
399
476
  if('null' === propertyUpdateValue){
400
477
  propertyUpdateValue = null;
401
478
  }
@@ -410,7 +487,15 @@ class RouterContents
410
487
  let isEmpty = '' === propertyUpdateValue;
411
488
  let isNull = null === propertyUpdateValue;
412
489
  if('number' === propertyType && !isNull && !isEmpty){
413
- propertyUpdateValue = Number(propertyUpdateValue);
490
+ let numValue = Number(propertyUpdateValue);
491
+ if(isNaN(numValue)){
492
+ Logger.critical(
493
+ 'Invalid number value for property.',
494
+ {propertyKey: i, value: propertyUpdateValue, entity: driverResource.entityKey}
495
+ );
496
+ return false;
497
+ }
498
+ propertyUpdateValue = numValue;
414
499
  }
415
500
  if('string' === propertyType && !isNull && !isEmpty){
416
501
  propertyUpdateValue = String(propertyUpdateValue);
@@ -420,7 +505,11 @@ class RouterContents
420
505
  }
421
506
  let isUploadCreate = property.isUpload && !id;
422
507
  if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
423
- Logger.critical('Bad patch data on update.', propertyUpdateValue, property);
508
+ // missing required fields would break the update:
509
+ Logger.critical(
510
+ 'Missing property on prepare patch data.',
511
+ {propertyKey: i, config: property, propertyUpdateValue, entity: driverResource.entityKey}
512
+ );
424
513
  return false;
425
514
  }
426
515
  if(!property.isUpload || (property.isUpload && !isNull)){
@@ -430,6 +519,7 @@ class RouterContents
430
519
  return entityDataPatch;
431
520
  }
432
521
 
522
+
433
523
  prepareUploadPatchData(req, i, propertyUpdateValue, property)
434
524
  {
435
525
  let filesData = sc.get(req.files, i, null);
@@ -557,7 +647,7 @@ class RouterContents
557
647
  {
558
648
  let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
559
649
  if(!relationEntityRepository){
560
- return false;
650
+ return [];
561
651
  }
562
652
  return await relationEntityRepository.loadAll();
563
653
  }
@@ -171,6 +171,7 @@ class Router
171
171
  }
172
172
  let fields = [];
173
173
  let allowedFileTypes = {};
174
+ let entityBuckets = {};
174
175
  for(let uploadPropertyKey of Object.keys(uploadProperties)){
175
176
  let property = uploadProperties[uploadPropertyKey];
176
177
  allowedFileTypes[uploadPropertyKey] = property.allowedTypes || false;
@@ -179,12 +180,12 @@ class Router
179
180
  field.maxCount = 1;
180
181
  }
181
182
  fields.push(field);
182
- this.buckets[uploadPropertyKey] = property.bucket;
183
+ entityBuckets[uploadPropertyKey] = property.bucket;
183
184
  }
184
185
  this.adminRouter.post(
185
186
  entityRoute + this.savePath,
186
187
  this.isAuthenticated.bind(this),
187
- this.uploaderFactory.createUploader(fields, this.buckets, allowedFileTypes),
188
+ this.uploaderFactory.createUploader(fields, entityBuckets, allowedFileTypes),
188
189
  async (req, res) => {
189
190
  //await this.reloadTemplatesIfNeeded();
190
191
  await this.emitEvent('reldens.adminBeforeEntitySave', {
@@ -241,7 +241,7 @@ class AdminManager
241
241
 
242
242
  fetchEntityIdPropertyKey(driverResource)
243
243
  {
244
- let resourceProperties = driverResource.options?.properties;
244
+ let resourceProperties = driverResource?.options?.properties;
245
245
  if(!resourceProperties){
246
246
  Logger.error('Property "ID" not found.', resourceProperties);
247
247
  return '';
@@ -29,7 +29,7 @@ class AddCacheButtonSubscriber
29
29
  return false;
30
30
  }
31
31
  if(!this.cacheManager.isEnabled()){
32
- Logger.debug('Cache Manager not enabled.');
32
+ Logger.debug('Cache Manager not enabled on AddCacheButtonSubscriber.');
33
33
  return false;
34
34
  }
35
35
  if(!this.events){
@@ -30,7 +30,7 @@ class CacheRoutesHandler
30
30
  return false;
31
31
  }
32
32
  if(!this.cacheManager.isEnabled()){
33
- Logger.debug('Cache Manager not enabled.');
33
+ Logger.debug('Cache Manager not enabled on CacheRoutesHandler.');
34
34
  return false;
35
35
  }
36
36
  if(!this.router){
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.23.0",
4
+ "version": "0.25.0",
5
5
  "description": "Reldens - CMS",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -33,9 +33,9 @@
33
33
  "url": "https://github.com/damian-pastorini/reldens-cms/issues"
34
34
  },
35
35
  "dependencies": {
36
- "@reldens/server-utils": "^0.24.0",
37
- "@reldens/storage": "^0.65.0",
38
- "@reldens/utils": "^0.52.0",
36
+ "@reldens/server-utils": "^0.26.0",
37
+ "@reldens/storage": "^0.68.0",
38
+ "@reldens/utils": "^0.53.0",
39
39
  "dotenv": "^17.2.1",
40
40
  "mustache": "^4.2.0"
41
41
  }