ghost 5.61.3 → 5.63.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.63.0.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.63.0.tgz +0 -0
- package/components/{tryghost-adapter-manager-5.61.3.tgz → tryghost-adapter-manager-5.63.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.63.0.tgz +0 -0
- package/components/tryghost-api-framework-5.63.0.tgz +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.61.3.tgz → tryghost-api-version-compatibility-service-5.63.0.tgz} +0 -0
- package/components/{tryghost-audience-feedback-5.61.3.tgz → tryghost-audience-feedback-5.63.0.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.63.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.63.0.tgz +0 -0
- package/components/tryghost-collections-5.63.0.tgz +0 -0
- package/components/{tryghost-constants-5.61.3.tgz → tryghost-constants-5.63.0.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.63.0.tgz +0 -0
- package/components/tryghost-data-generator-5.63.0.tgz +0 -0
- package/components/tryghost-domain-events-5.63.0.tgz +0 -0
- package/components/tryghost-donations-5.63.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.63.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.63.0.tgz +0 -0
- package/components/{tryghost-email-analytics-service-5.61.3.tgz → tryghost-email-analytics-service-5.63.0.tgz} +0 -0
- package/components/tryghost-email-content-generator-5.63.0.tgz +0 -0
- package/components/tryghost-email-events-5.63.0.tgz +0 -0
- package/components/{tryghost-email-service-5.61.3.tgz → tryghost-email-service-5.63.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.63.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.63.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.63.0.tgz +0 -0
- package/components/{tryghost-external-media-inliner-5.61.3.tgz → tryghost-external-media-inliner-5.63.0.tgz} +0 -0
- package/components/{tryghost-extract-api-key-5.61.3.tgz → tryghost-extract-api-key-5.63.0.tgz} +0 -0
- package/components/{tryghost-html-to-plaintext-5.61.3.tgz → tryghost-html-to-plaintext-5.63.0.tgz} +0 -0
- package/components/tryghost-i18n-5.63.0.tgz +0 -0
- package/components/{tryghost-importer-handler-content-files-5.61.3.tgz → tryghost-importer-handler-content-files-5.63.0.tgz} +0 -0
- package/components/{tryghost-importer-revue-5.61.3.tgz → tryghost-importer-revue-5.63.0.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.63.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.61.3.tgz → tryghost-job-manager-5.63.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.63.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.63.0.tgz +0 -0
- package/components/{tryghost-link-tracking-5.61.3.tgz → tryghost-link-tracking-5.63.0.tgz} +0 -0
- package/components/tryghost-magic-link-5.63.0.tgz +0 -0
- package/components/tryghost-mail-events-5.63.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.63.0.tgz +0 -0
- package/components/{tryghost-member-attribution-5.61.3.tgz → tryghost-member-attribution-5.63.0.tgz} +0 -0
- package/components/tryghost-member-events-5.63.0.tgz +0 -0
- package/components/tryghost-members-api-5.63.0.tgz +0 -0
- package/components/{tryghost-members-csv-5.61.3.tgz → tryghost-members-csv-5.63.0.tgz} +0 -0
- package/components/{tryghost-members-events-service-5.61.3.tgz → tryghost-members-events-service-5.63.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.61.3.tgz → tryghost-members-importer-5.63.0.tgz} +0 -0
- package/components/tryghost-members-offers-5.63.0.tgz +0 -0
- package/components/tryghost-members-payments-5.63.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.63.0.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.61.3.tgz → tryghost-members-stripe-service-5.63.0.tgz} +0 -0
- package/components/tryghost-mentions-email-report-5.63.0.tgz +0 -0
- package/components/{tryghost-milestones-5.61.3.tgz → tryghost-milestones-5.63.0.tgz} +0 -0
- package/components/{tryghost-minifier-5.61.3.tgz → tryghost-minifier-5.63.0.tgz} +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.63.0.tgz +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.61.3.tgz → tryghost-mw-api-version-mismatch-5.63.0.tgz} +0 -0
- package/components/tryghost-mw-cache-control-5.63.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.61.3.tgz → tryghost-mw-error-handler-5.63.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.63.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.63.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.63.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.63.0.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.63.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.63.0.tgz +0 -0
- package/components/{tryghost-package-json-5.61.3.tgz → tryghost-package-json-5.63.0.tgz} +0 -0
- package/components/tryghost-post-events-5.63.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.63.0.tgz +0 -0
- package/components/tryghost-posts-service-5.63.0.tgz +0 -0
- package/components/tryghost-recommendations-5.63.0.tgz +0 -0
- package/components/tryghost-referrers-5.63.0.tgz +0 -0
- package/components/{tryghost-security-5.61.3.tgz → tryghost-security-5.63.0.tgz} +0 -0
- package/components/tryghost-session-service-5.63.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.63.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.63.0.tgz +0 -0
- package/components/{tryghost-staff-service-5.61.3.tgz → tryghost-staff-service-5.63.0.tgz} +0 -0
- package/components/tryghost-stats-service-5.63.0.tgz +0 -0
- package/components/{tryghost-tiers-5.61.3.tgz → tryghost-tiers-5.63.0.tgz} +0 -0
- package/components/{tryghost-update-check-service-5.61.3.tgz → tryghost-update-check-service-5.63.0.tgz} +0 -0
- package/components/{tryghost-verification-trigger-5.61.3.tgz → tryghost-verification-trigger-5.63.0.tgz} +0 -0
- package/components/tryghost-version-notifications-data-service-5.63.0.tgz +0 -0
- package/components/tryghost-webmentions-5.63.0.tgz +0 -0
- package/core/built/admin/assets/{chunk.143.da84b3b434141aff0f01.js → chunk.143.1c5d21facf1f9b9beef9.js} +6 -5
- package/core/built/admin/assets/{chunk.178.659d369f286d4162b294.js → chunk.178.43b03c9ac011a54262b0.js} +4 -4
- package/core/built/admin/assets/{chunk.208.dbf172ad32f72f21a5dc.js → chunk.237.9b7032162949850f6c76.js} +778 -702
- package/core/built/admin/assets/{ghost-8a4e981c272f793157133814ca7c7e84.css → ghost-33664cad4cd6664a8b5fa56e62c5005f.css} +1 -1
- package/core/built/admin/assets/{ghost-54f84395df6a6fb47b37008ded8eba22.js → ghost-7c3f2de2ec83e591ad9f9db5165b2733.js} +68 -63
- package/core/built/admin/assets/ghost-dark-0452daeaee3a9b16dcd954ea60dad518.css +1 -0
- package/core/built/admin/assets/{vendor-3631184082d609038638c1e169a002e7.js → vendor-f8ce8bd43cf5dad6608f828ab48cee9b.js} +249 -322
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/foreach.js +8 -0
- package/core/frontend/helpers/get.js +3 -0
- package/core/frontend/meta/schema.js +1 -1
- package/core/frontend/services/data/checks.js +6 -0
- package/core/frontend/src/cards/css/collection.css +186 -0
- package/core/frontend/src/cards/css/header_v2.css +19 -23
- package/core/frontend/src/cards/css/signup.css +20 -23
- package/core/server/adapters/storage/LocalStorageBase.js +1 -8
- package/core/server/api/endpoints/recommendations-public.js +44 -5
- package/core/server/api/endpoints/recommendations.js +7 -3
- package/core/server/api/endpoints/utils/serializers/input/pages.js +8 -0
- package/core/server/api/endpoints/utils/serializers/input/posts.js +9 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +22 -5
- package/core/server/api/endpoints/utils/serializers/output/site.js +2 -0
- package/core/server/data/exporter/table-lists.js +3 -1
- package/core/server/data/migrations/versions/5.63/2023-09-12-11-22-10-add-recommendation-click-events-table.js +8 -0
- package/core/server/data/migrations/versions/5.63/2023-09-12-11-22-11-add-recommendation-subscribe-events-table.js +8 -0
- package/core/server/data/migrations/versions/5.63/2023-09-13-13-03-10-add-ghost-core-content-integration.js +57 -0
- package/core/server/data/migrations/versions/5.63/2023-09-13-13-34-11-add-ghost-core-content-integration-key.js +87 -0
- package/core/server/data/schema/fixtures/fixtures.json +7 -0
- package/core/server/data/schema/schema.js +12 -0
- package/core/server/lib/lexical.js +35 -1
- package/core/server/models/post.js +12 -2
- package/core/server/models/recommendation-click-event.js +22 -0
- package/core/server/models/recommendation-subscribe-event.js +22 -0
- package/core/server/services/collections/BookshelfCollectionsRepository.js +67 -5
- package/core/server/services/collections/service.js +8 -8
- package/core/server/services/members/middleware.js +14 -1
- package/core/server/services/members/utils.js +1 -0
- package/core/server/services/mentions/WebmentionMetadata.js +38 -1
- package/core/server/services/mentions/service.js +2 -1
- package/core/server/services/offers/OfferBookshelfRepository.js +230 -0
- package/core/server/services/offers/service.js +7 -3
- package/core/server/services/public-config/site.js +3 -1
- package/core/server/services/recommendations/RecommendationEnablerService.js +28 -0
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +45 -2
- package/core/server/services/settings/SettingsBREADService.js +6 -1
- package/core/server/services/settings/settings-service.js +1 -0
- package/core/server/services/settings-helpers/SettingsHelpers.js +7 -0
- package/core/server/web/members/app.js +21 -0
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/settings-cache/public.js +3 -1
- package/package.json +161 -160
- package/yarn.lock +492 -512
- package/components/tryghost-adapter-cache-memory-ttl-5.61.3.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.61.3.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.61.3.tgz +0 -0
- package/components/tryghost-api-framework-5.61.3.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.61.3.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.61.3.tgz +0 -0
- package/components/tryghost-collections-5.61.3.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.61.3.tgz +0 -0
- package/components/tryghost-data-generator-5.61.3.tgz +0 -0
- package/components/tryghost-domain-events-5.61.3.tgz +0 -0
- package/components/tryghost-donations-5.61.3.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.61.3.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.61.3.tgz +0 -0
- package/components/tryghost-email-content-generator-5.61.3.tgz +0 -0
- package/components/tryghost-email-events-5.61.3.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.61.3.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.61.3.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.61.3.tgz +0 -0
- package/components/tryghost-i18n-5.61.3.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.61.3.tgz +0 -0
- package/components/tryghost-link-redirects-5.61.3.tgz +0 -0
- package/components/tryghost-link-replacer-5.61.3.tgz +0 -0
- package/components/tryghost-magic-link-5.61.3.tgz +0 -0
- package/components/tryghost-mail-events-5.61.3.tgz +0 -0
- package/components/tryghost-mailgun-client-5.61.3.tgz +0 -0
- package/components/tryghost-member-events-5.61.3.tgz +0 -0
- package/components/tryghost-members-api-5.61.3.tgz +0 -0
- package/components/tryghost-members-offers-5.61.3.tgz +0 -0
- package/components/tryghost-members-payments-5.61.3.tgz +0 -0
- package/components/tryghost-members-ssr-5.61.3.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.61.3.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.61.3.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.61.3.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.61.3.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.61.3.tgz +0 -0
- package/components/tryghost-mw-version-match-5.61.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.61.3.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.61.3.tgz +0 -0
- package/components/tryghost-oembed-service-5.61.3.tgz +0 -0
- package/components/tryghost-post-events-5.61.3.tgz +0 -0
- package/components/tryghost-post-revisions-5.61.3.tgz +0 -0
- package/components/tryghost-posts-service-5.61.3.tgz +0 -0
- package/components/tryghost-recommendations-5.61.3.tgz +0 -0
- package/components/tryghost-referrers-5.61.3.tgz +0 -0
- package/components/tryghost-session-service-5.61.3.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.61.3.tgz +0 -0
- package/components/tryghost-slack-notifications-5.61.3.tgz +0 -0
- package/components/tryghost-stats-service-5.61.3.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.61.3.tgz +0 -0
- package/components/tryghost-webmentions-5.61.3.tgz +0 -0
- package/core/built/admin/assets/ghost-dark-084169b0e968ef763dfbbf63b253e0c6.css +0 -1
- /package/core/built/admin/assets/{chunk.208.dbf172ad32f72f21a5dc.js.LICENSE.txt → chunk.237.9b7032162949850f6c76.js.LICENSE.txt} +0 -0
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
const oembedService = require('../oembed');
|
|
2
2
|
|
|
3
3
|
module.exports = class WebmentionMetadata {
|
|
4
|
+
/**
|
|
5
|
+
* Helpers that change the URL for which metadata for a given external resource is fetched. Return undefined to now handle the URL.
|
|
6
|
+
* @type {((url: URL) => URL|undefined)[]}
|
|
7
|
+
*/
|
|
8
|
+
#mappers = [];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {(url: URL) => URL|undefined} mapper
|
|
12
|
+
*/
|
|
13
|
+
addMapper(mapper) {
|
|
14
|
+
this.#mappers.push(mapper);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param {URL} url
|
|
20
|
+
*/
|
|
21
|
+
#getMappedUrl(url) {
|
|
22
|
+
for (const mapper of this.#mappers) {
|
|
23
|
+
const mappedUrl = mapper(url);
|
|
24
|
+
if (mappedUrl) {
|
|
25
|
+
return this.#getMappedUrl(mappedUrl);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return url;
|
|
29
|
+
}
|
|
30
|
+
|
|
4
31
|
/**
|
|
5
32
|
* @param {URL} url
|
|
6
33
|
* @returns {Promise<import('@tryghost/webmentions/lib/MentionsAPI').WebmentionMetadata>}
|
|
7
34
|
*/
|
|
8
35
|
async fetch(url) {
|
|
9
|
-
const
|
|
36
|
+
const mappedUrl = this.#getMappedUrl(url);
|
|
37
|
+
const data = await oembedService.fetchOembedDataFromUrl(mappedUrl.href, 'mention');
|
|
38
|
+
|
|
10
39
|
const result = {
|
|
11
40
|
siteTitle: data.metadata.publisher,
|
|
12
41
|
title: data.metadata.title,
|
|
@@ -17,6 +46,14 @@ module.exports = class WebmentionMetadata {
|
|
|
17
46
|
body: data.body,
|
|
18
47
|
contentType: data.contentType
|
|
19
48
|
};
|
|
49
|
+
|
|
50
|
+
if (mappedUrl.href !== url.href) {
|
|
51
|
+
// Still need to fetch body and contentType separately now
|
|
52
|
+
// For verification
|
|
53
|
+
const {body, contentType} = await oembedService.fetchPageHtml(url);
|
|
54
|
+
result.body = body;
|
|
55
|
+
result.contentType = contentType;
|
|
56
|
+
}
|
|
20
57
|
return result;
|
|
21
58
|
}
|
|
22
59
|
};
|
|
@@ -28,6 +28,7 @@ module.exports = {
|
|
|
28
28
|
/** @type {import('@tryghost/webmentions/lib/MentionsAPI')} */
|
|
29
29
|
api: null,
|
|
30
30
|
controller: new MentionController(),
|
|
31
|
+
metadata: new WebmentionMetadata(),
|
|
31
32
|
/** @type {import('@tryghost/webmentions/lib/MentionSendingService')} */
|
|
32
33
|
sendingService: null,
|
|
33
34
|
didInit: false,
|
|
@@ -40,7 +41,7 @@ module.exports = {
|
|
|
40
41
|
MentionModel: models.Mention,
|
|
41
42
|
DomainEvents
|
|
42
43
|
});
|
|
43
|
-
const webmentionMetadata =
|
|
44
|
+
const webmentionMetadata = this.metadata;
|
|
44
45
|
const discoveryService = new MentionDiscoveryService({externalRequest});
|
|
45
46
|
const resourceService = new ResourceService({
|
|
46
47
|
urlUtils,
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const {flowRight} = require('lodash');
|
|
2
|
+
const {mapKeyValues, mapQuery} = require('@tryghost/mongo-utils');
|
|
3
|
+
const DomainEvents = require('@tryghost/domain-events');
|
|
4
|
+
const {Offer} = require('@tryghost/members-offers');
|
|
5
|
+
const sentry = require('../../../shared/sentry');
|
|
6
|
+
const logger = require('@tryghost/logging');
|
|
7
|
+
|
|
8
|
+
const statusTransformer = mapKeyValues({
|
|
9
|
+
key: {
|
|
10
|
+
from: 'status',
|
|
11
|
+
to: 'active'
|
|
12
|
+
},
|
|
13
|
+
values: [{
|
|
14
|
+
from: 'active',
|
|
15
|
+
to: true
|
|
16
|
+
}, {
|
|
17
|
+
from: 'archived',
|
|
18
|
+
to: false
|
|
19
|
+
}]
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const rejectNonStatusTransformer = input => mapQuery(input, function (value, key) {
|
|
23
|
+
if (key !== 'status') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
[key]: value
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const mongoTransformer = flowRight(statusTransformer, rejectNonStatusTransformer);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {object} BaseOptions
|
|
36
|
+
* @prop {import('knex').Transaction} transacting
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} ListOptions
|
|
41
|
+
* @prop {import('knex').Transaction} transacting
|
|
42
|
+
* @prop {string} filter
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
class OfferBookshelfRepository {
|
|
46
|
+
/**
|
|
47
|
+
* @param {{forge: (data: object) => import('bookshelf').Model<Offer.OfferProps>}} OfferModel
|
|
48
|
+
* @param {{forge: (data: object) => import('bookshelf').Model<any>}} OfferRedemptionModel
|
|
49
|
+
*/
|
|
50
|
+
constructor(OfferModel, OfferRedemptionModel) {
|
|
51
|
+
/** @private */
|
|
52
|
+
this.OfferModel = OfferModel;
|
|
53
|
+
/** @private */
|
|
54
|
+
this.OfferRedemptionModel = OfferRedemptionModel;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @template T
|
|
59
|
+
* @param {(t: import('knex').Transaction) => Promise<T>} cb
|
|
60
|
+
* @returns {Promise<T>}
|
|
61
|
+
*/
|
|
62
|
+
async createTransaction(cb) {
|
|
63
|
+
return this.OfferModel.transaction(cb);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} name
|
|
68
|
+
* @param {BaseOptions} [options]
|
|
69
|
+
* @returns {Promise<boolean>}
|
|
70
|
+
*/
|
|
71
|
+
async existsByName(name, options) {
|
|
72
|
+
const model = await this.OfferModel.findOne({name}, options);
|
|
73
|
+
if (!model) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} code
|
|
81
|
+
* @param {BaseOptions} [options]
|
|
82
|
+
* @returns {Promise<boolean>}
|
|
83
|
+
*/
|
|
84
|
+
async existsByCode(code, options) {
|
|
85
|
+
const model = await this.OfferModel.findOne({code}, options);
|
|
86
|
+
if (!model) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @private
|
|
94
|
+
* @param {import('bookshelf').Model<any>} model
|
|
95
|
+
* @param {BaseOptions} options
|
|
96
|
+
* @returns {Promise<import('@tryghost/members-offers').Offer>}
|
|
97
|
+
*/
|
|
98
|
+
async mapToOffer(model, options) {
|
|
99
|
+
const json = model.toJSON();
|
|
100
|
+
|
|
101
|
+
const count = await this.OfferRedemptionModel.where({offer_id: json.id}).count('id', {
|
|
102
|
+
transacting: options.transacting
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
return await Offer.create({
|
|
106
|
+
id: json.id,
|
|
107
|
+
name: json.name,
|
|
108
|
+
code: json.code,
|
|
109
|
+
display_title: json.portal_title,
|
|
110
|
+
display_description: json.portal_description,
|
|
111
|
+
type: json.discount_type === 'amount' ? 'fixed' : json.discount_type,
|
|
112
|
+
amount: json.discount_amount,
|
|
113
|
+
cadence: json.interval,
|
|
114
|
+
currency: json.currency,
|
|
115
|
+
duration: json.duration,
|
|
116
|
+
duration_in_months: json.duration_in_months,
|
|
117
|
+
redemptionCount: count,
|
|
118
|
+
status: json.active ? 'active' : 'archived',
|
|
119
|
+
tier: {
|
|
120
|
+
id: json.product.id,
|
|
121
|
+
name: json.product.name
|
|
122
|
+
}
|
|
123
|
+
}, null);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.error(err);
|
|
126
|
+
sentry.captureException(err);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} id
|
|
133
|
+
* @param {BaseOptions} [options]
|
|
134
|
+
* @returns {Promise<import('@tryghost/members-offers').Offer>}
|
|
135
|
+
*/
|
|
136
|
+
async getById(id, options) {
|
|
137
|
+
const model = await this.OfferModel.findOne({id}, {
|
|
138
|
+
...options,
|
|
139
|
+
withRelated: ['product']
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!model) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return this.mapToOffer(model, options);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} id stripe_coupon_id
|
|
151
|
+
* @param {BaseOptions} [options]
|
|
152
|
+
* @returns {Promise<import('@tryghost/members-offers').Offer>}
|
|
153
|
+
*/
|
|
154
|
+
async getByStripeCouponId(id, options) {
|
|
155
|
+
const model = await this.OfferModel.findOne({stripe_coupon_id: id}, {
|
|
156
|
+
...options,
|
|
157
|
+
withRelated: ['product']
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!model) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this.mapToOffer(model, options);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {ListOptions} options
|
|
169
|
+
* @returns {Promise<import('@tryghost/members-offers').Offer[]>}
|
|
170
|
+
*/
|
|
171
|
+
async getAll(options) {
|
|
172
|
+
const models = await this.OfferModel.findAll({
|
|
173
|
+
...options,
|
|
174
|
+
mongoTransformer,
|
|
175
|
+
withRelated: ['product']
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const mapOptions = {
|
|
179
|
+
transacting: options && options.transacting
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const offers = models.map(model => this.mapToOffer(model, mapOptions));
|
|
183
|
+
|
|
184
|
+
return (await Promise.all(offers)).filter(offer => offer !== null);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {import('@tryghost/members-offers').Offer} offer
|
|
189
|
+
* @param {BaseOptions} [options]
|
|
190
|
+
* @returns {Promise<void>}
|
|
191
|
+
*/
|
|
192
|
+
async save(offer, options) {
|
|
193
|
+
/** @type any */
|
|
194
|
+
const data = {
|
|
195
|
+
id: offer.id,
|
|
196
|
+
name: offer.name.value,
|
|
197
|
+
code: offer.code.value,
|
|
198
|
+
portal_title: offer.displayTitle.value || null,
|
|
199
|
+
portal_description: offer.displayDescription.value || null,
|
|
200
|
+
discount_type: offer.type.value === 'fixed' ? 'amount' : offer.type.value,
|
|
201
|
+
discount_amount: offer.amount.value,
|
|
202
|
+
interval: offer.cadence.value,
|
|
203
|
+
product_id: offer.tier.id,
|
|
204
|
+
duration: offer.duration.value.type,
|
|
205
|
+
duration_in_months: offer.duration.value.type === 'repeating' ? offer.duration.value.months : null,
|
|
206
|
+
currency: offer.currency ? offer.currency.value : null,
|
|
207
|
+
active: offer.status.value === 'active'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (offer.isNew) {
|
|
211
|
+
await this.OfferModel.add(data, options);
|
|
212
|
+
} else {
|
|
213
|
+
await this.OfferModel.edit(data, {...options, id: data.id});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const event of offer.events) {
|
|
217
|
+
if (options.transacting) {
|
|
218
|
+
// Only dispatch the event after the transaction has finished
|
|
219
|
+
// Because else the offer won't be committed to the database yet
|
|
220
|
+
options.transacting.executionPromise.then(() => {
|
|
221
|
+
DomainEvents.dispatch(event);
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
DomainEvents.dispatch(event);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = OfferBookshelfRepository;
|
|
@@ -4,6 +4,7 @@ const OffersModule = require('@tryghost/members-offers');
|
|
|
4
4
|
const config = require('../../../shared/config');
|
|
5
5
|
const urlUtils = require('../../../shared/url-utils');
|
|
6
6
|
const models = require('../../models');
|
|
7
|
+
const OfferBookshelfRepository = require('./OfferBookshelfRepository');
|
|
7
8
|
|
|
8
9
|
let redirectManager;
|
|
9
10
|
|
|
@@ -15,10 +16,13 @@ module.exports = {
|
|
|
15
16
|
return urlUtils.urlJoin(urlUtils.getSubdir(), pathname);
|
|
16
17
|
}
|
|
17
18
|
});
|
|
19
|
+
const repository = new OfferBookshelfRepository(
|
|
20
|
+
models.Offer,
|
|
21
|
+
models.OfferRedemption
|
|
22
|
+
);
|
|
18
23
|
const offersModule = OffersModule.create({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
redirectManager
|
|
24
|
+
redirectManager,
|
|
25
|
+
repository
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
this.api = offersModule.api;
|
|
@@ -9,10 +9,12 @@ module.exports = function getSiteProperties() {
|
|
|
9
9
|
description: settingsCache.get('description'),
|
|
10
10
|
logo: settingsCache.get('logo'),
|
|
11
11
|
icon: settingsCache.get('icon'),
|
|
12
|
+
cover_image: settingsCache.get('cover_image'),
|
|
12
13
|
accent_color: settingsCache.get('accent_color'),
|
|
13
14
|
locale: settingsCache.get('locale'),
|
|
14
15
|
url: urlUtils.urlFor('home', true),
|
|
15
|
-
version: ghostVersion.safe
|
|
16
|
+
version: ghostVersion.safe,
|
|
17
|
+
allow_self_signup: settingsCache.get('allow_self_signup')
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
if (config.get('client_sentry') && !config.get('client_sentry').disabled) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = class RecommendationEnablerService {
|
|
2
|
+
/** @type {import('../settings/SettingsBREADService')} */
|
|
3
|
+
#settingsService;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} deps
|
|
7
|
+
* @param {import('../settings/SettingsBREADService')} deps.settingsService
|
|
8
|
+
*/
|
|
9
|
+
constructor(deps) {
|
|
10
|
+
this.#settingsService = deps.settingsService;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
getSetting() {
|
|
17
|
+
this.#settingsService.read('recommendations_enabled');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {string} value
|
|
23
|
+
* @returns Promise<void>
|
|
24
|
+
*/
|
|
25
|
+
async setSetting(value) {
|
|
26
|
+
this.#settingsService.edit([{key: 'recommendations_enabled', value}], {context: {internal: true}});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -4,6 +4,16 @@ class RecommendationServiceWrapper {
|
|
|
4
4
|
*/
|
|
5
5
|
repository;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @type {import('@tryghost/recommendations').BookshelfClickEventRepository}
|
|
9
|
+
*/
|
|
10
|
+
clickEventRepository;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @type {import('@tryghost/recommendations').BookshelfSubscribeEventRepository}
|
|
14
|
+
*/
|
|
15
|
+
subscribeEventRepository;
|
|
16
|
+
|
|
7
17
|
/**
|
|
8
18
|
* @type {import('@tryghost/recommendations').RecommendationController}
|
|
9
19
|
*/
|
|
@@ -23,7 +33,15 @@ class RecommendationServiceWrapper {
|
|
|
23
33
|
const urlUtils = require('../../../shared/url-utils');
|
|
24
34
|
const models = require('../../models');
|
|
25
35
|
const sentry = require('../../../shared/sentry');
|
|
26
|
-
const
|
|
36
|
+
const settings = require('../settings');
|
|
37
|
+
const RecommendationEnablerService = require('./RecommendationEnablerService');
|
|
38
|
+
const {
|
|
39
|
+
BookshelfRecommendationRepository,
|
|
40
|
+
RecommendationService,
|
|
41
|
+
RecommendationController,
|
|
42
|
+
WellknownService,
|
|
43
|
+
BookshelfClickEventRepository
|
|
44
|
+
} = require('@tryghost/recommendations');
|
|
27
45
|
|
|
28
46
|
const mentions = require('../mentions');
|
|
29
47
|
|
|
@@ -37,13 +55,27 @@ class RecommendationServiceWrapper {
|
|
|
37
55
|
urlUtils
|
|
38
56
|
});
|
|
39
57
|
|
|
58
|
+
const settingsService = settings.getSettingsBREADServiceInstance();
|
|
59
|
+
const recommendationEnablerService = new RecommendationEnablerService({settingsService});
|
|
60
|
+
|
|
40
61
|
this.repository = new BookshelfRecommendationRepository(models.Recommendation, {
|
|
41
62
|
sentry
|
|
42
63
|
});
|
|
64
|
+
|
|
65
|
+
this.clickEventRepository = new BookshelfClickEventRepository(models.RecommendationClickEvent, {
|
|
66
|
+
sentry
|
|
67
|
+
});
|
|
68
|
+
this.subscribeEventRepository = new BookshelfClickEventRepository(models.RecommendationSubscribeEvent, {
|
|
69
|
+
sentry
|
|
70
|
+
});
|
|
71
|
+
|
|
43
72
|
this.service = new RecommendationService({
|
|
44
73
|
repository: this.repository,
|
|
74
|
+
recommendationEnablerService,
|
|
45
75
|
wellknownService,
|
|
46
|
-
mentionSendingService: mentions.sendingService
|
|
76
|
+
mentionSendingService: mentions.sendingService,
|
|
77
|
+
clickEventRepository: this.clickEventRepository,
|
|
78
|
+
subscribeEventRepository: this.subscribeEventRepository
|
|
47
79
|
});
|
|
48
80
|
this.controller = new RecommendationController({
|
|
49
81
|
service: this.service
|
|
@@ -51,6 +83,17 @@ class RecommendationServiceWrapper {
|
|
|
51
83
|
|
|
52
84
|
// eslint-disable-next-line no-console
|
|
53
85
|
this.service.init().catch(console.error);
|
|
86
|
+
|
|
87
|
+
// Add mapper to WebmentionMetadata
|
|
88
|
+
mentions.metadata.addMapper((url) => {
|
|
89
|
+
const p = '/.well-known/recommendations.json';
|
|
90
|
+
if (url.pathname.endsWith(p)) {
|
|
91
|
+
// Strip p
|
|
92
|
+
const newUrl = new URL(url.toString());
|
|
93
|
+
newUrl.pathname = newUrl.pathname.slice(0, -p.length);
|
|
94
|
+
return newUrl;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
99
|
|
|
@@ -66,6 +66,11 @@ class SettingsBREADService {
|
|
|
66
66
|
const adminUrl = urlUtils.urlFor('admin', true);
|
|
67
67
|
const signinURL = new URL(adminUrl);
|
|
68
68
|
signinURL.hash = `/settings/members/?verifyEmail=${token}`;
|
|
69
|
+
// NOTE: to be removed in future, this is to ensure that the new settings are used when enabled
|
|
70
|
+
if (labsService && labsService.isSet('adminXSettings')) {
|
|
71
|
+
signinURL.hash = `/settings-x/portal/edit?verifyEmail=${token}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
return signinURL.href;
|
|
70
75
|
}
|
|
71
76
|
};
|
|
@@ -147,7 +152,7 @@ class SettingsBREADService {
|
|
|
147
152
|
* @param {Object[]} settings
|
|
148
153
|
* @param {Object} options
|
|
149
154
|
* @param {Object} [options.context]
|
|
150
|
-
* @param {Object} [stripeConnectData]
|
|
155
|
+
* @param {Object|null} [stripeConnectData]
|
|
151
156
|
* @returns
|
|
152
157
|
*/
|
|
153
158
|
async edit(settings, options, stripeConnectData) {
|
|
@@ -87,6 +87,7 @@ module.exports = {
|
|
|
87
87
|
|
|
88
88
|
fields.push(new CalculatedField({key: 'members_enabled', type: 'boolean', group: 'members', fn: settingsHelpers.isMembersEnabled.bind(settingsHelpers), dependents: ['members_signup_access']}));
|
|
89
89
|
fields.push(new CalculatedField({key: 'members_invite_only', type: 'boolean', group: 'members', fn: settingsHelpers.isMembersInviteOnly.bind(settingsHelpers), dependents: ['members_signup_access']}));
|
|
90
|
+
fields.push(new CalculatedField({key: 'allow_self_signup', type: 'boolean', group: 'members', fn: settingsHelpers.allowSelfSignup.bind(settingsHelpers), dependents: ['members_signup_access', 'portal_plans', 'stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']}));
|
|
90
91
|
fields.push(new CalculatedField({key: 'paid_members_enabled', type: 'boolean', group: 'members', fn: settingsHelpers.arePaidMembersEnabled.bind(settingsHelpers), dependents: ['members_signup_access', 'stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']}));
|
|
91
92
|
fields.push(new CalculatedField({key: 'firstpromoter_account', type: 'string', group: 'firstpromoter', fn: settingsHelpers.getFirstpromoterId.bind(settingsHelpers), dependents: ['firstpromoter', 'firstpromoter_id']}));
|
|
92
93
|
fields.push(new CalculatedField({key: 'donations_enabled', type: 'boolean', group: 'donations', fn: settingsHelpers.areDonationsEnabled.bind(settingsHelpers), dependents: ['stripe_secret_key', 'stripe_publishable_key', 'stripe_connect_secret_key', 'stripe_connect_publishable_key']}));
|
|
@@ -20,6 +20,13 @@ class SettingsHelpers {
|
|
|
20
20
|
return this.settingsCache.get('members_signup_access') === 'invite';
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* NOTE! The backend still allows to self signup if this returns false because a site might use built-in free signup forms apart from Portal
|
|
25
|
+
*/
|
|
26
|
+
allowSelfSignup() {
|
|
27
|
+
return this.settingsCache.get('members_signup_access') === 'all' && (this.settingsCache.get('portal_plans').includes('free') || !this.arePaidMembersEnabled());
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
/**
|
|
24
31
|
* @param {'direct' | 'connect'} type - The "type" of keys to fetch from settings
|
|
25
32
|
* @returns {{publicKey: string, secretKey: string} | null}
|
|
@@ -88,6 +88,27 @@ module.exports = function setupMembersApp() {
|
|
|
88
88
|
announcementRouter()
|
|
89
89
|
);
|
|
90
90
|
|
|
91
|
+
// Recommendations
|
|
92
|
+
membersApp.post(
|
|
93
|
+
'/api/recommendations/:id/clicked',
|
|
94
|
+
middleware.loadMemberSession,
|
|
95
|
+
http(api.recommendationsPublic.trackClicked)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Recommendations
|
|
99
|
+
membersApp.post(
|
|
100
|
+
'/api/recommendations/:id/subscribed',
|
|
101
|
+
middleware.loadMemberSession,
|
|
102
|
+
http(api.recommendationsPublic.trackSubscribed)
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Allow external systems to read public settings via the members api
|
|
106
|
+
// Without CORS issues and without a required integration token
|
|
107
|
+
// 1. Detect if a site is Running Ghost
|
|
108
|
+
// 2. For recommendations to know when we can offer 'one-click-subscribe' to know if members are enabled
|
|
109
|
+
// Why not content API? Domain can be different from recommended domain + CORS issues
|
|
110
|
+
membersApp.get('/api/site', http(api.site.read));
|
|
111
|
+
|
|
91
112
|
// API error handling
|
|
92
113
|
membersApp.use('/api', errorHandler.resourceNotFound);
|
|
93
114
|
membersApp.use('/api', errorHandler.handleJSONResponse(sentry));
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
},
|
|
183
183
|
"portal": {
|
|
184
184
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
185
|
-
"version": "2.
|
|
185
|
+
"version": "2.36"
|
|
186
186
|
},
|
|
187
187
|
"sodoSearch": {
|
|
188
188
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
"version": "0.4"
|
|
203
203
|
},
|
|
204
204
|
"adminX": {
|
|
205
|
-
"url": "https://cdn.jsdelivr.net/ghost/admin-x-settings@~{version}/dist/admin-x-settings.
|
|
205
|
+
"url": "https://cdn.jsdelivr.net/ghost/admin-x-settings@~{version}/dist/admin-x-settings.js",
|
|
206
206
|
"version": "0.0"
|
|
207
207
|
},
|
|
208
208
|
"signupForm": {
|
|
@@ -28,6 +28,7 @@ module.exports = {
|
|
|
28
28
|
twitter_description: 'twitter_description',
|
|
29
29
|
members_support_address: 'members_support_address',
|
|
30
30
|
members_enabled: 'members_enabled',
|
|
31
|
+
allow_self_signup: 'allow_self_signup',
|
|
31
32
|
members_invite_only: 'members_invite_only',
|
|
32
33
|
paid_members_enabled: 'paid_members_enabled',
|
|
33
34
|
firstpromoter_account: 'firstpromoter_account',
|
|
@@ -40,5 +41,6 @@ module.exports = {
|
|
|
40
41
|
portal_name: 'portal_name',
|
|
41
42
|
portal_button: 'portal_button',
|
|
42
43
|
comments_enabled: 'comments_enabled',
|
|
43
|
-
recommendations_enabled: 'recommendations_enabled'
|
|
44
|
+
recommendations_enabled: 'recommendations_enabled',
|
|
45
|
+
outbound_link_tagging: 'outbound_link_tagging'
|
|
44
46
|
};
|