ghost 5.33.7 → 5.34.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-redis-5.34.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.34.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.33.7.tgz → tryghost-api-framework-5.34.0.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.34.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.34.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.34.0.tgz +0 -0
- package/components/tryghost-constants-5.34.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.34.0.tgz +0 -0
- package/components/tryghost-data-generator-5.34.0.tgz +0 -0
- package/components/tryghost-domain-events-5.34.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.34.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.34.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.34.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.34.0.tgz +0 -0
- package/components/tryghost-email-events-5.34.0.tgz +0 -0
- package/components/tryghost-email-service-5.34.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.34.0.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.33.7.tgz → tryghost-express-dynamic-redirects-5.34.0.tgz} +0 -0
- package/components/tryghost-extract-api-key-5.34.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.34.0.tgz +0 -0
- package/components/tryghost-i18n-5.34.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.34.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.33.7.tgz → tryghost-job-manager-5.34.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.34.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.34.0.tgz +0 -0
- package/components/{tryghost-link-tracking-5.33.7.tgz → tryghost-link-tracking-5.34.0.tgz} +0 -0
- package/components/tryghost-magic-link-5.34.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.34.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.34.0.tgz +0 -0
- package/components/tryghost-member-events-5.34.0.tgz +0 -0
- package/components/tryghost-members-api-5.34.0.tgz +0 -0
- package/components/tryghost-members-csv-5.34.0.tgz +0 -0
- package/components/{tryghost-members-events-service-5.33.7.tgz → tryghost-members-events-service-5.34.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.33.7.tgz → tryghost-members-importer-5.34.0.tgz} +0 -0
- package/components/tryghost-members-offers-5.34.0.tgz +0 -0
- package/components/tryghost-members-payments-5.34.0.tgz +0 -0
- package/components/{tryghost-members-ssr-5.33.7.tgz → tryghost-members-ssr-5.34.0.tgz} +0 -0
- package/components/tryghost-members-stripe-service-5.34.0.tgz +0 -0
- package/components/tryghost-milestone-emails-5.34.0.tgz +0 -0
- package/components/tryghost-minifier-5.34.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.34.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.34.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.33.7.tgz → tryghost-mw-error-handler-5.34.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.34.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.34.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.34.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.34.0.tgz +0 -0
- package/components/{tryghost-package-json-5.33.7.tgz → tryghost-package-json-5.34.0.tgz} +0 -0
- package/components/tryghost-referrers-5.34.0.tgz +0 -0
- package/components/{tryghost-security-5.33.7.tgz → tryghost-security-5.34.0.tgz} +0 -0
- package/components/tryghost-session-service-5.34.0.tgz +0 -0
- package/components/{tryghost-settings-path-manager-5.33.7.tgz → tryghost-settings-path-manager-5.34.0.tgz} +0 -0
- package/components/tryghost-staff-service-5.34.0.tgz +0 -0
- package/components/tryghost-stats-service-5.34.0.tgz +0 -0
- package/components/tryghost-tags-public-5.34.0.tgz +0 -0
- package/components/tryghost-tiers-5.34.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.34.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.34.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.34.0.tgz +0 -0
- package/components/tryghost-webmentions-5.34.0.tgz +0 -0
- package/core/boot.js +8 -1
- package/core/built/admin/assets/{chunk.143.9057a1659075c0ee7336.js → chunk.143.a07da1be864f2e500e18.js} +14 -14
- package/core/built/admin/assets/{chunk.178.1d2c848ebe2c7baa38dd.js → chunk.178.9ed5500c900a9032f6c2.js} +4 -4
- package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js → chunk.616.181e1ad6c33f0bec7a65.js} +3519 -3512
- package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js.LICENSE.txt → chunk.616.181e1ad6c33f0bec7a65.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/{chunk.79.c3c2c05ea7ff7707fcad.js → chunk.79.ec143a398298020c87e6.js} +112 -112
- package/core/built/admin/assets/codemirror/{codemirror-a81c0653d8e57286b75c5a1792f80779.js → codemirror-6c43f4894cbd8db73d7f35cde836c58e.js} +810 -809
- package/core/built/admin/assets/{ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css → ghost-558c1e319d6e025bfab2054bc0f7fe83.css} +1 -1
- package/core/built/admin/assets/{ghost-904f203ff1f5f6bff4e55a03f39a4fbf.js → ghost-ad40d109653288e74a7cd922341fb33d.js} +115 -90
- package/core/built/admin/assets/{ghost-dark-ac0cb221eddc8652a0e7c263ed6513dc.css → ghost-dark-a15754df1f9070dc2525482ce22e2251.css} +1 -1
- package/core/built/admin/assets/simplemde/{simplemde-2885e4a40ed66fcae974595584efe50b.js → simplemde-28049a9bd7f432b0648747eb26958a33.js} +836 -836
- package/core/built/admin/assets/{vendor-0441964c34d58f2aacd5a04bbe240243.js → vendor-253d6527ca6353855164ef65f896f762.js} +1208 -1233
- package/core/built/admin/index.html +6 -6
- package/core/cli/generate-data.js +21 -3
- package/core/frontend/services/routing/router-manager.js +1 -1
- package/core/frontend/web/middleware/handle-image-sizes.js +6 -21
- package/core/server/adapters/cache/Redis.js +3 -0
- package/core/server/adapters/storage/LocalStorageBase.js +11 -1
- package/core/server/api/endpoints/images.js +47 -6
- package/core/server/api/endpoints/mentions.js +1 -1
- package/core/server/api/endpoints/tags-public.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/email-posts.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/mappers/mentions.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/pages.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/posts.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/previews.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +1 -0
- package/core/server/data/migrations/versions/5.34/2023-01-30-07-27-add-mentions-permission.js +10 -0
- package/core/server/data/migrations/versions/5.34/2023-02-08-03-08-add-mentions-notifications-column.js +7 -0
- package/core/server/data/migrations/versions/5.34/2023-02-08-22-32-add-mentions-delete-column.js +7 -0
- package/core/server/data/schema/fixtures/fixtures.json +9 -2
- package/core/server/data/schema/schema.js +3 -1
- package/core/server/lib/image/image-size.js +23 -5
- package/core/server/lib/lexical.js +36 -3
- package/core/server/models/member.js +3 -0
- package/core/server/models/mention.js +7 -1
- package/core/server/models/post.js +1 -1
- package/core/server/models/user.js +4 -1
- package/core/server/services/adapter-manager/index.js +7 -0
- package/core/server/services/email-analytics/events/StartEmailAnalyticsJobEvent.js +22 -0
- package/core/server/services/email-analytics/index.js +2 -22
- package/core/server/services/email-analytics/jobs/fetch-latest/index.js +6 -12
- package/core/server/services/email-analytics/wrapper.js +99 -0
- package/core/server/services/email-service/wrapper.js +3 -14
- package/core/server/services/mega/post-email-serializer.js +1 -1
- package/core/server/services/members/jobs/index.js +4 -2
- package/core/server/services/mentions/BookshelfMentionRepository.js +3 -2
- package/core/server/services/mentions/MentionController.js +78 -12
- package/core/server/services/mentions/service.js +43 -3
- package/core/server/services/milestone-emails/MilestoneQueries.js +58 -0
- package/core/server/services/milestone-emails/index.js +1 -0
- package/core/server/services/milestone-emails/service.js +58 -0
- package/core/server/services/tags-public/index.js +1 -0
- package/core/server/services/tags-public/service.js +31 -0
- package/core/server/web/api/endpoints/admin/routes.js +0 -1
- package/core/server/web/api/middleware/index.js +0 -1
- package/core/server/web/shared/middleware/api/spam-prevention.js +31 -1
- package/core/server/web/shared/middleware/brute.js +13 -0
- package/core/server/web/webmentions/routes.js +3 -0
- package/core/shared/config/defaults.json +17 -1
- package/core/shared/config/env/config.development.json +0 -3
- package/core/shared/config/env/config.testing-browser.json +6 -0
- package/core/shared/config/env/config.testing-mysql.json +7 -0
- package/core/shared/config/env/config.testing.json +6 -0
- package/core/shared/labs.js +3 -3
- package/package.json +121 -114
- package/yarn.lock +327 -242
- package/components/tryghost-adapter-manager-5.33.7.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.33.7.tgz +0 -0
- package/components/tryghost-audience-feedback-5.33.7.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.33.7.tgz +0 -0
- package/components/tryghost-constants-5.33.7.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.33.7.tgz +0 -0
- package/components/tryghost-data-generator-5.33.7.tgz +0 -0
- package/components/tryghost-domain-events-5.33.7.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.33.7.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.33.7.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.33.7.tgz +0 -0
- package/components/tryghost-email-content-generator-5.33.7.tgz +0 -0
- package/components/tryghost-email-events-5.33.7.tgz +0 -0
- package/components/tryghost-email-service-5.33.7.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.33.7.tgz +0 -0
- package/components/tryghost-extract-api-key-5.33.7.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.33.7.tgz +0 -0
- package/components/tryghost-i18n-5.33.7.tgz +0 -0
- package/components/tryghost-importer-revue-5.33.7.tgz +0 -0
- package/components/tryghost-link-redirects-5.33.7.tgz +0 -0
- package/components/tryghost-link-replacer-5.33.7.tgz +0 -0
- package/components/tryghost-magic-link-5.33.7.tgz +0 -0
- package/components/tryghost-mailgun-client-5.33.7.tgz +0 -0
- package/components/tryghost-member-attribution-5.33.7.tgz +0 -0
- package/components/tryghost-member-events-5.33.7.tgz +0 -0
- package/components/tryghost-members-api-5.33.7.tgz +0 -0
- package/components/tryghost-members-csv-5.33.7.tgz +0 -0
- package/components/tryghost-members-offers-5.33.7.tgz +0 -0
- package/components/tryghost-members-payments-5.33.7.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.33.7.tgz +0 -0
- package/components/tryghost-minifier-5.33.7.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.33.7.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.33.7.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.33.7.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.33.7.tgz +0 -0
- package/components/tryghost-mw-vhost-5.33.7.tgz +0 -0
- package/components/tryghost-oembed-service-5.33.7.tgz +0 -0
- package/components/tryghost-referrers-5.33.7.tgz +0 -0
- package/components/tryghost-session-service-5.33.7.tgz +0 -0
- package/components/tryghost-staff-service-5.33.7.tgz +0 -0
- package/components/tryghost-stats-service-5.33.7.tgz +0 -0
- package/components/tryghost-tiers-5.33.7.tgz +0 -0
- package/components/tryghost-update-check-service-5.33.7.tgz +0 -0
- package/components/tryghost-verification-trigger-5.33.7.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.33.7.tgz +0 -0
- package/components/tryghost-webmentions-5.33.7.tgz +0 -0
- package/core/server/services/email-analytics/jobs/fetch-latest/run.js +0 -57
- package/core/server/web/api/middleware/normalize-image.js +0 -42
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const {parentPort} = require('worker_threads');
|
|
2
|
+
const StartEmailAnalyticsJobEvent = require('../../events/StartEmailAnalyticsJobEvent');
|
|
2
3
|
|
|
3
4
|
// recurring job to fetch analytics since the most recently seen event timestamp
|
|
4
5
|
|
|
@@ -24,22 +25,15 @@ if (parentPort) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
(async () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
event: {
|
|
33
|
-
type: event.constructor.name,
|
|
34
|
-
data: event
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
28
|
+
// We send an evnet message, so that it is emitted on the main thread by the job manager
|
|
29
|
+
// This will start the email analytics job on the main thread (the wrapper service is listening for this event)
|
|
30
|
+
parentPort.postMessage({
|
|
31
|
+
event: {
|
|
32
|
+
type: StartEmailAnalyticsJobEvent.name
|
|
38
33
|
}
|
|
39
34
|
});
|
|
40
35
|
|
|
41
36
|
if (parentPort) {
|
|
42
|
-
parentPort.postMessage(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate - fetchStartDate}ms`);
|
|
43
37
|
parentPort.postMessage('done');
|
|
44
38
|
} else {
|
|
45
39
|
// give the logging pipes time finish writing before exit
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const debug = require('@tryghost/debug')('jobs:email-analytics:fetch-latest');
|
|
3
|
+
|
|
4
|
+
class EmailAnalyticsServiceWrapper {
|
|
5
|
+
init() {
|
|
6
|
+
if (this.service) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {EmailAnalyticsService} = require('@tryghost/email-analytics-service');
|
|
11
|
+
const {EmailEventStorage, EmailEventProcessor} = require('@tryghost/email-service');
|
|
12
|
+
const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun');
|
|
13
|
+
const {EmailRecipientFailure, EmailSpamComplaintEvent} = require('../../models');
|
|
14
|
+
const StartEmailAnalyticsJobEvent = require('./events/StartEmailAnalyticsJobEvent');
|
|
15
|
+
|
|
16
|
+
const domainEvents = require('@tryghost/domain-events');
|
|
17
|
+
const config = require('../../../shared/config');
|
|
18
|
+
const settings = require('../../../shared/settings-cache');
|
|
19
|
+
const db = require('../../data/db');
|
|
20
|
+
const queries = require('./lib/queries');
|
|
21
|
+
const membersService = require('../members');
|
|
22
|
+
const membersRepository = membersService.api.members;
|
|
23
|
+
|
|
24
|
+
this.eventStorage = new EmailEventStorage({
|
|
25
|
+
db,
|
|
26
|
+
membersRepository,
|
|
27
|
+
models: {
|
|
28
|
+
EmailRecipientFailure,
|
|
29
|
+
EmailSpamComplaintEvent
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Since this is running in a worker thread, we cant dispatch directly
|
|
34
|
+
// So we post the events as a message to the job manager
|
|
35
|
+
const eventProcessor = new EmailEventProcessor({
|
|
36
|
+
domainEvents,
|
|
37
|
+
db,
|
|
38
|
+
eventStorage: this.eventStorage
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.service = new EmailAnalyticsService({
|
|
42
|
+
config,
|
|
43
|
+
settings,
|
|
44
|
+
eventProcessor,
|
|
45
|
+
providers: [
|
|
46
|
+
new MailgunProvider({config, settings})
|
|
47
|
+
],
|
|
48
|
+
queries
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// We currently cannot trigger a non-offloaded job from the job manager
|
|
52
|
+
// So the email analytics jobs simply emits an event.
|
|
53
|
+
domainEvents.subscribe(StartEmailAnalyticsJobEvent, async () => {
|
|
54
|
+
await this.startFetch();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async fetchLatest() {
|
|
59
|
+
const fetchStartDate = new Date();
|
|
60
|
+
debug('Starting email analytics fetch of latest events');
|
|
61
|
+
const eventStats = await this.service.fetchLatest();
|
|
62
|
+
const fetchEndDate = new Date();
|
|
63
|
+
debug(`Finished fetching ${eventStats.totalEvents} analytics events in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
64
|
+
|
|
65
|
+
const aggregateStartDate = new Date();
|
|
66
|
+
debug(`Starting email analytics aggregation for ${eventStats.emailIds.length} emails`);
|
|
67
|
+
await this.service.aggregateStats(eventStats);
|
|
68
|
+
const aggregateEndDate = new Date();
|
|
69
|
+
debug(`Finished aggregating email analytics in ${aggregateEndDate.getTime() - aggregateStartDate.getTime()}ms`);
|
|
70
|
+
|
|
71
|
+
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
72
|
+
|
|
73
|
+
return eventStats;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async startFetch() {
|
|
77
|
+
if (this.fetching) {
|
|
78
|
+
logging.info('Email analytics fetch already running, skipping');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.fetching = true;
|
|
82
|
+
|
|
83
|
+
logging.info('Email analytics fetch started');
|
|
84
|
+
try {
|
|
85
|
+
const eventStats = await this.fetchLatest();
|
|
86
|
+
|
|
87
|
+
this.fetching = false;
|
|
88
|
+
return eventStats;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
logging.error(e, 'Error while fetching email analytics');
|
|
91
|
+
|
|
92
|
+
// Log again only the error, otherwise we lose the stack trace
|
|
93
|
+
logging.error(e);
|
|
94
|
+
}
|
|
95
|
+
this.fetching = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = EmailAnalyticsServiceWrapper;
|
|
@@ -13,8 +13,8 @@ class EmailServiceWrapper {
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const {EmailService, EmailController, EmailRenderer, SendingService, BatchSendingService, EmailSegmenter,
|
|
17
|
-
const {Post, Newsletter, Email, EmailBatch, EmailRecipient, Member
|
|
16
|
+
const {EmailService, EmailController, EmailRenderer, SendingService, BatchSendingService, EmailSegmenter, MailgunEmailProvider} = require('@tryghost/email-service');
|
|
17
|
+
const {Post, Newsletter, Email, EmailBatch, EmailRecipient, Member} = require('../../models');
|
|
18
18
|
const MailgunClient = require('@tryghost/mailgun-client');
|
|
19
19
|
const configService = require('../../../shared/config');
|
|
20
20
|
const settingsCache = require('../../../shared/settings-cache');
|
|
@@ -25,7 +25,6 @@ class EmailServiceWrapper {
|
|
|
25
25
|
const sentry = require('../../../shared/sentry');
|
|
26
26
|
const membersRepository = membersService.api.members;
|
|
27
27
|
const limitService = require('../limits');
|
|
28
|
-
const domainEvents = require('@tryghost/domain-events');
|
|
29
28
|
|
|
30
29
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
31
30
|
const lexicalLib = require('../../lib/lexical');
|
|
@@ -59,7 +58,7 @@ class EmailServiceWrapper {
|
|
|
59
58
|
settingsHelpers,
|
|
60
59
|
renderers: {
|
|
61
60
|
mobiledoc: mobiledocLib.mobiledocHtmlRenderer,
|
|
62
|
-
lexical: lexicalLib
|
|
61
|
+
lexical: lexicalLib
|
|
63
62
|
},
|
|
64
63
|
imageSize,
|
|
65
64
|
urlUtils,
|
|
@@ -116,16 +115,6 @@ class EmailServiceWrapper {
|
|
|
116
115
|
Email
|
|
117
116
|
}
|
|
118
117
|
});
|
|
119
|
-
|
|
120
|
-
this.eventStorage = new EmailEventStorage({
|
|
121
|
-
db,
|
|
122
|
-
membersRepository,
|
|
123
|
-
models: {
|
|
124
|
-
EmailRecipientFailure,
|
|
125
|
-
EmailSpamComplaintEvent
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
this.eventStorage.listen(domainEvents);
|
|
129
118
|
}
|
|
130
119
|
}
|
|
131
120
|
|
|
@@ -14,10 +14,12 @@ module.exports = {
|
|
|
14
14
|
) {
|
|
15
15
|
// use a random seconds value to avoid spikes to external APIs on the minute
|
|
16
16
|
const s = Math.floor(Math.random() * 60); // 0-59
|
|
17
|
+
const m = Math.floor(Math.random() * 60); // 0-59
|
|
18
|
+
const h = Math.floor(Math.random() * 6); // 0-5
|
|
17
19
|
|
|
18
|
-
// Run everyday at
|
|
20
|
+
// Run everyday at {0-5}:XX:XX AM to clean all expired complimentary subscriptions
|
|
19
21
|
jobsService.addJob({
|
|
20
|
-
at: `${s}
|
|
22
|
+
at: `${s} ${m} ${h} * * *`,
|
|
21
23
|
job: path.resolve(__dirname, 'clean-expired-comped.js'),
|
|
22
24
|
name: 'clean-expired-comped'
|
|
23
25
|
});
|
|
@@ -54,7 +54,7 @@ module.exports = class BookshelfMentionRepository {
|
|
|
54
54
|
sourceAuthor: model.get('source_author'),
|
|
55
55
|
sourceExcerpt: model.get('source_excerpt'),
|
|
56
56
|
sourceFavicon: model.get('source_favicon'),
|
|
57
|
-
|
|
57
|
+
sourceFeaturedImage: model.get('source_featured_image')
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -106,7 +106,8 @@ module.exports = class BookshelfMentionRepository {
|
|
|
106
106
|
target: mention.target.href,
|
|
107
107
|
resource_id: mention.resourceId?.toHexString(),
|
|
108
108
|
resource_type: mention.resourceId ? 'post' : null,
|
|
109
|
-
payload: mention.payload ? JSON.stringify(mention.payload) : null
|
|
109
|
+
payload: mention.payload ? JSON.stringify(mention.payload) : null,
|
|
110
|
+
deleted: Mention.isDeleted(mention)
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
const existing = await this.#MentionModel.findOne({id: data.id}, {require: false});
|
|
@@ -10,17 +10,53 @@ const logging = require('@tryghost/logging');
|
|
|
10
10
|
* @typedef {import('@tryghost/webmentions/lib/MentionsAPI').Page} Page<Model>
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} MentionResource
|
|
15
|
+
* @prop {ObjectID} id
|
|
16
|
+
* @prop {string} type
|
|
17
|
+
* @prop {string} name
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Mention} MentionDTO
|
|
22
|
+
* @prop {Resource} resource
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} IJobService
|
|
27
|
+
* @prop {(name: string, fn: Function) => void} addJob
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} IMentionResourceService
|
|
32
|
+
* @prop {(id: ObjectID) => Promise<MentionResource>} getByID
|
|
33
|
+
*/
|
|
34
|
+
|
|
13
35
|
module.exports = class MentionController {
|
|
14
36
|
/** @type {import('@tryghost/webmentions/lib/MentionsAPI')} */
|
|
15
37
|
#api;
|
|
16
38
|
|
|
39
|
+
/** @type {IJobService} */
|
|
40
|
+
#jobService;
|
|
41
|
+
|
|
42
|
+
/** @type {IMentionResourceService} */
|
|
43
|
+
#mentionResourceService;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {object} deps
|
|
47
|
+
* @param {import('@tryghost/webmentions/lib/MentionsAPI')} deps.api
|
|
48
|
+
* @param {IJobService} deps.jobService
|
|
49
|
+
* @param {IMentionResourceService} deps.mentionResourceService
|
|
50
|
+
*/
|
|
17
51
|
async init(deps) {
|
|
18
52
|
this.#api = deps.api;
|
|
53
|
+
this.#jobService = deps.jobService;
|
|
54
|
+
this.#mentionResourceService = deps.mentionResourceService;
|
|
19
55
|
}
|
|
20
56
|
|
|
21
57
|
/**
|
|
22
58
|
* @param {import('@tryghost/api-framework').Frame} frame
|
|
23
|
-
* @returns {Promise<Page<
|
|
59
|
+
* @returns {Promise<Page<MentionDTO>>}
|
|
24
60
|
*/
|
|
25
61
|
async browse(frame) {
|
|
26
62
|
let limit;
|
|
@@ -37,13 +73,41 @@ module.exports = class MentionController {
|
|
|
37
73
|
page = 1;
|
|
38
74
|
}
|
|
39
75
|
|
|
40
|
-
|
|
76
|
+
let order;
|
|
77
|
+
if (frame.options.order && frame.options.order === 'created_at desc') {
|
|
78
|
+
order = 'created_at desc';
|
|
79
|
+
} else {
|
|
80
|
+
order = 'created_at asc';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const mentions = await this.#api.listMentions({
|
|
41
84
|
filter: frame.options.filter,
|
|
85
|
+
order,
|
|
42
86
|
limit,
|
|
43
87
|
page
|
|
44
88
|
});
|
|
45
89
|
|
|
46
|
-
|
|
90
|
+
const resources = await Promise.all(mentions.data.map((mention) => {
|
|
91
|
+
return this.#mentionResourceService.getByID(mention.resourceId);
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
/** @type {Page<MentionDTO>} */
|
|
95
|
+
const result = {
|
|
96
|
+
data: mentions.data.map((mention, index) => {
|
|
97
|
+
const mentionDTO = {
|
|
98
|
+
...mention.toJSON(),
|
|
99
|
+
resource: resources[index],
|
|
100
|
+
toJSON() {
|
|
101
|
+
return mentionDTO;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
delete mentionDTO.resourceId;
|
|
105
|
+
return mentionDTO;
|
|
106
|
+
}),
|
|
107
|
+
meta: mentions.meta
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return result;
|
|
47
111
|
}
|
|
48
112
|
|
|
49
113
|
/**
|
|
@@ -52,15 +116,17 @@ module.exports = class MentionController {
|
|
|
52
116
|
*/
|
|
53
117
|
async receive(frame) {
|
|
54
118
|
logging.info('[Webmention] ' + JSON.stringify(frame.data));
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
119
|
+
this.#jobService.addJob('processWebmention', async () => {
|
|
120
|
+
const {source, target, ...payload} = frame.data;
|
|
121
|
+
try {
|
|
122
|
+
await this.#api.processWebmention({
|
|
123
|
+
source: new URL(source),
|
|
124
|
+
target: new URL(target),
|
|
125
|
+
payload
|
|
126
|
+
});
|
|
127
|
+
} catch (err) {
|
|
128
|
+
logging.error(err);
|
|
129
|
+
}
|
|
64
130
|
});
|
|
65
131
|
}
|
|
66
132
|
};
|
|
@@ -13,9 +13,10 @@ const events = require('../../lib/common/events');
|
|
|
13
13
|
const externalRequest = require('../../../server/lib/request-external.js');
|
|
14
14
|
const urlUtils = require('../../../shared/url-utils');
|
|
15
15
|
const outputSerializerUrlUtil = require('../../../server/api/endpoints/utils/serializers/output/utils/url');
|
|
16
|
-
const labs = require('../../../shared/labs');
|
|
17
16
|
const urlService = require('../url');
|
|
17
|
+
const settingsCache = require('../../../shared/settings-cache');
|
|
18
18
|
const DomainEvents = require('@tryghost/domain-events');
|
|
19
|
+
const jobsService = require('../jobs');
|
|
19
20
|
|
|
20
21
|
function getPostUrl(post) {
|
|
21
22
|
const jsonModel = {};
|
|
@@ -50,14 +51,53 @@ module.exports = {
|
|
|
50
51
|
routingService
|
|
51
52
|
});
|
|
52
53
|
|
|
53
|
-
this.controller.init({
|
|
54
|
+
this.controller.init({
|
|
55
|
+
api,
|
|
56
|
+
jobService: {
|
|
57
|
+
async addJob(name, fn) {
|
|
58
|
+
jobsService.addJob({
|
|
59
|
+
name,
|
|
60
|
+
job: fn,
|
|
61
|
+
offloaded: false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
mentionResourceService: {
|
|
66
|
+
async getByID(id) {
|
|
67
|
+
if (!id) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const post = await models.Post.findOne({id: id.toHexString()});
|
|
72
|
+
|
|
73
|
+
if (!post) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
id: id,
|
|
79
|
+
name: post.get('title'),
|
|
80
|
+
type: 'post'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
54
85
|
|
|
55
86
|
const sendingService = new MentionSendingService({
|
|
56
87
|
discoveryService,
|
|
57
88
|
externalRequest,
|
|
58
89
|
getSiteUrl: () => urlUtils.urlFor('home', true),
|
|
59
90
|
getPostUrl: post => getPostUrl(post),
|
|
60
|
-
isEnabled: () =>
|
|
91
|
+
isEnabled: () => !settingsCache.get('is_private'),
|
|
92
|
+
jobService: {
|
|
93
|
+
async addJob(name, fn) {
|
|
94
|
+
jobsService.addJob({
|
|
95
|
+
name,
|
|
96
|
+
job: fn,
|
|
97
|
+
offloaded: false
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
61
101
|
});
|
|
62
102
|
sendingService.listen(events);
|
|
63
103
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const MIN_DAYS_SINCE_IMPORTED = 7;
|
|
2
|
+
|
|
3
|
+
module.exports = class MilestoneQueries {
|
|
4
|
+
#db;
|
|
5
|
+
|
|
6
|
+
constructor(deps) {
|
|
7
|
+
this.#db = deps.db;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @returns {Promise<number>}
|
|
12
|
+
*/
|
|
13
|
+
async getMembersCount() {
|
|
14
|
+
const [membersCount] = await this.#db.knex('members').count('id as count');
|
|
15
|
+
|
|
16
|
+
return membersCount?.count || 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {Promise<Array>}
|
|
21
|
+
*/
|
|
22
|
+
async getARR() {
|
|
23
|
+
const currentARR = await this.#db.knex('members_paid_subscription_events as stripe')
|
|
24
|
+
.select(this.#db.knex.raw('ROUND(SUM(stripe.mrr_delta) * 12) / 100 AS arr, stripe.currency as currency'))
|
|
25
|
+
.groupBy('stripe.currency');
|
|
26
|
+
|
|
27
|
+
return currentARR;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @returns {Promise<boolean>}
|
|
32
|
+
*/
|
|
33
|
+
async hasImportedMembersInPeriod() {
|
|
34
|
+
const [hasImportedMembers] = await this.#db.knex('members_subscribe_events')
|
|
35
|
+
.count('id as count')
|
|
36
|
+
.where('source', '=', 'import')
|
|
37
|
+
.where('created_at', '>=', MIN_DAYS_SINCE_IMPORTED);
|
|
38
|
+
|
|
39
|
+
return hasImportedMembers?.count > 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @returns {Promise<string>}
|
|
44
|
+
*/
|
|
45
|
+
async getDefaultCurrency() {
|
|
46
|
+
const currentARR = await this.getARR();
|
|
47
|
+
|
|
48
|
+
// Set the default currency as the one with the highest value
|
|
49
|
+
if (currentARR.length > 1) {
|
|
50
|
+
const highestValues = currentARR.sort((a, b) => b.arr - a.arr);
|
|
51
|
+
return highestValues?.[0]?.currency;
|
|
52
|
+
} else if (currentARR?.[0]?.currency) {
|
|
53
|
+
return currentARR[0].currency;
|
|
54
|
+
} else {
|
|
55
|
+
return 'usd';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./service');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Stubbing stripe in test was causing issues. Moved it
|
|
2
|
+
// into this function to be able to rewire and stub the
|
|
3
|
+
// expected return value.
|
|
4
|
+
const getStripeLiveEnabled = () => {
|
|
5
|
+
const stripeService = require('../stripe');
|
|
6
|
+
// This seems to be the only true way to check if Stripe is configured in live mode
|
|
7
|
+
// settingsCache only cares if Stripe is enabled
|
|
8
|
+
return stripeService.api.configured && stripeService.api.mode === 'live';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @returns {Promise<any>}
|
|
14
|
+
*/
|
|
15
|
+
module.exports = {
|
|
16
|
+
async initAndRun() {
|
|
17
|
+
const labs = require('../../../shared/labs');
|
|
18
|
+
|
|
19
|
+
if (labs.isSet('milestoneEmails')) {
|
|
20
|
+
const db = require('../../data/db');
|
|
21
|
+
const MilestoneQueries = require('./MilestoneQueries');
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
MilestonesEmailService,
|
|
25
|
+
InMemoryMilestoneRepository
|
|
26
|
+
} = require('@tryghost/milestone-emails');
|
|
27
|
+
const config = require('../../../shared/config');
|
|
28
|
+
const milestonesConfig = config.get('milestones');
|
|
29
|
+
const {GhostMailer} = require('../mail');
|
|
30
|
+
|
|
31
|
+
const mailer = new GhostMailer();
|
|
32
|
+
const repository = new InMemoryMilestoneRepository();
|
|
33
|
+
const queries = new MilestoneQueries({db});
|
|
34
|
+
|
|
35
|
+
const milestonesEmailService = new MilestonesEmailService({
|
|
36
|
+
mailer,
|
|
37
|
+
repository,
|
|
38
|
+
milestonesConfig, // avoid using getters and pass as JSON
|
|
39
|
+
queries
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let arrResult;
|
|
43
|
+
|
|
44
|
+
// @TODO: schedule recurring jobs instead
|
|
45
|
+
const membersResult = await milestonesEmailService.checkMilestones('members');
|
|
46
|
+
const stripeLiveEnabled = getStripeLiveEnabled();
|
|
47
|
+
|
|
48
|
+
if (stripeLiveEnabled) {
|
|
49
|
+
arrResult = await milestonesEmailService.checkMilestones('arr');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
members: membersResult,
|
|
54
|
+
arr: arrResult
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./service');
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class TagsPublicServiceWrapper {
|
|
2
|
+
async init() {
|
|
3
|
+
if (this.api) {
|
|
4
|
+
// Already done
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Wire up all the dependencies
|
|
9
|
+
const models = require('../../models');
|
|
10
|
+
const adapterManager = require('../adapter-manager');
|
|
11
|
+
const config = require('../../../shared/config');
|
|
12
|
+
|
|
13
|
+
let tagsCache;
|
|
14
|
+
if (config.get('hostSettings:tagsPublicCache:enabled')) {
|
|
15
|
+
tagsCache = adapterManager.getAdapter('cache:tagsPublic');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const {TagsPublicRepository} = require('@tryghost/tags-public');
|
|
19
|
+
|
|
20
|
+
this.linkRedirectRepository = new TagsPublicRepository({
|
|
21
|
+
Tag: models.TagPublic,
|
|
22
|
+
cache: tagsCache
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.api = {
|
|
26
|
+
browse: this.linkRedirectRepository.getAll.bind(this.linkRedirectRepository)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = new TagsPublicServiceWrapper();
|
|
@@ -20,7 +20,8 @@ const messages = {
|
|
|
20
20
|
error: 'Only {rateSigninAttempts} tries per IP address every {rateSigninPeriod} seconds.',
|
|
21
21
|
context: 'Too many login attempts.'
|
|
22
22
|
},
|
|
23
|
-
tooManyAttempts: 'Too many attempts.'
|
|
23
|
+
tooManyAttempts: 'Too many attempts.',
|
|
24
|
+
webmentionsBlock: 'Too many mention attempts'
|
|
24
25
|
};
|
|
25
26
|
let spamPrivateBlock = spam.private_block || {};
|
|
26
27
|
let spamGlobalBlock = spam.global_block || {};
|
|
@@ -29,12 +30,14 @@ let spamUserReset = spam.user_reset || {};
|
|
|
29
30
|
let spamUserLogin = spam.user_login || {};
|
|
30
31
|
let spamMemberLogin = spam.member_login || {};
|
|
31
32
|
let spamContentApiKey = spam.content_api_key || {};
|
|
33
|
+
let spamWebmentionsBlock = spam.webmentions_block || {};
|
|
32
34
|
|
|
33
35
|
let store;
|
|
34
36
|
let memoryStore;
|
|
35
37
|
let privateBlogInstance;
|
|
36
38
|
let globalResetInstance;
|
|
37
39
|
let globalBlockInstance;
|
|
40
|
+
let webmentionsBlockInstance;
|
|
38
41
|
let userLoginInstance;
|
|
39
42
|
let membersAuthInstance;
|
|
40
43
|
let membersAuthEnumerationInstance;
|
|
@@ -123,6 +126,32 @@ const globalReset = () => {
|
|
|
123
126
|
return globalResetInstance;
|
|
124
127
|
};
|
|
125
128
|
|
|
129
|
+
const webmentionsBlock = () => {
|
|
130
|
+
const ExpressBrute = require('express-brute');
|
|
131
|
+
const BruteKnex = require('brute-knex');
|
|
132
|
+
const db = require('../../../../data/db');
|
|
133
|
+
|
|
134
|
+
store = store || new BruteKnex({
|
|
135
|
+
tablename: 'brute',
|
|
136
|
+
createTable: false,
|
|
137
|
+
knex: db.knex
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
webmentionsBlockInstance = webmentionsBlockInstance || new ExpressBrute(store,
|
|
141
|
+
extend({
|
|
142
|
+
attachResetToRequest: false,
|
|
143
|
+
failCallback(req, res, next) {
|
|
144
|
+
return next(new errors.TooManyRequestsError({
|
|
145
|
+
message: messages.webmentionsBlock
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
148
|
+
handleStoreError: handleStoreError
|
|
149
|
+
}, pick(spamWebmentionsBlock, spamConfigKeys))
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return webmentionsBlockInstance;
|
|
153
|
+
};
|
|
154
|
+
|
|
126
155
|
const membersAuth = () => {
|
|
127
156
|
const ExpressBrute = require('express-brute');
|
|
128
157
|
const BruteKnex = require('brute-knex');
|
|
@@ -319,6 +348,7 @@ module.exports = {
|
|
|
319
348
|
userReset: userReset,
|
|
320
349
|
privateBlog: privateBlog,
|
|
321
350
|
contentApiKey: contentApiKey,
|
|
351
|
+
webmentionsBlock: webmentionsBlock,
|
|
322
352
|
reset: () => {
|
|
323
353
|
store = undefined;
|
|
324
354
|
memoryStore = undefined;
|
|
@@ -104,5 +104,18 @@ module.exports = {
|
|
|
104
104
|
*/
|
|
105
105
|
membersAuthEnumeration(req, res, next) {
|
|
106
106
|
return spamPrevention.membersAuthEnumeration().prevent(req, res, next);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Blocks webmention spam
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
webmentionsLimiter(req, res, next) {
|
|
114
|
+
return spamPrevention.webmentionsBlock().getMiddleware({
|
|
115
|
+
ignoreIP: false,
|
|
116
|
+
key(_req, _res, _next) {
|
|
117
|
+
return _next('webmention_blocked');
|
|
118
|
+
}
|
|
119
|
+
})(req, res, next);
|
|
107
120
|
}
|
|
108
121
|
};
|