@reldens/cms 0.24.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', ''),
@@ -159,7 +169,8 @@ class RouterContents
159
169
  }
160
170
  let extraDataEvent = {entitySerializedData, entityId: driverResource.id(), entity: loadedEntity};
161
171
  await this.emitEvent('adminEntityExtraData', extraDataEvent);
162
- renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData).replace(/"/g, '"');
172
+ renderedViewProperties.entitySerializedData = JSON.stringify(extraDataEvent.entitySerializedData)
173
+ .replace(/"/g, '"');
163
174
  await this.emitEvent('reldens.adminViewPropertiesPopulation', {
164
175
  idProperty,
165
176
  req,
@@ -244,10 +255,30 @@ class RouterContents
244
255
 
245
256
  async processSaveEntity(req, res, driverResource, entityPath)
246
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
+ }
247
272
  let idProperty = this.fetchEntityIdPropertyKey(driverResource);
248
273
  let id = (req?.body[idProperty] || '');
249
274
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
250
- 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
+ );
251
282
  if(!entityDataPatch){
252
283
  Logger.error('Bad patch data.', entityDataPatch);
253
284
  return this.rootPath+'/'+entityPath+'?result=saveBadPatchData';
@@ -256,7 +287,8 @@ class RouterContents
256
287
  let saveResult = await this.saveEntity(id, entityRepository, entityDataPatch);
257
288
  if(!saveResult){
258
289
  Logger.error('Save result error.', saveResult, entityDataPatch);
259
- return this.generateEntityRoute('editPath', driverResource, idProperty, null, id)+'?result=saveEntityStorageError';
290
+ return this.generateEntityRoute('editPath', driverResource, idProperty, null, id)
291
+ +'?result=saveEntityStorageError';
260
292
  }
261
293
  await this.emitEvent('reldens.adminAfterEntitySave', {
262
294
  req,
@@ -292,6 +324,40 @@ class RouterContents
292
324
  }
293
325
  }
294
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
+
295
361
  async countTotalEntities(driverResource, filters)
296
362
  {
297
363
  let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
@@ -394,12 +460,19 @@ class RouterContents
394
460
  preparePatchData(driverResource, idProperty, req, resourceProperties, id)
395
461
  {
396
462
  let entityDataPatch = {};
397
- for(let i of driverResource.options.editProperties){
463
+ for(let i of Object.keys(resourceProperties)){
398
464
  if(i === idProperty){
399
465
  continue;
400
466
  }
401
- let propertyUpdateValue = sc.get(req.body, i, null);
402
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);
403
476
  if('null' === propertyUpdateValue){
404
477
  propertyUpdateValue = null;
405
478
  }
@@ -414,7 +487,15 @@ class RouterContents
414
487
  let isEmpty = '' === propertyUpdateValue;
415
488
  let isNull = null === propertyUpdateValue;
416
489
  if('number' === propertyType && !isNull && !isEmpty){
417
- 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;
418
499
  }
419
500
  if('string' === propertyType && !isNull && !isEmpty){
420
501
  propertyUpdateValue = String(propertyUpdateValue);
@@ -424,7 +505,11 @@ class RouterContents
424
505
  }
425
506
  let isUploadCreate = property.isUpload && !id;
426
507
  if(property.isRequired && (isNull || isEmpty) && (!property.isUpload || isUploadCreate)){
427
- 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
+ );
428
513
  return false;
429
514
  }
430
515
  if(!property.isUpload || (property.isUpload && !isNull)){
@@ -434,6 +519,7 @@ class RouterContents
434
519
  return entityDataPatch;
435
520
  }
436
521
 
522
+
437
523
  prepareUploadPatchData(req, i, propertyUpdateValue, property)
438
524
  {
439
525
  let filesData = sc.get(req.files, i, null);
@@ -561,7 +647,7 @@ class RouterContents
561
647
  {
562
648
  let relationEntityRepository = this.dataServer.getEntity(relationDriverResource.entityKey);
563
649
  if(!relationEntityRepository){
564
- return false;
650
+ return [];
565
651
  }
566
652
  return await relationEntityRepository.loadAll();
567
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 '';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/cms",
3
3
  "scope": "@reldens",
4
- "version": "0.24.0",
4
+ "version": "0.25.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.25.0",
37
- "@reldens/storage": "^0.67.0",
36
+ "@reldens/server-utils": "^0.26.0",
37
+ "@reldens/storage": "^0.68.0",
38
38
  "@reldens/utils": "^0.53.0",
39
39
  "dotenv": "^17.2.1",
40
40
  "mustache": "^4.2.0"