apostrophe 3.17.0 → 3.18.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.
- package/.editorconfig +3 -0
- package/.eslintrc +4 -3
- package/.github/workflows/main.yml +2 -2
- package/.stylelintrc +12 -2
- package/CHANGELOG.md +34 -2
- package/defaults.js +2 -2
- package/index.js +124 -33
- package/lib/escape-host.js +8 -0
- package/lib/mongodb-connect.js +55 -0
- package/lib/opentelemetry.js +144 -0
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +20 -8
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +10 -0
- package/modules/@apostrophecms/asset/lib/globalIcons.js +1 -0
- package/modules/@apostrophecms/attachment/index.js +81 -29
- package/modules/@apostrophecms/db/index.js +7 -10
- package/modules/@apostrophecms/doc/index.js +138 -23
- package/modules/@apostrophecms/doc-type/index.js +162 -63
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +39 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -1
- package/modules/@apostrophecms/email/index.js +1 -1
- package/modules/@apostrophecms/express/index.js +2 -2
- package/modules/@apostrophecms/http/index.js +2 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +10 -0
- package/modules/@apostrophecms/i18n/i18n/es.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +7 -0
- package/modules/@apostrophecms/image/index.js +182 -1
- package/modules/@apostrophecms/image/ui/apos/apps/AposImageRelationshipQueryFilter.js +13 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +460 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +510 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +5 -1
- package/modules/@apostrophecms/image/ui/apos/lib/aspectRatios.js +26 -0
- package/modules/@apostrophecms/image-widget/views/widget.html +5 -2
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +45 -1
- package/modules/@apostrophecms/module/index.js +98 -17
- package/modules/@apostrophecms/module/lib/events.js +46 -11
- package/modules/@apostrophecms/page/index.js +55 -22
- package/modules/@apostrophecms/piece-page-type/index.js +1 -0
- package/modules/@apostrophecms/piece-type/index.js +13 -4
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -2
- package/modules/@apostrophecms/rich-text-widget/index.js +1 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +4 -0
- package/modules/@apostrophecms/schema/index.js +79 -73
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +10 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +22 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +72 -36
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +7 -26
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +8 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +45 -15
- package/modules/@apostrophecms/task/index.js +106 -52
- package/modules/@apostrophecms/template/index.js +111 -76
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +42 -22
- package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +61 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +46 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +10 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -22
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
- package/modules/@apostrophecms/widget-type/index.js +2 -23
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +20 -1
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +0 -9
- package/package.json +16 -12
- package/scripts/lint-i18n.js +2 -2
- package/test/assets.js +2 -1
- package/test/attachments.js +119 -26
- package/test/bundle.js +1 -1
- package/test/content-i18n.js +6 -6
- package/test/docs.js +244 -4
- package/test/draft-published.js +41 -41
- package/test/express.js +1 -1
- package/test/http.js +2 -2
- package/test/images.js +94 -4
- package/test/job.js +1 -1
- package/test/locks.js +1 -1
- package/test/middleware-and-route-order.js +3 -3
- package/test/pages-public-api.js +48 -4
- package/test/pages-rest.js +20 -20
- package/test/pages.js +377 -11
- package/test/parked-pages.js +1 -1
- package/test/permissions.js +10 -10
- package/test/pieces-public-api.js +130 -6
- package/test/pieces.js +247 -60
- package/test/recursionGuard.js +6 -6
- package/test/restApiRoutes.js +6 -6
- package/test/schemaBuilders.js +7 -7
- package/test/schemas.js +59 -59
- package/test/search.js +3 -3
- package/test/soft-redirects.js +13 -13
- package/test/static-i18n.js +1 -1
- package/test/templates.js +10 -10
- package/test/urls.js +2 -2
- package/test/users.js +21 -21
- package/test/utils.js +13 -13
- package/test/widgets.js +2 -2
- package/test-lib/util.js +2 -5
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidget.vue +0 -26
|
@@ -184,15 +184,20 @@ module.exports = {
|
|
|
184
184
|
self.canUpload,
|
|
185
185
|
async function (req) {
|
|
186
186
|
const _id = self.apos.launder.id(req.body._id);
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const { crop } = req.body;
|
|
188
|
+
|
|
189
|
+
if (!_id || !crop || typeof crop !== 'object' || Array.isArray(crop)) {
|
|
189
190
|
throw self.apos.error('invalid');
|
|
190
191
|
}
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
|
|
193
|
+
const sanitizedCrop = self.sanitizeCrop(crop);
|
|
194
|
+
|
|
195
|
+
if (!sanitizedCrop) {
|
|
193
196
|
throw self.apos.error('invalid');
|
|
194
197
|
}
|
|
195
|
-
|
|
198
|
+
|
|
199
|
+
await self.crop(req, _id, sanitizedCrop);
|
|
200
|
+
|
|
196
201
|
return true;
|
|
197
202
|
}
|
|
198
203
|
]
|
|
@@ -455,49 +460,62 @@ module.exports = {
|
|
|
455
460
|
});
|
|
456
461
|
},
|
|
457
462
|
async crop(req, _id, crop) {
|
|
458
|
-
const info = await self.db.findOne({ _id
|
|
463
|
+
const info = await self.db.findOne({ _id });
|
|
464
|
+
|
|
459
465
|
if (!info) {
|
|
460
466
|
throw self.apos.error('notfound');
|
|
461
467
|
}
|
|
468
|
+
|
|
462
469
|
if (!self.croppable[info.extension]) {
|
|
463
|
-
throw
|
|
470
|
+
throw self.apos.error('invalid', req.t('apostrophe:fileTypeCannotBeCropped', {
|
|
471
|
+
extension: info.extension
|
|
472
|
+
}));
|
|
464
473
|
}
|
|
465
474
|
const crops = info.crops || [];
|
|
466
475
|
const existing = _.find(crops, crop);
|
|
476
|
+
|
|
467
477
|
if (existing) {
|
|
468
478
|
// We're done, this crop is already available
|
|
469
479
|
return;
|
|
470
480
|
}
|
|
471
481
|
// Pull the original out of cloud storage to a temporary folder where
|
|
472
482
|
// it can be cropped and popped back into uploadfs
|
|
473
|
-
const originalFile =
|
|
474
|
-
const tempFile = self.uploadfs.getTempPath()
|
|
475
|
-
const croppedFile =
|
|
483
|
+
const originalFile = `/attachments/${info._id}-${info.name}.${info.extension}`;
|
|
484
|
+
const tempFile = `${self.uploadfs.getTempPath()}/${self.apos.util.generateId()}.${info.extension}`;
|
|
485
|
+
const croppedFile = `/attachments/${info._id}-${info.name}.${crop.left}.${crop.top}.${crop.width}.${crop.height}.${info.extension}`;
|
|
486
|
+
|
|
476
487
|
await Promise.promisify(self.uploadfs.copyOut)(originalFile, tempFile);
|
|
477
488
|
await Promise.promisify(self.uploadfs.copyImageIn)(tempFile, croppedFile, {
|
|
478
489
|
crop: crop,
|
|
479
490
|
sizes: self.imageSizes
|
|
480
491
|
});
|
|
481
|
-
|
|
492
|
+
|
|
482
493
|
await self.db.updateOne({
|
|
483
494
|
_id: info._id
|
|
484
495
|
}, {
|
|
485
496
|
$set: {
|
|
486
|
-
crops
|
|
497
|
+
crops: [
|
|
498
|
+
...crops,
|
|
499
|
+
crop
|
|
500
|
+
]
|
|
487
501
|
}
|
|
488
502
|
});
|
|
489
503
|
await Promise.promisify(fs.unlink)(tempFile);
|
|
490
504
|
},
|
|
491
505
|
sanitizeCrop(crop) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (_.keys(crop).length < 4) {
|
|
498
|
-
return undefined;
|
|
506
|
+
const neededProps = [ 'top', 'left', 'width', 'height' ];
|
|
507
|
+
const { integer: sanitizeInteger } = self.apos.launder;
|
|
508
|
+
|
|
509
|
+
if (neededProps.some((prop) => !Object.keys(crop).includes(prop))) {
|
|
510
|
+
return null;
|
|
499
511
|
}
|
|
500
|
-
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
top: sanitizeInteger(crop.top, 0, 0, 10000),
|
|
515
|
+
left: sanitizeInteger(crop.left, 0, 0, 10000),
|
|
516
|
+
width: sanitizeInteger(crop.width, 0, 0, 10000),
|
|
517
|
+
height: sanitizeInteger(crop.height, 0, 0, 10000)
|
|
518
|
+
};
|
|
501
519
|
},
|
|
502
520
|
// This method return a default icon url if an attachment is missing
|
|
503
521
|
// to avoid template errors
|
|
@@ -669,6 +687,9 @@ module.exports = {
|
|
|
669
687
|
if (o.alt) {
|
|
670
688
|
value._alt = o.alt;
|
|
671
689
|
}
|
|
690
|
+
|
|
691
|
+
value._isCroppable = self.isCroppable(value);
|
|
692
|
+
|
|
672
693
|
o[key] = value;
|
|
673
694
|
|
|
674
695
|
// If one of our ancestors has a relationship to the piece that
|
|
@@ -678,11 +699,14 @@ module.exports = {
|
|
|
678
699
|
// apos.attachment.url with the returned object
|
|
679
700
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
680
701
|
const ancestor = ancestors[i];
|
|
681
|
-
const
|
|
682
|
-
|
|
702
|
+
const ancestorFields = ancestor.attachment &&
|
|
703
|
+
ancestor.attachment._id === value._id && ancestor._fields;
|
|
704
|
+
|
|
705
|
+
if (ancestorFields) {
|
|
683
706
|
value = _.clone(value);
|
|
684
|
-
|
|
685
|
-
value.
|
|
707
|
+
o.attachment = value;
|
|
708
|
+
value._crop = _.pick(ancestorFields, 'width', 'height', 'top', 'left');
|
|
709
|
+
value._focalPoint = _.pick(ancestorFields, 'x', 'y');
|
|
686
710
|
break;
|
|
687
711
|
}
|
|
688
712
|
}
|
|
@@ -690,15 +714,31 @@ module.exports = {
|
|
|
690
714
|
if (options.annotate) {
|
|
691
715
|
// Add URLs
|
|
692
716
|
value._urls = {};
|
|
717
|
+
if (value._crop) {
|
|
718
|
+
value._urls.uncropped = {};
|
|
719
|
+
}
|
|
693
720
|
if (value.group === 'images') {
|
|
694
721
|
_.each(self.imageSizes, function (size) {
|
|
695
722
|
value._urls[size.name] = self.url(value, { size: size.name });
|
|
723
|
+
if (value._crop) {
|
|
724
|
+
value._urls.uncropped[size.name] = self.url(value, {
|
|
725
|
+
size: size.name,
|
|
726
|
+
crop: false
|
|
727
|
+
});
|
|
728
|
+
}
|
|
696
729
|
});
|
|
697
730
|
value._urls.original = self.url(value, { size: 'original' });
|
|
731
|
+
if (value._crop) {
|
|
732
|
+
value._urls.uncropped.original = self.url(value, {
|
|
733
|
+
size: 'original',
|
|
734
|
+
crop: false
|
|
735
|
+
});
|
|
736
|
+
}
|
|
698
737
|
} else {
|
|
699
738
|
value._url = self.url(value);
|
|
700
739
|
}
|
|
701
740
|
}
|
|
741
|
+
|
|
702
742
|
winners.push(value);
|
|
703
743
|
}
|
|
704
744
|
});
|
|
@@ -762,14 +802,22 @@ module.exports = {
|
|
|
762
802
|
return attachment._focalPoint && typeof attachment._focalPoint.x === 'number';
|
|
763
803
|
},
|
|
764
804
|
// If a focal point is present on the attachment, convert it to
|
|
765
|
-
// CSS syntax for `
|
|
805
|
+
// CSS syntax for `object-position`. No trailing `;` is returned.
|
|
766
806
|
// The coordinates are in percentage terms.
|
|
767
|
-
|
|
807
|
+
focalPointToObjectPosition(attachment) {
|
|
768
808
|
if (!self.hasFocalPoint(attachment)) {
|
|
769
809
|
return 'center center';
|
|
770
810
|
}
|
|
771
811
|
const point = self.getFocalPoint(attachment);
|
|
772
|
-
return point.x
|
|
812
|
+
return `${point.x}% ${point.y}%`;
|
|
813
|
+
},
|
|
814
|
+
// Returns the effective attachment width.
|
|
815
|
+
getWidth(attachment) {
|
|
816
|
+
return attachment._crop ? attachment._crop.width : attachment.width;
|
|
817
|
+
},
|
|
818
|
+
// Returns the effective attachment height.
|
|
819
|
+
getHeight(attachment) {
|
|
820
|
+
return attachment._crop ? attachment._crop.height : attachment.height;
|
|
773
821
|
},
|
|
774
822
|
// Returns an object with `x` and `y` properties containing the
|
|
775
823
|
// focal point chosen by the user, as percentages. If there is no
|
|
@@ -788,7 +836,9 @@ module.exports = {
|
|
|
788
836
|
// Returns true if this type of attachment is croppable.
|
|
789
837
|
// Available as a template helper.
|
|
790
838
|
isCroppable(attachment) {
|
|
791
|
-
return attachment &&
|
|
839
|
+
return (attachment &&
|
|
840
|
+
self.croppable[self.resolveExtension(attachment.extension)]) ||
|
|
841
|
+
false;
|
|
792
842
|
},
|
|
793
843
|
// Returns true if this type of attachment is sized,
|
|
794
844
|
// i.e. uploadfs produces versions of it for each configured
|
|
@@ -1139,7 +1189,9 @@ module.exports = {
|
|
|
1139
1189
|
'all',
|
|
1140
1190
|
'hasFocalPoint',
|
|
1141
1191
|
'getFocalPoint',
|
|
1142
|
-
'
|
|
1192
|
+
'focalPointToObjectPosition',
|
|
1193
|
+
'getWidth',
|
|
1194
|
+
'getHeight',
|
|
1143
1195
|
'isCroppable'
|
|
1144
1196
|
]
|
|
1145
1197
|
};
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
// in your project. However you may find it easier to just use the
|
|
50
50
|
// `client` option.
|
|
51
51
|
|
|
52
|
-
const
|
|
52
|
+
const mongodbConnect = require('../../../lib/mongodb-connect');
|
|
53
|
+
const escapeHost = require('../../../lib/escape-host');
|
|
53
54
|
|
|
54
55
|
module.exports = {
|
|
55
56
|
options: {
|
|
@@ -119,18 +120,14 @@ module.exports = {
|
|
|
119
120
|
if (!self.options.name) {
|
|
120
121
|
self.options.name = self.apos.shortName;
|
|
121
122
|
}
|
|
122
|
-
uri += self.options.host + ':' + self.options.port + '/' + self.options.name;
|
|
123
|
+
uri += escapeHost(self.options.host) + ':' + self.options.port + '/' + self.options.name;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
useUnifiedTopology: true,
|
|
127
|
-
useNewUrlParser: true,
|
|
128
|
-
...Object(self.options.connect || {})
|
|
129
|
-
};
|
|
130
|
-
self.apos.dbClient = await mongo.MongoClient.connect(uri, connectOptions);
|
|
131
|
-
const parsed = new URL(uri);
|
|
126
|
+
self.apos.dbClient = await mongodbConnect(uri, self.options.connect);
|
|
132
127
|
self.uri = uri;
|
|
133
|
-
|
|
128
|
+
const parsed = new URL(uri);
|
|
129
|
+
self.apos.db = self.apos.dbClient.db(parsed.pathname.substring(1));
|
|
130
|
+
|
|
134
131
|
},
|
|
135
132
|
async versionCheck() {
|
|
136
133
|
if (!self.options.versionCheck) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const cuid = require('cuid');
|
|
3
|
+
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
|
|
3
4
|
|
|
4
5
|
// This module is responsible for managing all of the documents (apostrophe "docs")
|
|
5
6
|
// in the `aposDocs` mongodb collection.
|
|
@@ -29,11 +30,13 @@ module.exports = {
|
|
|
29
30
|
},
|
|
30
31
|
async init(self) {
|
|
31
32
|
self.managers = {};
|
|
33
|
+
self.contextOperations = [];
|
|
32
34
|
self.enableBrowserData();
|
|
33
35
|
await self.enableCollection();
|
|
34
36
|
self.apos.isNew = await self.detectNew();
|
|
35
37
|
await self.createIndexes();
|
|
36
38
|
self.addLegacyMigrations();
|
|
39
|
+
self.addCacheFieldMigration();
|
|
37
40
|
},
|
|
38
41
|
restApiRoutes(self) {
|
|
39
42
|
return {
|
|
@@ -198,7 +201,9 @@ module.exports = {
|
|
|
198
201
|
}
|
|
199
202
|
});
|
|
200
203
|
if (options.setUpdatedAtAndBy !== false) {
|
|
201
|
-
|
|
204
|
+
const date = new Date();
|
|
205
|
+
doc.updatedAt = date;
|
|
206
|
+
doc.cacheInvalidatedAt = date;
|
|
202
207
|
doc.updatedBy = req.user ? {
|
|
203
208
|
_id: req.user._id,
|
|
204
209
|
title: req.user.title || null,
|
|
@@ -268,7 +273,9 @@ module.exports = {
|
|
|
268
273
|
// deletes both the published and previous docs.
|
|
269
274
|
async deleteOtherModes(req, doc, options) {
|
|
270
275
|
if (doc.aposLocale && doc.aposLocale.endsWith(':draft')) {
|
|
271
|
-
|
|
276
|
+
await cleanup('published');
|
|
277
|
+
await self.emit('afterAllModesDeleted', req, doc, options);
|
|
278
|
+
return;
|
|
272
279
|
}
|
|
273
280
|
if (doc.aposLocale && doc.aposLocale.endsWith(':published')) {
|
|
274
281
|
return cleanup('previous');
|
|
@@ -436,16 +443,49 @@ module.exports = {
|
|
|
436
443
|
// If `options.permissions` is set explicitly to
|
|
437
444
|
// `false`, permissions checks are bypassed.
|
|
438
445
|
async insert(req, doc, options) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
const telemetry = self.apos.telemetry;
|
|
447
|
+
return telemetry.startActiveSpan(`model:${doc.type}:insert`, async (span) => {
|
|
448
|
+
span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'insert');
|
|
449
|
+
span.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
|
|
450
|
+
span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, doc.type);
|
|
451
|
+
span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, 'insert');
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
options = options || {};
|
|
455
|
+
const m = self.getManager(doc.type);
|
|
456
|
+
await m.emit('beforeInsert', req, doc, options);
|
|
457
|
+
await m.emit('beforeSave', req, doc, options);
|
|
458
|
+
|
|
459
|
+
await telemetry.startActiveSpan(`db:${doc.type}:insert`, async (spanInsert) => {
|
|
460
|
+
spanInsert.setAttribute(SemanticAttributes.CODE_FUNCTION, 'insertBody');
|
|
461
|
+
spanInsert.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
|
|
462
|
+
spanInsert.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, doc.type);
|
|
463
|
+
spanInsert.setAttribute(telemetry.Attributes.TARGET_FUNCTION, 'insert');
|
|
464
|
+
try {
|
|
465
|
+
const result = await self.insertBody(req, doc, options);
|
|
466
|
+
spanInsert.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
467
|
+
return result;
|
|
468
|
+
} catch (e) {
|
|
469
|
+
telemetry.handleError(spanInsert, e);
|
|
470
|
+
throw e;
|
|
471
|
+
} finally {
|
|
472
|
+
spanInsert.end();
|
|
473
|
+
}
|
|
474
|
+
}, span, {});
|
|
475
|
+
|
|
476
|
+
await m.emit('afterInsert', req, doc, options);
|
|
477
|
+
await m.emit('afterSave', req, doc, options);
|
|
478
|
+
// TODO: Remove `afterLoad` in next major version. Deprecated.
|
|
479
|
+
await m.emit('afterLoad', req, [ doc ]);
|
|
480
|
+
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
481
|
+
return doc;
|
|
482
|
+
} catch (err) {
|
|
483
|
+
telemetry.handleError(span, err);
|
|
484
|
+
throw err;
|
|
485
|
+
} finally {
|
|
486
|
+
span.end();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
449
489
|
},
|
|
450
490
|
// Updates the given document. If the slug is not
|
|
451
491
|
// unique it is made unique. `beforeUpdate`, `beforeSave`,
|
|
@@ -472,16 +512,49 @@ module.exports = {
|
|
|
472
512
|
// If `options.permissions` is set explicitly to
|
|
473
513
|
// `false`, permissions checks are bypassed.
|
|
474
514
|
async update(req, doc, options) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
515
|
+
const telemetry = self.apos.telemetry;
|
|
516
|
+
return telemetry.startActiveSpan(`model:${doc.type}:update`, async (span) => {
|
|
517
|
+
span.setAttribute(SemanticAttributes.CODE_FUNCTION, 'update');
|
|
518
|
+
span.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
|
|
519
|
+
span.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, doc.type);
|
|
520
|
+
span.setAttribute(telemetry.Attributes.TARGET_FUNCTION, 'update');
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
options = options || {};
|
|
524
|
+
const m = self.getManager(doc.type);
|
|
525
|
+
await m.emit('beforeUpdate', req, doc, options);
|
|
526
|
+
await m.emit('beforeSave', req, doc, options);
|
|
527
|
+
|
|
528
|
+
await telemetry.startActiveSpan(`db:${doc.type}:update`, async (spanUpdate) => {
|
|
529
|
+
spanUpdate.setAttribute(SemanticAttributes.CODE_FUNCTION, 'updateBody');
|
|
530
|
+
spanUpdate.setAttribute(SemanticAttributes.CODE_NAMESPACE, self.__meta.name);
|
|
531
|
+
spanUpdate.setAttribute(telemetry.Attributes.TARGET_NAMESPACE, doc.type);
|
|
532
|
+
spanUpdate.setAttribute(telemetry.Attributes.TARGET_FUNCTION, 'update');
|
|
533
|
+
try {
|
|
534
|
+
const result = await self.updateBody(req, doc, options);
|
|
535
|
+
spanUpdate.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
536
|
+
return result;
|
|
537
|
+
} catch (e) {
|
|
538
|
+
telemetry.handleError(spanUpdate, e);
|
|
539
|
+
throw e;
|
|
540
|
+
} finally {
|
|
541
|
+
spanUpdate.end();
|
|
542
|
+
}
|
|
543
|
+
}, span, {});
|
|
544
|
+
|
|
545
|
+
await m.emit('afterUpdate', req, doc, options);
|
|
546
|
+
await m.emit('afterSave', req, doc, options);
|
|
547
|
+
// TODO: Remove `afterLoad` in next major version. Deprecated.
|
|
548
|
+
await m.emit('afterLoad', req, [ doc ]);
|
|
549
|
+
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
550
|
+
return doc;
|
|
551
|
+
} catch (err) {
|
|
552
|
+
telemetry.handleError(span, err);
|
|
553
|
+
throw err;
|
|
554
|
+
} finally {
|
|
555
|
+
span.end();
|
|
556
|
+
}
|
|
557
|
+
});
|
|
485
558
|
},
|
|
486
559
|
|
|
487
560
|
// True delete. To place a document in the archive,
|
|
@@ -915,9 +988,39 @@ module.exports = {
|
|
|
915
988
|
'slug'
|
|
916
989
|
];
|
|
917
990
|
},
|
|
991
|
+
// Add context menu operation to be used in AposDocContextMenu.
|
|
992
|
+
// Expected operation format is:
|
|
993
|
+
// {
|
|
994
|
+
// context: 'update',
|
|
995
|
+
// action: 'someAction',
|
|
996
|
+
// modal: 'ModalComponent',
|
|
997
|
+
// label: 'Context Menu Label'
|
|
998
|
+
// }
|
|
999
|
+
// All properties are required.
|
|
1000
|
+
// The only supported `context` for now is `update`.
|
|
1001
|
+
// `action` is the operation idefntifier and should be globally unique.
|
|
1002
|
+
// Overriding existing custom actions is possible (the last wins).
|
|
1003
|
+
// `modal` is the name of the modal component to be opened.
|
|
1004
|
+
// `label` is the menu label to be shown when expanding the context menu.
|
|
1005
|
+
// Additional optional `modifiers` property is supported - button modifiers
|
|
1006
|
+
// as supported by `AposContextMenu` (e.g. modifiers: [ 'danger' ]).
|
|
1007
|
+
// An optional `manuallyPublished` boolean property is supported - if true
|
|
1008
|
+
// the menu will be shown only for docs which have `autopublish: false` and
|
|
1009
|
+
// `localized: true` options.
|
|
1010
|
+
addContextOperation(moduleName, operation) {
|
|
1011
|
+
self.contextOperations = [
|
|
1012
|
+
...self.contextOperations
|
|
1013
|
+
.filter(op => op.action !== operation.action),
|
|
1014
|
+
{
|
|
1015
|
+
...operation,
|
|
1016
|
+
moduleName
|
|
1017
|
+
}
|
|
1018
|
+
];
|
|
1019
|
+
},
|
|
918
1020
|
getBrowserData(req) {
|
|
919
1021
|
return {
|
|
920
|
-
action: self.action
|
|
1022
|
+
action: self.action,
|
|
1023
|
+
contextOperations: self.contextOperations
|
|
921
1024
|
};
|
|
922
1025
|
},
|
|
923
1026
|
migrateRelationshipIds(doc) {
|
|
@@ -1157,6 +1260,18 @@ module.exports = {
|
|
|
1157
1260
|
}
|
|
1158
1261
|
}
|
|
1159
1262
|
},
|
|
1263
|
+
// Add the "cacheInvalidatedAt" field to the documents that do not have it yet,
|
|
1264
|
+
// and set it to equal doc.updatedAt.
|
|
1265
|
+
setCacheField() {
|
|
1266
|
+
return self.apos.migration.eachDoc({ cacheInvalidatedAt: { $exists: 0 } }, 5, async doc => {
|
|
1267
|
+
await self.apos.doc.db.updateOne({ _id: doc._id }, {
|
|
1268
|
+
$set: { cacheInvalidatedAt: doc.updatedAt }
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
},
|
|
1272
|
+
addCacheFieldMigration() {
|
|
1273
|
+
self.apos.migration.add('add-cache-invalidated-at-field', self.setCacheField);
|
|
1274
|
+
},
|
|
1160
1275
|
...require('./lib/legacy-migrations')(self)
|
|
1161
1276
|
};
|
|
1162
1277
|
}
|