ghost 5.64.0 → 5.65.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/components/{tryghost-adapter-cache-memory-ttl-5.64.0.tgz → tryghost-adapter-cache-memory-ttl-5.65.0.tgz} +0 -0
- package/components/{tryghost-adapter-cache-redis-5.64.0.tgz → tryghost-adapter-cache-redis-5.65.0.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.64.0.tgz → tryghost-adapter-manager-5.65.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.65.0.tgz +0 -0
- package/components/tryghost-api-framework-5.65.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.65.0.tgz +0 -0
- package/components/{tryghost-audience-feedback-5.64.0.tgz → tryghost-audience-feedback-5.65.0.tgz} +0 -0
- package/components/{tryghost-bookshelf-repository-5.64.0.tgz → tryghost-bookshelf-repository-5.65.0.tgz} +0 -0
- package/components/tryghost-bootstrap-socket-5.65.0.tgz +0 -0
- package/components/tryghost-collections-5.65.0.tgz +0 -0
- package/components/{tryghost-constants-5.64.0.tgz → tryghost-constants-5.65.0.tgz} +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.64.0.tgz → tryghost-custom-theme-settings-service-5.65.0.tgz} +0 -0
- package/components/{tryghost-data-generator-5.64.0.tgz → tryghost-data-generator-5.65.0.tgz} +0 -0
- package/components/{tryghost-domain-events-5.64.0.tgz → tryghost-domain-events-5.65.0.tgz} +0 -0
- package/components/tryghost-donations-5.65.0.tgz +0 -0
- package/components/{tryghost-dynamic-routing-events-5.64.0.tgz → tryghost-dynamic-routing-events-5.65.0.tgz} +0 -0
- package/components/{tryghost-email-analytics-provider-mailgun-5.64.0.tgz → tryghost-email-analytics-provider-mailgun-5.65.0.tgz} +0 -0
- package/components/tryghost-email-analytics-service-5.65.0.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.64.0.tgz → tryghost-email-content-generator-5.65.0.tgz} +0 -0
- package/components/tryghost-email-events-5.65.0.tgz +0 -0
- package/components/{tryghost-email-service-5.64.0.tgz → tryghost-email-service-5.65.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.65.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.65.0.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.64.0.tgz → tryghost-express-dynamic-redirects-5.65.0.tgz} +0 -0
- package/components/{tryghost-external-media-inliner-5.64.0.tgz → tryghost-external-media-inliner-5.65.0.tgz} +0 -0
- package/components/{tryghost-extract-api-key-5.64.0.tgz → tryghost-extract-api-key-5.65.0.tgz} +0 -0
- package/components/{tryghost-html-to-plaintext-5.64.0.tgz → tryghost-html-to-plaintext-5.65.0.tgz} +0 -0
- package/components/tryghost-i18n-5.65.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.65.0.tgz +0 -0
- package/components/{tryghost-importer-revue-5.64.0.tgz → tryghost-importer-revue-5.65.0.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.65.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.64.0.tgz → tryghost-job-manager-5.65.0.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.64.0.tgz → tryghost-link-redirects-5.65.0.tgz} +0 -0
- package/components/tryghost-link-replacer-5.65.0.tgz +0 -0
- package/components/{tryghost-link-tracking-5.64.0.tgz → tryghost-link-tracking-5.65.0.tgz} +0 -0
- package/components/{tryghost-magic-link-5.64.0.tgz → tryghost-magic-link-5.65.0.tgz} +0 -0
- package/components/tryghost-mail-events-5.65.0.tgz +0 -0
- package/components/{tryghost-mailgun-client-5.64.0.tgz → tryghost-mailgun-client-5.65.0.tgz} +0 -0
- package/components/{tryghost-member-attribution-5.64.0.tgz → tryghost-member-attribution-5.65.0.tgz} +0 -0
- package/components/{tryghost-member-events-5.64.0.tgz → tryghost-member-events-5.65.0.tgz} +0 -0
- package/components/tryghost-members-api-5.65.0.tgz +0 -0
- package/components/{tryghost-members-csv-5.64.0.tgz → tryghost-members-csv-5.65.0.tgz} +0 -0
- package/components/{tryghost-members-events-service-5.64.0.tgz → tryghost-members-events-service-5.65.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.64.0.tgz → tryghost-members-importer-5.65.0.tgz} +0 -0
- package/components/{tryghost-members-offers-5.64.0.tgz → tryghost-members-offers-5.65.0.tgz} +0 -0
- package/components/tryghost-members-payments-5.65.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.65.0.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.64.0.tgz → tryghost-members-stripe-service-5.65.0.tgz} +0 -0
- package/components/tryghost-mentions-email-report-5.65.0.tgz +0 -0
- package/components/{tryghost-milestones-5.64.0.tgz → tryghost-milestones-5.65.0.tgz} +0 -0
- package/components/tryghost-minifier-5.65.0.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.65.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.65.0.tgz +0 -0
- package/components/{tryghost-mw-cache-control-5.64.0.tgz → tryghost-mw-cache-control-5.65.0.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.64.0.tgz → tryghost-mw-error-handler-5.65.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.65.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.65.0.tgz +0 -0
- package/components/{tryghost-mw-version-match-5.64.0.tgz → tryghost-mw-version-match-5.65.0.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.65.0.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.65.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.65.0.tgz +0 -0
- package/components/{tryghost-package-json-5.64.0.tgz → tryghost-package-json-5.65.0.tgz} +0 -0
- package/components/{tryghost-post-events-5.64.0.tgz → tryghost-post-events-5.65.0.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.64.0.tgz → tryghost-post-revisions-5.65.0.tgz} +0 -0
- package/components/tryghost-posts-service-5.65.0.tgz +0 -0
- package/components/tryghost-recommendations-5.65.0.tgz +0 -0
- package/components/tryghost-referrers-5.65.0.tgz +0 -0
- package/components/tryghost-security-5.65.0.tgz +0 -0
- package/components/tryghost-session-service-5.65.0.tgz +0 -0
- package/components/{tryghost-settings-path-manager-5.64.0.tgz → tryghost-settings-path-manager-5.65.0.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.65.0.tgz +0 -0
- package/components/{tryghost-staff-service-5.64.0.tgz → tryghost-staff-service-5.65.0.tgz} +0 -0
- package/components/{tryghost-stats-service-5.64.0.tgz → tryghost-stats-service-5.65.0.tgz} +0 -0
- package/components/{tryghost-tiers-5.64.0.tgz → tryghost-tiers-5.65.0.tgz} +0 -0
- package/components/{tryghost-update-check-service-5.64.0.tgz → tryghost-update-check-service-5.65.0.tgz} +0 -0
- package/components/{tryghost-verification-trigger-5.64.0.tgz → tryghost-verification-trigger-5.65.0.tgz} +0 -0
- package/components/{tryghost-version-notifications-data-service-5.64.0.tgz → tryghost-version-notifications-data-service-5.65.0.tgz} +0 -0
- package/components/tryghost-webmentions-5.65.0.tgz +0 -0
- package/core/built/admin/assets/admin-x-settings/CodeEditorView-2b332315.mjs +2347 -0
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +6 -0
- package/core/built/admin/assets/admin-x-settings/index-4a560e9e.mjs +19693 -0
- package/core/built/admin/assets/admin-x-settings/index-9cc1df42.mjs +13955 -0
- package/core/built/admin/assets/admin-x-settings/index-b8cc4f2e.mjs +3465 -0
- package/core/built/admin/assets/admin-x-settings/limit-service-7694c0d4.mjs +12902 -0
- package/core/built/admin/assets/admin-x-settings/modals-2073207d.mjs +11518 -0
- package/core/built/admin/assets/{chunk.143.ff0abfe4495f055b0f3d.js → chunk.143.c91dc1d2a8a5a1f318ec.js} +5 -5
- package/core/built/admin/assets/{chunk.178.3e470522312d6d4ed2e4.js → chunk.178.d921de78463d288f3bcd.js} +4 -4
- package/core/built/admin/assets/{chunk.237.9b7032162949850f6c76.js → chunk.518.396deed1a0c759d1f403.js} +1136 -1077
- package/core/built/admin/assets/{ghost-10ff4bcba99e1759098702a12c531352.js → ghost-38746349a72536a92f4d8235a582d716.js} +58 -57
- package/core/built/admin/assets/ghost-761b67711827277b7095ebc7acc5c49f.css +1 -0
- package/core/built/admin/assets/ghost-dark-d3f636c30e80bdd7f21d3fb4770073c9.css +1 -0
- package/core/built/admin/assets/{vendor-f8ce8bd43cf5dad6608f828ab48cee9b.js → vendor-240cd1dbd84a2e07709b867442c41840.js} +4 -4
- package/core/built/admin/index.html +6 -6
- package/core/frontend/src/cards/css/collection.css +42 -12
- package/core/server/api/endpoints/posts-public.js +63 -0
- package/core/server/api/endpoints/recommendations-public.js +1 -1
- package/core/server/api/endpoints/recommendations.js +20 -5
- package/core/server/api/endpoints/utils/serializers/output/config.js +0 -1
- package/core/server/api/endpoints/utils/serializers/output/site.js +1 -1
- package/core/server/data/migrations/versions/5.64/2023-09-19-04-25-40-truncate-stale-built-in-collections-posts.js +6 -8
- package/core/server/data/migrations/versions/5.64/2023-09-19-04-34-10-repopulate-built-in-collection-posts.js +6 -65
- package/core/server/data/migrations/versions/5.65/2023-09-22-06-42-15-truncate-stale-built-in-collections-posts.js +12 -0
- package/core/server/data/migrations/versions/5.65/2023-09-22-06-42-55-repopulate-built-in-featured-collection-posts.js +49 -0
- package/core/server/data/schema/fixtures/fixtures.json +0 -14
- package/core/server/lib/lexical.js +22 -3
- package/core/server/lib/request-external.js +10 -4
- package/core/server/models/post.js +71 -1
- package/core/server/models/relations/authors.js +1 -1
- package/core/server/services/collections/BookshelfCollectionsRepository.js +38 -35
- package/core/server/services/collections/PostsRepository.js +5 -0
- package/core/server/services/collections/service.js +7 -3
- package/core/server/services/members/content-gating.js +11 -7
- package/core/server/services/mentions/WebmentionMetadata.js +14 -2
- package/core/server/services/public-config/site.js +1 -1
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +3 -2
- package/core/server/web/api/endpoints/admin/routes.js +2 -1
- package/core/shared/config/defaults.json +0 -4
- package/core/shared/labs.js +1 -1
- package/package.json +160 -159
- package/yarn.lock +361 -364
- package/components/tryghost-announcement-bar-settings-5.64.0.tgz +0 -0
- package/components/tryghost-api-framework-5.64.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.64.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.64.0.tgz +0 -0
- package/components/tryghost-collections-5.64.0.tgz +0 -0
- package/components/tryghost-donations-5.64.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.64.0.tgz +0 -0
- package/components/tryghost-email-events-5.64.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.64.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.64.0.tgz +0 -0
- package/components/tryghost-i18n-5.64.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.64.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.64.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.64.0.tgz +0 -0
- package/components/tryghost-mail-events-5.64.0.tgz +0 -0
- package/components/tryghost-members-api-5.64.0.tgz +0 -0
- package/components/tryghost-members-payments-5.64.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.64.0.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.64.0.tgz +0 -0
- package/components/tryghost-minifier-5.64.0.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.64.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.64.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.64.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.64.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.64.0.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.64.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.64.0.tgz +0 -0
- package/components/tryghost-posts-service-5.64.0.tgz +0 -0
- package/components/tryghost-recommendations-5.64.0.tgz +0 -0
- package/components/tryghost-referrers-5.64.0.tgz +0 -0
- package/components/tryghost-security-5.64.0.tgz +0 -0
- package/components/tryghost-session-service-5.64.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.64.0.tgz +0 -0
- package/components/tryghost-webmentions-5.64.0.tgz +0 -0
- package/core/built/admin/assets/ghost-33664cad4cd6664a8b5fa56e62c5005f.css +0 -1
- package/core/built/admin/assets/ghost-dark-0452daeaee3a9b16dcd954ea60dad518.css +0 -1
- /package/core/built/admin/assets/{chunk.237.9b7032162949850f6c76.js.LICENSE.txt → chunk.518.396deed1a0c759d1f403.js.LICENSE.txt} +0 -0
|
@@ -367,6 +367,47 @@ Post = ghostBookshelf.Model.extend({
|
|
|
367
367
|
ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
|
|
368
368
|
},
|
|
369
369
|
|
|
370
|
+
onFetched: async function onFetched(model, response, options) {
|
|
371
|
+
if (!labs.isSet('collectionsCard')) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await this.renderIfNeeded(model, options);
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
onFetchedCollection: async function onFetched(collection, response, options) {
|
|
379
|
+
if (!labs.isSet('collectionsCard')) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for await (const model of collection.models) {
|
|
384
|
+
await this.renderIfNeeded(model, options);
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
renderIfNeeded: async function renderIfNeeded(model, options = {}) {
|
|
389
|
+
// pages can have their html cleared to "queue" a re-render to update dynamic data such
|
|
390
|
+
// as collection cards. Detect that and re-render here so the page is always up to date
|
|
391
|
+
if (model.get('lexical') !== null && model.get('html') === null) {
|
|
392
|
+
const html = await lexicalLib.render(model.get('lexical'));
|
|
393
|
+
const plaintext = htmlToPlaintext.excerpt(html);
|
|
394
|
+
|
|
395
|
+
// set model attributes so they are available immediately in code that uses the returned model
|
|
396
|
+
model.set('html', html);
|
|
397
|
+
model.set('plaintext', plaintext);
|
|
398
|
+
|
|
399
|
+
// update database manually using knex to avoid hooks being called multiple times
|
|
400
|
+
const query = ghostBookshelf.knex.raw('UPDATE posts SET html = ?, plaintext = ? WHERE id = ?', [html, plaintext, model.id]);
|
|
401
|
+
if (options.transacting) {
|
|
402
|
+
await query.transacting(options.transacting);
|
|
403
|
+
} else {
|
|
404
|
+
await query;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return model;
|
|
409
|
+
},
|
|
410
|
+
|
|
370
411
|
/**
|
|
371
412
|
* We update the tags after the Post was inserted.
|
|
372
413
|
* We update the tags before the Post was updated, see `onSaving` event.
|
|
@@ -464,9 +505,25 @@ Post = ghostBookshelf.Model.extend({
|
|
|
464
505
|
}
|
|
465
506
|
},
|
|
466
507
|
|
|
467
|
-
onDestroyed: function onDestroyed(model, options) {
|
|
508
|
+
onDestroyed: async function onDestroyed(model, options) {
|
|
468
509
|
ghostBookshelf.Model.prototype.onDestroyed.apply(this, arguments);
|
|
469
510
|
|
|
511
|
+
if (labs.isSet('collectionsCard') && model.previous('type') === 'post' && model.previous('status') === 'published') {
|
|
512
|
+
// reset all page HTML when a published post is deleted so they can be re-rendered
|
|
513
|
+
// on next fetch so any collection cards are "dynamically" updated
|
|
514
|
+
const resetPages = function resetPages(transacting) {
|
|
515
|
+
return ghostBookshelf.knex.raw('UPDATE posts set html = NULL WHERE type = \'page\' AND lexical IS NOT NULL').transacting(transacting);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
if (options.transacting) {
|
|
519
|
+
await resetPages(options.transacting);
|
|
520
|
+
} else {
|
|
521
|
+
await ghostBookshelf.knex.transaction(async (transacting) => {
|
|
522
|
+
await resetPages(transacting);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
470
527
|
if (model.previous('status') === 'published') {
|
|
471
528
|
model.emitChange('unpublished', Object.assign({usePreviousAttribute: true}, options));
|
|
472
529
|
}
|
|
@@ -941,6 +998,19 @@ Post = ghostBookshelf.Model.extend({
|
|
|
941
998
|
})));
|
|
942
999
|
}
|
|
943
1000
|
|
|
1001
|
+
if (labs.isSet('collectionsCard') && this.get('type') === 'post' && (newStatus === 'published' || olderStatus === 'published')) {
|
|
1002
|
+
// reset all page HTML when a published post is updated so they can be re-rendered
|
|
1003
|
+
// on next fetch so any collection cards are "dynamically" updated
|
|
1004
|
+
ops.push(async function resetPageHTML() {
|
|
1005
|
+
const query = ghostBookshelf.knex.raw('UPDATE posts set html = NULL WHERE type = ? AND lexical IS NOT NULL', ['page']);
|
|
1006
|
+
if (options.transacting) {
|
|
1007
|
+
await query.transacting(options.transacting);
|
|
1008
|
+
} else {
|
|
1009
|
+
await query;
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
944
1014
|
return sequence(ops);
|
|
945
1015
|
},
|
|
946
1016
|
|
|
@@ -71,7 +71,7 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
|
|
|
71
71
|
model._originalOptions = collection._originalOptions;
|
|
72
72
|
}));
|
|
73
73
|
|
|
74
|
-
return proto.
|
|
74
|
+
return proto.onFetchedCollection.call(this, collection, attrs, options);
|
|
75
75
|
},
|
|
76
76
|
|
|
77
77
|
onCreating: function onCreating(model, attrs, options) {
|
|
@@ -12,9 +12,12 @@ const {default: ObjectID} = require('bson-objectid');
|
|
|
12
12
|
module.exports = class BookshelfCollectionsRepository {
|
|
13
13
|
#model;
|
|
14
14
|
#relationModel;
|
|
15
|
-
|
|
15
|
+
/** @type {import('@tryghost/domain-events')} */
|
|
16
|
+
#DomainEvents;
|
|
17
|
+
constructor(model, relationModel, DomainEvents) {
|
|
16
18
|
this.#model = model;
|
|
17
19
|
this.#relationModel = relationModel;
|
|
20
|
+
this.#DomainEvents = DomainEvents;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
async createTransaction(cb) {
|
|
@@ -201,46 +204,46 @@ module.exports = class BookshelfCollectionsRepository {
|
|
|
201
204
|
transacting: options.transaction
|
|
202
205
|
});
|
|
203
206
|
|
|
204
|
-
const collectionPostsRelations = collection.posts.map((postId, index) => {
|
|
205
|
-
return {
|
|
206
|
-
id: (new ObjectID).toHexString(),
|
|
207
|
-
sort_order: collection.type === 'manual' ? index : 0,
|
|
208
|
-
collection_id: collection.id,
|
|
209
|
-
post_id: postId
|
|
210
|
-
};
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const collectionPostRelationsToDeleteIds = [];
|
|
214
|
-
|
|
215
207
|
if (collection.type === 'manual') {
|
|
208
|
+
const collectionPostsRelations = collection.posts.map((postId, index) => {
|
|
209
|
+
return {
|
|
210
|
+
id: (new ObjectID).toHexString(),
|
|
211
|
+
sort_order: index,
|
|
212
|
+
collection_id: collection.id,
|
|
213
|
+
post_id: postId
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
216
|
await this.#relationModel.query().delete().where('collection_id', collection.id).transacting(options.transaction);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
transaction: options.transaction,
|
|
220
|
-
columns: ['id', 'post_id']
|
|
221
|
-
};
|
|
222
|
-
const existingRelations = await this.#fetchCollectionPostIds(collection.id, collectionPostsOptions);
|
|
223
|
-
|
|
224
|
-
for (const existingRelation of existingRelations) {
|
|
225
|
-
const found = collectionPostsRelations.find((thing) => {
|
|
226
|
-
return thing.post_id === existingRelation.post_id;
|
|
227
|
-
});
|
|
228
|
-
if (found) {
|
|
229
|
-
found.id = null;
|
|
230
|
-
} else {
|
|
231
|
-
collectionPostRelationsToDeleteIds.push(existingRelation.id);
|
|
232
|
-
}
|
|
217
|
+
if (collectionPostsRelations.length > 0) {
|
|
218
|
+
await this.#relationModel.query().insert(collectionPostsRelations).transacting(options.transaction);
|
|
233
219
|
}
|
|
234
|
-
}
|
|
220
|
+
} else {
|
|
221
|
+
const collectionPostsToDelete = collection.events.filter(event => event.type === 'CollectionPostRemoved').map((event) => {
|
|
222
|
+
return event.data.post_id;
|
|
223
|
+
});
|
|
235
224
|
|
|
236
|
-
|
|
225
|
+
const collectionPostsToInsert = collection.events.filter(event => event.type === 'CollectionPostAdded').map((event) => {
|
|
226
|
+
return {
|
|
227
|
+
id: (new ObjectID).toHexString(),
|
|
228
|
+
sort_order: 0,
|
|
229
|
+
collection_id: collection.id,
|
|
230
|
+
post_id: event.data.post_id
|
|
231
|
+
};
|
|
232
|
+
});
|
|
237
233
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
234
|
+
if (collectionPostsToDelete.length > 0) {
|
|
235
|
+
await this.#relationModel.query().delete().where('collection_id', collection.id).whereIn('post_id', collectionPostsToDelete).transacting(options.transaction);
|
|
236
|
+
}
|
|
237
|
+
if (collectionPostsToInsert.length > 0) {
|
|
238
|
+
await this.#relationModel.query().insert(collectionPostsToInsert).transacting(options.transaction);
|
|
239
|
+
}
|
|
243
240
|
}
|
|
241
|
+
|
|
242
|
+
options.transaction.executionPromise.then(() => {
|
|
243
|
+
for (const event of collection.events) {
|
|
244
|
+
this.#DomainEvents.dispatch(event);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
};
|
|
@@ -4,6 +4,11 @@ class PostsRepository {
|
|
|
4
4
|
this.moment = moment;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
async getAllIds() {
|
|
8
|
+
const rows = await this.models.Post.query().select('id').where('type', 'post');
|
|
9
|
+
|
|
10
|
+
return rows.map(row => row.id);
|
|
11
|
+
}
|
|
7
12
|
async getAll({filter, transaction}) {
|
|
8
13
|
const {data: models} = await this.models.Post.findPage({
|
|
9
14
|
filter: `(${filter})+type:post`,
|
|
@@ -12,7 +12,7 @@ class CollectionsServiceWrapper {
|
|
|
12
12
|
const DomainEvents = require('@tryghost/domain-events');
|
|
13
13
|
const postsRepository = require('./PostsRepository').getInstance();
|
|
14
14
|
const models = require('../../models');
|
|
15
|
-
const collectionsRepositoryInMemory = new BookshelfCollectionsRepository(models.Collection, models.CollectionPost);
|
|
15
|
+
const collectionsRepositoryInMemory = new BookshelfCollectionsRepository(models.Collection, models.CollectionPost, DomainEvents);
|
|
16
16
|
|
|
17
17
|
const collectionsService = new CollectionsService({
|
|
18
18
|
collectionsRepository: collectionsRepositoryInMemory,
|
|
@@ -34,8 +34,12 @@ class CollectionsServiceWrapper {
|
|
|
34
34
|
const config = require('../../../shared/config');
|
|
35
35
|
const labs = require('../../../shared/labs');
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
if (config.get('hostSettings:collections:enabled')
|
|
37
|
+
// CASE: emergency kill switch in case we need to disable collections outside of labs
|
|
38
|
+
if (config.get('hostSettings:collections:enabled') === false) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (labs.isSet('collections')) {
|
|
39
43
|
if (inited) {
|
|
40
44
|
return;
|
|
41
45
|
}
|
|
@@ -8,12 +8,6 @@ const BLOCK_ACCESS = false;
|
|
|
8
8
|
|
|
9
9
|
// TODO: better place to store this?
|
|
10
10
|
const MEMBER_NQL_EXPANSIONS = [{
|
|
11
|
-
key: 'labels',
|
|
12
|
-
replacement: 'labels.slug'
|
|
13
|
-
}, {
|
|
14
|
-
key: 'label',
|
|
15
|
-
replacement: 'labels.slug'
|
|
16
|
-
}, {
|
|
17
11
|
key: 'products',
|
|
18
12
|
replacement: 'products.slug'
|
|
19
13
|
}, {
|
|
@@ -21,6 +15,16 @@ const MEMBER_NQL_EXPANSIONS = [{
|
|
|
21
15
|
replacement: 'products.slug'
|
|
22
16
|
}];
|
|
23
17
|
|
|
18
|
+
const rejectUnknownKeys = input => nql.utils.mapQuery(input, function (value, key) {
|
|
19
|
+
if (!['product', 'products', 'status'].includes(key.toLowerCase())) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
[key]: value
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
24
28
|
/**
|
|
25
29
|
* @param {object} post - A post object to check access to
|
|
26
30
|
* @param {object} member - The member whos access should be checked
|
|
@@ -50,7 +54,7 @@ function checkPostAccess(post, member) {
|
|
|
50
54
|
}).join(',');
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
if (visibility && member.status && nql(visibility, {expansions: MEMBER_NQL_EXPANSIONS}).queryJSON(member)) {
|
|
57
|
+
if (visibility && member.status && nql(visibility, {expansions: MEMBER_NQL_EXPANSIONS, transformer: rejectUnknownKeys}).queryJSON(member)) {
|
|
54
58
|
return PERMIT_ACCESS;
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -34,7 +34,13 @@ module.exports = class WebmentionMetadata {
|
|
|
34
34
|
*/
|
|
35
35
|
async fetch(url) {
|
|
36
36
|
const mappedUrl = this.#getMappedUrl(url);
|
|
37
|
-
const data = await oembedService.fetchOembedDataFromUrl(mappedUrl.href, 'mention'
|
|
37
|
+
const data = await oembedService.fetchOembedDataFromUrl(mappedUrl.href, 'mention', {
|
|
38
|
+
timeout: 15000,
|
|
39
|
+
retry: {
|
|
40
|
+
// Only retry on network issues, or specific HTTP status codes
|
|
41
|
+
limit: 3
|
|
42
|
+
}
|
|
43
|
+
});
|
|
38
44
|
|
|
39
45
|
const result = {
|
|
40
46
|
siteTitle: data.metadata.publisher,
|
|
@@ -50,7 +56,13 @@ module.exports = class WebmentionMetadata {
|
|
|
50
56
|
if (mappedUrl.href !== url.href) {
|
|
51
57
|
// Still need to fetch body and contentType separately now
|
|
52
58
|
// For verification
|
|
53
|
-
const {body, contentType} = await oembedService.fetchPageHtml(url
|
|
59
|
+
const {body, contentType} = await oembedService.fetchPageHtml(url, {
|
|
60
|
+
timeout: 15000,
|
|
61
|
+
retry: {
|
|
62
|
+
// Only retry on network issues, or specific HTTP status codes
|
|
63
|
+
limit: 3
|
|
64
|
+
}
|
|
65
|
+
});
|
|
54
66
|
result.body = body;
|
|
55
67
|
result.contentType = contentType;
|
|
56
68
|
}
|
|
@@ -14,7 +14,7 @@ module.exports = function getSiteProperties() {
|
|
|
14
14
|
locale: settingsCache.get('locale'),
|
|
15
15
|
url: urlUtils.urlFor('home', true),
|
|
16
16
|
version: ghostVersion.safe,
|
|
17
|
-
|
|
17
|
+
allow_external_signup: settingsCache.get('allow_self_signup') && !(settingsCache.get('portal_signup_checkbox_required') && settingsCache.get('portal_signup_terms_html'))
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
if (config.get('client_sentry') && !config.get('client_sentry').disabled) {
|
|
@@ -45,7 +45,7 @@ class RecommendationServiceWrapper {
|
|
|
45
45
|
|
|
46
46
|
const mentions = require('../mentions');
|
|
47
47
|
|
|
48
|
-
if (!mentions.sendingService) {
|
|
48
|
+
if (!mentions.sendingService || !mentions.api) {
|
|
49
49
|
// eslint-disable-next-line ghost/ghost-custom/no-native-error
|
|
50
50
|
throw new Error('MentionSendingService not intialized, but this is a dependency of RecommendationServiceWrapper. Check boot order.');
|
|
51
51
|
}
|
|
@@ -75,7 +75,8 @@ class RecommendationServiceWrapper {
|
|
|
75
75
|
wellknownService,
|
|
76
76
|
mentionSendingService: mentions.sendingService,
|
|
77
77
|
clickEventRepository: this.clickEventRepository,
|
|
78
|
-
subscribeEventRepository: this.subscribeEventRepository
|
|
78
|
+
subscribeEventRepository: this.subscribeEventRepository,
|
|
79
|
+
mentionsApi: mentions.api
|
|
79
80
|
});
|
|
80
81
|
this.controller = new RecommendationController({
|
|
81
82
|
service: this.service
|
|
@@ -46,7 +46,7 @@ module.exports = function apiRoutes() {
|
|
|
46
46
|
router.del('/posts/:id', mw.authAdminApi, http(api.posts.destroy));
|
|
47
47
|
router.post('/posts/:id/copy', mw.authAdminApi, http(api.posts.copy));
|
|
48
48
|
|
|
49
|
-
router.get('/mentions',
|
|
49
|
+
router.get('/mentions', mw.authAdminApi, http(api.mentions.browse));
|
|
50
50
|
|
|
51
51
|
router.put('/comments/:id', mw.authAdminApi, http(api.comments.edit));
|
|
52
52
|
|
|
@@ -349,6 +349,7 @@ module.exports = function apiRoutes() {
|
|
|
349
349
|
|
|
350
350
|
// Recommendations
|
|
351
351
|
router.get('/recommendations', mw.authAdminApi, http(api.recommendations.browse));
|
|
352
|
+
router.get('/recommendations/:id', mw.authAdminApi, http(api.recommendations.read));
|
|
352
353
|
router.post('/recommendations', mw.authAdminApi, http(api.recommendations.add));
|
|
353
354
|
router.put('/recommendations/:id', mw.authAdminApi, http(api.recommendations.edit));
|
|
354
355
|
router.del('/recommendations/:id', mw.authAdminApi, http(api.recommendations.destroy));
|
|
@@ -201,10 +201,6 @@
|
|
|
201
201
|
"url": "https://cdn.jsdelivr.net/ghost/koenig-lexical@~{version}/dist/koenig-lexical.umd.js",
|
|
202
202
|
"version": "0.4"
|
|
203
203
|
},
|
|
204
|
-
"adminX": {
|
|
205
|
-
"url": "https://cdn.jsdelivr.net/ghost/admin-x-settings@~{version}/dist/admin-x-settings.js",
|
|
206
|
-
"version": "0.0"
|
|
207
|
-
},
|
|
208
204
|
"signupForm": {
|
|
209
205
|
"url": "https://cdn.jsdelivr.net/ghost/signup-form@~{version}/umd/signup-form.min.js",
|
|
210
206
|
"version": "0.1"
|
package/core/shared/labs.js
CHANGED
|
@@ -15,6 +15,7 @@ const messages = {
|
|
|
15
15
|
// flags in this list always return `true`, allows quick global enable prior to full flag removal
|
|
16
16
|
const GA_FEATURES = [
|
|
17
17
|
'audienceFeedback',
|
|
18
|
+
'collections',
|
|
18
19
|
'themeErrorsNotification',
|
|
19
20
|
'outboundLinkTagging',
|
|
20
21
|
'announcementBar',
|
|
@@ -36,7 +37,6 @@ const ALPHA_FEATURES = [
|
|
|
36
37
|
'websockets',
|
|
37
38
|
'stripeAutomaticTax',
|
|
38
39
|
'emailCustomization',
|
|
39
|
-
'collections',
|
|
40
40
|
'adminXSettings',
|
|
41
41
|
'mailEvents',
|
|
42
42
|
'collectionsCard',
|