ghost 5.33.8 → 5.34.1
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.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.34.1.tgz +0 -0
- package/components/{tryghost-api-framework-5.33.8.tgz → tryghost-api-framework-5.34.1.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.34.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.34.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.34.1.tgz +0 -0
- package/components/tryghost-constants-5.34.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.33.8.tgz → tryghost-custom-theme-settings-service-5.34.1.tgz} +0 -0
- package/components/tryghost-data-generator-5.34.1.tgz +0 -0
- package/components/tryghost-domain-events-5.34.1.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.34.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.34.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.34.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.34.1.tgz +0 -0
- package/components/tryghost-email-events-5.34.1.tgz +0 -0
- package/components/tryghost-email-service-5.34.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.34.1.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.33.8.tgz → tryghost-express-dynamic-redirects-5.34.1.tgz} +0 -0
- package/components/tryghost-extract-api-key-5.34.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.34.1.tgz +0 -0
- package/components/tryghost-i18n-5.34.1.tgz +0 -0
- package/components/{tryghost-importer-revue-5.33.8.tgz → tryghost-importer-revue-5.34.1.tgz} +0 -0
- package/components/{tryghost-job-manager-5.33.8.tgz → tryghost-job-manager-5.34.1.tgz} +0 -0
- package/components/tryghost-link-redirects-5.34.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.34.1.tgz +0 -0
- package/components/tryghost-link-tracking-5.34.1.tgz +0 -0
- package/components/{tryghost-magic-link-5.33.8.tgz → tryghost-magic-link-5.34.1.tgz} +0 -0
- package/components/tryghost-mailgun-client-5.34.1.tgz +0 -0
- package/components/{tryghost-member-attribution-5.33.8.tgz → tryghost-member-attribution-5.34.1.tgz} +0 -0
- package/components/tryghost-member-events-5.34.1.tgz +0 -0
- package/components/tryghost-members-api-5.34.1.tgz +0 -0
- package/components/tryghost-members-csv-5.34.1.tgz +0 -0
- package/components/{tryghost-members-events-service-5.33.8.tgz → tryghost-members-events-service-5.34.1.tgz} +0 -0
- package/components/{tryghost-members-importer-5.33.8.tgz → tryghost-members-importer-5.34.1.tgz} +0 -0
- package/components/tryghost-members-offers-5.34.1.tgz +0 -0
- package/components/tryghost-members-payments-5.34.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.34.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.34.1.tgz +0 -0
- package/components/tryghost-milestone-emails-5.34.1.tgz +0 -0
- package/components/tryghost-minifier-5.34.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.34.1.tgz +0 -0
- package/components/{tryghost-mw-cache-control-5.33.8.tgz → tryghost-mw-cache-control-5.34.1.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.33.8.tgz → tryghost-mw-error-handler-5.34.1.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.34.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.34.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.34.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.34.1.tgz +0 -0
- package/components/{tryghost-package-json-5.33.8.tgz → tryghost-package-json-5.34.1.tgz} +0 -0
- package/components/tryghost-referrers-5.34.1.tgz +0 -0
- package/components/tryghost-security-5.34.1.tgz +0 -0
- package/components/tryghost-session-service-5.34.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.34.1.tgz +0 -0
- package/components/tryghost-staff-service-5.34.1.tgz +0 -0
- package/components/tryghost-stats-service-5.34.1.tgz +0 -0
- package/components/tryghost-tags-public-5.34.1.tgz +0 -0
- package/components/tryghost-tiers-5.34.1.tgz +0 -0
- package/components/{tryghost-update-check-service-5.33.8.tgz → tryghost-update-check-service-5.34.1.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.34.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.34.1.tgz +0 -0
- package/components/tryghost-webmentions-5.34.1.tgz +0 -0
- package/core/boot.js +6 -1
- package/core/built/admin/assets/{chunk.143.caa7dbd727b76021c31b.js → chunk.143.07f5af56ff872bb0e9e4.js} +14 -14
- package/core/built/admin/assets/{chunk.178.9803e6194593a0ed6595.js → chunk.178.2e831ef9072743e38dd1.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-872d240189f9b5ff85f4f7cb2ac3ab4c.js → ghost-ad40d109653288e74a7cd922341fb33d.js} +114 -89
- 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/mappers/mentions.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/wrapper.js +22 -13
- package/core/server/services/email-service/wrapper.js +1 -1
- 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.8.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.33.8.tgz +0 -0
- package/components/tryghost-audience-feedback-5.33.8.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.33.8.tgz +0 -0
- package/components/tryghost-constants-5.33.8.tgz +0 -0
- package/components/tryghost-data-generator-5.33.8.tgz +0 -0
- package/components/tryghost-domain-events-5.33.8.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.33.8.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.33.8.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.33.8.tgz +0 -0
- package/components/tryghost-email-content-generator-5.33.8.tgz +0 -0
- package/components/tryghost-email-events-5.33.8.tgz +0 -0
- package/components/tryghost-email-service-5.33.8.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.33.8.tgz +0 -0
- package/components/tryghost-extract-api-key-5.33.8.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.33.8.tgz +0 -0
- package/components/tryghost-i18n-5.33.8.tgz +0 -0
- package/components/tryghost-link-redirects-5.33.8.tgz +0 -0
- package/components/tryghost-link-replacer-5.33.8.tgz +0 -0
- package/components/tryghost-link-tracking-5.33.8.tgz +0 -0
- package/components/tryghost-mailgun-client-5.33.8.tgz +0 -0
- package/components/tryghost-member-events-5.33.8.tgz +0 -0
- package/components/tryghost-members-api-5.33.8.tgz +0 -0
- package/components/tryghost-members-csv-5.33.8.tgz +0 -0
- package/components/tryghost-members-offers-5.33.8.tgz +0 -0
- package/components/tryghost-members-payments-5.33.8.tgz +0 -0
- package/components/tryghost-members-ssr-5.33.8.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.33.8.tgz +0 -0
- package/components/tryghost-minifier-5.33.8.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.33.8.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.33.8.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.33.8.tgz +0 -0
- package/components/tryghost-mw-vhost-5.33.8.tgz +0 -0
- package/components/tryghost-oembed-service-5.33.8.tgz +0 -0
- package/components/tryghost-referrers-5.33.8.tgz +0 -0
- package/components/tryghost-security-5.33.8.tgz +0 -0
- package/components/tryghost-session-service-5.33.8.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.33.8.tgz +0 -0
- package/components/tryghost-staff-service-5.33.8.tgz +0 -0
- package/components/tryghost-stats-service-5.33.8.tgz +0 -0
- package/components/tryghost-tiers-5.33.8.tgz +0 -0
- package/components/tryghost-verification-trigger-5.33.8.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.33.8.tgz +0 -0
- package/components/tryghost-webmentions-5.33.8.tgz +0 -0
- package/core/server/web/api/middleware/normalize-image.js +0 -42
|
@@ -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
|
};
|
|
@@ -11,6 +11,9 @@ module.exports = function apiRoutes() {
|
|
|
11
11
|
// shouldn't be cached
|
|
12
12
|
router.use(shared.middleware.cacheControl('private'));
|
|
13
13
|
|
|
14
|
+
// rate limiter
|
|
15
|
+
router.use(shared.middleware.brute.webmentionsLimiter);
|
|
16
|
+
|
|
14
17
|
// Webmentions
|
|
15
18
|
router.post('/receive', bodyParser.urlencoded({extended: true, limit: '5mb'}), http(api.mentions.receive));
|
|
16
19
|
|
|
@@ -102,6 +102,12 @@
|
|
|
102
102
|
"maxWait": 43200000,
|
|
103
103
|
"lifetime": 43200,
|
|
104
104
|
"freeRetries": 8
|
|
105
|
+
},
|
|
106
|
+
"webmentions_block": {
|
|
107
|
+
"minWait": 10,
|
|
108
|
+
"maxWait": 100,
|
|
109
|
+
"lifetime": 1000,
|
|
110
|
+
"freeRetries": 100
|
|
105
111
|
}
|
|
106
112
|
},
|
|
107
113
|
"caching": {
|
|
@@ -198,5 +204,15 @@
|
|
|
198
204
|
},
|
|
199
205
|
"gravatar": {
|
|
200
206
|
"url": "https://www.gravatar.com/avatar/{hash}?s={size}&r={rating}&d={_default}"
|
|
201
|
-
}
|
|
207
|
+
},
|
|
208
|
+
"milestones":
|
|
209
|
+
{
|
|
210
|
+
"arr": [
|
|
211
|
+
{
|
|
212
|
+
"currency": "usd",
|
|
213
|
+
"values": [1000, 10000, 50000, 100000, 250000, 500000, 1000000]
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"members": [100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
|
|
217
|
+
}
|
|
202
218
|
}
|
package/core/shared/labs.js
CHANGED
|
@@ -27,6 +27,7 @@ const GA_FEATURES = [
|
|
|
27
27
|
// input for the "labs" setting value
|
|
28
28
|
const BETA_FEATURES = [
|
|
29
29
|
'activitypub',
|
|
30
|
+
'webmentions',
|
|
30
31
|
'emailErrors'
|
|
31
32
|
];
|
|
32
33
|
|
|
@@ -34,9 +35,8 @@ const ALPHA_FEATURES = [
|
|
|
34
35
|
'urlCache',
|
|
35
36
|
'beforeAfterCard',
|
|
36
37
|
'lexicalEditor',
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'outboundLinkTagging'
|
|
38
|
+
'outboundLinkTagging',
|
|
39
|
+
'milestoneEmails'
|
|
40
40
|
];
|
|
41
41
|
|
|
42
42
|
module.exports.GA_KEYS = [...GA_FEATURES];
|