ghost 5.10.0 → 5.12.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-manager-5.12.0.tgz +0 -0
- package/components/tryghost-api-framework-5.12.0.tgz +0 -0
- package/components/{tryghost-api-version-compatibility-service-0.0.0.tgz → tryghost-api-version-compatibility-service-5.12.0.tgz} +0 -0
- package/components/tryghost-bootstrap-socket-5.12.0.tgz +0 -0
- package/components/tryghost-constants-5.12.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.12.0.tgz +0 -0
- package/components/tryghost-domain-events-5.12.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.12.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.12.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.12.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.12.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.12.0.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-0.0.0.tgz → tryghost-html-to-plaintext-5.12.0.tgz} +0 -0
- package/components/tryghost-job-manager-5.12.0.tgz +0 -0
- package/components/tryghost-magic-link-5.12.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.12.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-5.12.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.12.0.tgz +0 -0
- package/components/tryghost-member-events-5.12.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.12.0.tgz +0 -0
- package/components/tryghost-members-api-5.12.0.tgz +0 -0
- package/components/{tryghost-members-csv-0.0.0.tgz → tryghost-members-csv-5.12.0.tgz} +0 -0
- package/components/tryghost-members-events-service-5.12.0.tgz +0 -0
- package/components/tryghost-members-importer-5.12.0.tgz +0 -0
- package/components/tryghost-members-offers-5.12.0.tgz +0 -0
- package/components/tryghost-members-payments-5.12.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.12.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.12.0.tgz +0 -0
- package/components/tryghost-minifier-5.12.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.12.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.12.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.12.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.12.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.12.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.12.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.12.0.tgz +0 -0
- package/components/tryghost-package-json-5.12.0.tgz +0 -0
- package/components/tryghost-security-5.12.0.tgz +0 -0
- package/components/tryghost-session-service-5.12.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.12.0.tgz +0 -0
- package/components/tryghost-staff-service-5.12.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.12.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.12.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.12.0.tgz +0 -0
- package/core/boot.js +2 -0
- package/core/built/admin/assets/chunk.143.da85cf08f47cfe520bf6.js +49 -0
- package/core/built/admin/assets/{chunk.174.0364e8abdae8210d8e6d.js → chunk.174.ae492405065373dbe102.js} +1 -1
- package/core/built/admin/assets/{chunk.178.8a19c35ce1a7cf4249ce.js → chunk.178.29d3fb3dc6811b673a00.js} +4 -4
- package/core/built/admin/assets/{chunk.351.ea4a4ff4b40d5f2ad141.js → chunk.579.65e09dd89eec70d059a0.js} +3 -11
- package/core/built/admin/assets/{chunk.351.ea4a4ff4b40d5f2ad141.js.LICENSE.txt → chunk.579.65e09dd89eec70d059a0.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/{ghost-ced03a7ac75c3148e0ea7d1bf51e39fc.js → ghost-0526c96b20843697927c1d06a9010197.js} +779 -610
- package/core/built/admin/assets/ghost-dark-e4d6987343ee26af534a06c1b9639d97.css +1 -0
- package/core/built/admin/assets/ghost-f62b0e78ddcd947273873bdeba4abc3c.css +1 -0
- package/core/built/admin/assets/icons/calendar-stroke.svg +1 -0
- package/core/built/admin/assets/icons/event-canceled-subscription--feature-attribution.svg +6 -0
- package/core/built/admin/assets/icons/event-comment--feature-attribution.svg +3 -0
- package/core/built/admin/assets/icons/event-email-delivery-failed--feature-attribution.svg +6 -0
- package/core/built/admin/assets/icons/event-logged-in--feature-attribution.svg +5 -0
- package/core/built/admin/assets/icons/event-made-a-payment--feature-attribution.svg +7 -0
- package/core/built/admin/assets/icons/event-opened-email--feature-attribution.svg +6 -0
- package/core/built/admin/assets/icons/event-received-email--feature-attribution.svg +5 -0
- package/core/built/admin/assets/icons/event-signed-up--feature-attribution.svg +6 -0
- package/core/built/admin/assets/icons/event-started-subscription--feature-attribution.svg +6 -0
- package/core/built/admin/assets/icons/event-subscribed-to-email--feature-attribution.svg +8 -0
- package/core/built/admin/assets/icons/event-subscriptions--feature-attribution.svg +5 -0
- package/core/built/admin/assets/icons/event-unsubscribed-from-email--feature-attribution.svg +5 -0
- package/core/built/admin/assets/icons/pen-stroke.svg +1 -0
- package/core/built/admin/assets/{vendor-a1ae7a38d5c38fcba5609eed4e37f02a.js → vendor-52613f40d62355e9ac64cbfa211169bb.js} +88 -60
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/search.js +5 -20
- package/core/frontend/meta/get-meta.js +1 -2
- package/core/frontend/meta/image-dimensions.js +47 -39
- package/core/server/api/endpoints/comments-members.js +10 -7
- package/core/server/api/endpoints/invites.js +1 -9
- package/core/server/api/endpoints/labels.js +1 -7
- package/core/server/api/endpoints/members.js +3 -13
- package/core/server/api/endpoints/offers.js +2 -2
- package/core/server/api/endpoints/pages.js +2 -10
- package/core/server/api/endpoints/posts.js +1 -9
- package/core/server/api/endpoints/snippets.js +1 -9
- package/core/server/api/endpoints/tags.js +1 -7
- package/core/server/api/endpoints/utils/serializers/input/pages.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/site.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +6 -7
- package/core/server/api/endpoints/webhooks.js +2 -19
- package/core/server/data/migrations/versions/5.11/2022-08-22-11-03-add-member-alert-settings-columns-to-users.js +21 -0
- package/core/server/data/migrations/versions/5.11/2022-08-23-13-41-backfill-members-created-events.js +32 -0
- package/core/server/data/migrations/versions/5.11/2022-08-23-13-59-fix-page-resource-type.js +22 -0
- package/core/server/data/schema/fixtures/fixtures.json +3 -0
- package/core/server/data/schema/schema.js +23 -4
- package/core/server/lib/image/gravatar.js +8 -7
- package/core/server/lib/image/image-size.js +60 -56
- package/core/server/models/action.js +6 -19
- package/core/server/models/base/plugins/actions.js +26 -3
- package/core/server/models/member-created-event.js +10 -2
- package/core/server/models/member-paid-subscription-event.js +4 -0
- package/core/server/models/member.js +18 -0
- package/core/server/models/offer.js +3 -0
- package/core/server/models/post.js +2 -3
- package/core/server/models/product.js +3 -0
- package/core/server/models/settings.js +4 -0
- package/core/server/models/subscription-created-event.js +10 -2
- package/core/server/models/user.js +41 -7
- package/core/server/services/auth/api-key/admin.js +0 -3
- package/core/server/services/auth/passwordreset.js +0 -3
- package/core/server/services/explore/service.js +7 -6
- package/core/server/services/member-attribution/index.js +34 -6
- package/core/server/services/members/api.js +4 -0
- package/core/server/services/members/service.js +6 -3
- package/core/server/services/public-config/site.js +1 -0
- package/core/server/services/route-settings/default-settings-manager.js +19 -17
- package/core/server/services/staff/index.js +26 -0
- package/core/server/services/webhooks/trigger.js +14 -5
- package/core/shared/config/defaults.json +3 -2
- package/core/shared/labs.js +8 -7
- package/package.json +84 -82
- package/yarn.lock +101 -123
- package/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
- package/components/tryghost-api-framework-0.0.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
- package/components/tryghost-constants-0.0.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
- package/components/tryghost-domain-events-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-email-content-generator-0.0.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
- package/components/tryghost-extract-api-key-0.0.0.tgz +0 -0
- package/components/tryghost-job-manager-0.0.0.tgz +0 -0
- package/components/tryghost-magic-link-0.0.0.tgz +0 -0
- package/components/tryghost-mailgun-client-0.0.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-member-attribution-0.0.0.tgz +0 -0
- package/components/tryghost-member-events-0.0.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
- package/components/tryghost-members-api-0.0.0.tgz +0 -0
- package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
- package/components/tryghost-members-importer-0.0.0.tgz +0 -0
- package/components/tryghost-members-offers-0.0.0.tgz +0 -0
- package/components/tryghost-members-payments-0.0.0.tgz +0 -0
- package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
- package/components/tryghost-minifier-0.0.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-0.0.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-0.0.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-0.0.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-0.0.0.tgz +0 -0
- package/components/tryghost-mw-vhost-0.0.0.tgz +0 -0
- package/components/tryghost-oembed-service-0.0.0.tgz +0 -0
- package/components/tryghost-package-json-0.0.0.tgz +0 -0
- package/components/tryghost-security-0.0.0.tgz +0 -0
- package/components/tryghost-session-service-0.0.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
- package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
- package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-0.0.0.tgz +0 -0
- package/core/built/admin/assets/chunk.143.6a3c46a89c731b86a730.js +0 -41
- package/core/built/admin/assets/ghost-13baab17b3f54b21f341fb8f36f83110.css +0 -1
- package/core/built/admin/assets/ghost-dark-b0500577a42e2770994e6aef0e70f182.css +0 -1
|
@@ -40,15 +40,16 @@ class Gravatar {
|
|
|
40
40
|
image: imageUrl
|
|
41
41
|
};
|
|
42
42
|
})
|
|
43
|
-
.catch(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
.catch(function (err) {
|
|
44
|
+
if (err.statusCode === 404) {
|
|
45
|
+
return {
|
|
46
|
+
image: undefined
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
49
50
|
// ignore error, just resolve with no image url
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
module.exports = Gravatar;
|
|
55
|
+
module.exports = Gravatar;
|
|
@@ -163,39 +163,41 @@ class ImageSize {
|
|
|
163
163
|
width: dimensions.width,
|
|
164
164
|
height: dimensions.height
|
|
165
165
|
};
|
|
166
|
-
}).catch(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
166
|
+
}).catch((err) => {
|
|
167
|
+
if (err.code === 'URL_MISSING_INVALID') {
|
|
168
|
+
return Promise.reject(new errors.InternalServerError({
|
|
169
|
+
message: err.message,
|
|
170
|
+
code: 'IMAGE_SIZE_URL',
|
|
171
|
+
statusCode: err.statusCode,
|
|
172
|
+
context: err.url || imagePath
|
|
173
|
+
}));
|
|
174
|
+
} else if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT' || err.code === 'ECONNRESET' || err.statusCode === 408) {
|
|
175
|
+
return Promise.reject(new errors.InternalServerError({
|
|
176
|
+
message: 'Request timed out.',
|
|
177
|
+
code: 'IMAGE_SIZE_URL',
|
|
178
|
+
statusCode: err.statusCode,
|
|
179
|
+
context: err.url || imagePath
|
|
180
|
+
}));
|
|
181
|
+
} else if (err.code === 'ENOENT' || err.code === 'ENOTFOUND' || err.statusCode === 404) {
|
|
182
|
+
return Promise.reject(new errors.NotFoundError({
|
|
183
|
+
message: 'Image not found.',
|
|
184
|
+
code: 'IMAGE_SIZE_URL',
|
|
185
|
+
statusCode: err.statusCode,
|
|
186
|
+
context: err.url || imagePath
|
|
187
|
+
}));
|
|
188
|
+
} else {
|
|
189
|
+
if (errors.utils.isGhostError(err)) {
|
|
190
|
+
return Promise.reject(err);
|
|
191
|
+
}
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
return Promise.reject(new errors.InternalServerError({
|
|
194
|
+
message: 'Unknown Request error.',
|
|
195
|
+
code: 'IMAGE_SIZE_URL',
|
|
196
|
+
statusCode: err.statusCode,
|
|
197
|
+
context: err.url || imagePath,
|
|
198
|
+
err: err
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
199
201
|
});
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -237,32 +239,34 @@ class ImageSize {
|
|
|
237
239
|
height: dimensions.height
|
|
238
240
|
};
|
|
239
241
|
})
|
|
240
|
-
.catch(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
.catch((err) => {
|
|
243
|
+
if (err.code === 'ENOENT') {
|
|
244
|
+
return Promise.reject(new errors.NotFoundError({
|
|
245
|
+
message: err.message,
|
|
246
|
+
code: 'IMAGE_SIZE_STORAGE',
|
|
247
|
+
err: err,
|
|
248
|
+
context: filePath,
|
|
249
|
+
errorDetails: {
|
|
250
|
+
originalPath: imagePath,
|
|
251
|
+
reqFilePath: filePath
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
} else {
|
|
255
|
+
if (errors.utils.isGhostError(err)) {
|
|
256
|
+
return Promise.reject(err);
|
|
249
257
|
}
|
|
250
|
-
}));
|
|
251
|
-
}).catch((err) => {
|
|
252
|
-
if (errors.utils.isGhostError(err)) {
|
|
253
|
-
return Promise.reject(err);
|
|
254
|
-
}
|
|
255
258
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
return Promise.reject(new errors.InternalServerError({
|
|
260
|
+
message: err.message,
|
|
261
|
+
code: 'IMAGE_SIZE_STORAGE',
|
|
262
|
+
err: err,
|
|
263
|
+
context: filePath,
|
|
264
|
+
errorDetails: {
|
|
265
|
+
originalPath: imagePath,
|
|
266
|
+
reqFilePath: filePath
|
|
267
|
+
}
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
266
270
|
});
|
|
267
271
|
}
|
|
268
272
|
|
|
@@ -1,34 +1,21 @@
|
|
|
1
|
-
const _ = require('lodash');
|
|
2
1
|
const ghostBookshelf = require('./base');
|
|
3
2
|
|
|
4
|
-
const candidates = [];
|
|
5
|
-
|
|
6
3
|
const Action = ghostBookshelf.Model.extend({
|
|
7
4
|
tableName: 'actions',
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
candidates() {
|
|
7
|
+
return Object.keys(ghostBookshelf.registry.models).map((key) => {
|
|
8
|
+
const model = ghostBookshelf.registry.models[key];
|
|
9
|
+
return [model, model.prototype.tableName.replace(/s$/, '')];
|
|
12
10
|
});
|
|
13
|
-
this.constructor.__super__.initialize.apply(this, arguments);
|
|
14
11
|
},
|
|
15
12
|
|
|
16
13
|
actor() {
|
|
17
|
-
return this.morphTo('actor', ['actor_type', 'actor_id'], ...candidates);
|
|
14
|
+
return this.morphTo('actor', ['actor_type', 'actor_id'], ...this.candidates());
|
|
18
15
|
},
|
|
19
16
|
|
|
20
17
|
resource() {
|
|
21
|
-
return this.morphTo('resource', ['resource_type', 'resource_id'], ...candidates);
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
toJSON(unfilteredOptions) {
|
|
25
|
-
const options = Action.filterOptions(unfilteredOptions, 'toJSON');
|
|
26
|
-
const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
|
|
27
|
-
|
|
28
|
-
// @TODO: context is not implemented yet
|
|
29
|
-
delete attrs.context;
|
|
30
|
-
|
|
31
|
-
return attrs;
|
|
18
|
+
return this.morphTo('resource', ['resource_type', 'resource_id'], ...this.candidates());
|
|
32
19
|
}
|
|
33
20
|
}, {
|
|
34
21
|
orderDefaultOptions: function orderDefaultOptions() {
|
|
@@ -29,14 +29,37 @@ module.exports = function (Bookshelf) {
|
|
|
29
29
|
resourceType = resourceType.bind(this)();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (!resourceType) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let context = {};
|
|
37
|
+
|
|
38
|
+
if (this.actionsExtraContext && Array.isArray(this.actionsExtraContext)) {
|
|
39
|
+
for (const c of this.actionsExtraContext) {
|
|
40
|
+
context[c] = this.get(c) || this.previous(c);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (event === 'deleted') {
|
|
45
|
+
context.primary_name = (this.previous('title') || this.previous('name'));
|
|
46
|
+
} else if (['added', 'edited'].includes(event)) {
|
|
47
|
+
context.primary_name = (this.get('title') || this.get('name') || this.previous('title') || this.previous('name'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = {
|
|
51
|
+
event,
|
|
35
52
|
resource_id: this.id || this.previous('id'),
|
|
36
53
|
resource_type: resourceType,
|
|
37
54
|
actor_id: actor.id,
|
|
38
55
|
actor_type: actor.type
|
|
39
56
|
};
|
|
57
|
+
|
|
58
|
+
if (context && Object.keys(context).length) {
|
|
59
|
+
data.context = context;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return data;
|
|
40
63
|
},
|
|
41
64
|
|
|
42
65
|
/**
|
|
@@ -8,8 +8,16 @@ const MemberCreatedEvent = ghostBookshelf.Model.extend({
|
|
|
8
8
|
return this.belongsTo('Member', 'member_id', 'id');
|
|
9
9
|
},
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
return this.belongsTo('Post', 'attribution_id', 'id');
|
|
11
|
+
postAttribution() {
|
|
12
|
+
return this.belongsTo('Post', 'attribution_id', 'id');
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
userAttribution() {
|
|
16
|
+
return this.belongsTo('User', 'attribution_id', 'id');
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
tagAttribution() {
|
|
20
|
+
return this.belongsTo('Tag', 'attribution_id', 'id');
|
|
13
21
|
}
|
|
14
22
|
}, {
|
|
15
23
|
async edit() {
|
|
@@ -8,6 +8,10 @@ const MemberPaidSubscriptionEvent = ghostBookshelf.Model.extend({
|
|
|
8
8
|
return this.belongsTo('Member', 'member_id', 'id');
|
|
9
9
|
},
|
|
10
10
|
|
|
11
|
+
subscriptionCreatedEvent() {
|
|
12
|
+
return this.belongsTo('SubscriptionCreatedEvent', 'subscription_id', 'subscription_id');
|
|
13
|
+
},
|
|
14
|
+
|
|
11
15
|
customQuery(qb, options) {
|
|
12
16
|
if (options.aggregateMRRDeltas) {
|
|
13
17
|
if (options.limit || options.filter) {
|
|
@@ -40,6 +40,12 @@ const Member = ghostBookshelf.Model.extend({
|
|
|
40
40
|
}, {
|
|
41
41
|
key: 'newsletters',
|
|
42
42
|
replacement: 'newsletters.slug'
|
|
43
|
+
}, {
|
|
44
|
+
key: 'signup',
|
|
45
|
+
replacement: 'signups.attribution_id'
|
|
46
|
+
}, {
|
|
47
|
+
key: 'conversion',
|
|
48
|
+
replacement: 'conversions.attribution_id'
|
|
43
49
|
}];
|
|
44
50
|
},
|
|
45
51
|
|
|
@@ -74,6 +80,18 @@ const Member = ghostBookshelf.Model.extend({
|
|
|
74
80
|
joinFrom: 'member_id',
|
|
75
81
|
joinTo: 'customer_id',
|
|
76
82
|
joinToForeign: 'customer_id'
|
|
83
|
+
},
|
|
84
|
+
signups: {
|
|
85
|
+
tableName: 'members_created_events',
|
|
86
|
+
tableNameAs: 'signups',
|
|
87
|
+
type: 'oneToOne',
|
|
88
|
+
joinFrom: 'member_id'
|
|
89
|
+
},
|
|
90
|
+
conversions: {
|
|
91
|
+
tableName: 'members_subscription_created_events',
|
|
92
|
+
tableNameAs: 'conversions',
|
|
93
|
+
type: 'oneToOne',
|
|
94
|
+
joinFrom: 'member_id'
|
|
77
95
|
}
|
|
78
96
|
};
|
|
79
97
|
},
|
|
@@ -39,9 +39,8 @@ Post = ghostBookshelf.Model.extend({
|
|
|
39
39
|
tableName: 'posts',
|
|
40
40
|
|
|
41
41
|
actionsCollectCRUD: true,
|
|
42
|
-
actionsResourceType:
|
|
43
|
-
|
|
44
|
-
},
|
|
42
|
+
actionsResourceType: 'post',
|
|
43
|
+
actionsExtraContext: ['type'],
|
|
45
44
|
|
|
46
45
|
/**
|
|
47
46
|
* @NOTE
|
|
@@ -97,6 +97,10 @@ Settings = ghostBookshelf.Model.extend({
|
|
|
97
97
|
|
|
98
98
|
tableName: 'settings',
|
|
99
99
|
|
|
100
|
+
actionsCollectCRUD: true,
|
|
101
|
+
actionsResourceType: 'setting',
|
|
102
|
+
actionsExtraContext: ['key', 'group'],
|
|
103
|
+
|
|
100
104
|
emitChange: function emitChange(event, options) {
|
|
101
105
|
const eventToTrigger = 'settings' + '.' + event;
|
|
102
106
|
ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options);
|
|
@@ -12,8 +12,16 @@ const SubscriptionCreatedEvent = ghostBookshelf.Model.extend({
|
|
|
12
12
|
return this.belongsTo('StripeCustomerSubscription', 'subscription_id', 'id');
|
|
13
13
|
},
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
return this.belongsTo('Post', 'attribution_id', 'id');
|
|
15
|
+
postAttribution() {
|
|
16
|
+
return this.belongsTo('Post', 'attribution_id', 'id');
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
userAttribution() {
|
|
20
|
+
return this.belongsTo('User', 'attribution_id', 'id');
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
tagAttribution() {
|
|
24
|
+
return this.belongsTo('Tag', 'attribution_id', 'id');
|
|
17
25
|
}
|
|
18
26
|
}, {
|
|
19
27
|
async edit() {
|
|
@@ -65,7 +65,10 @@ User = ghostBookshelf.Model.extend({
|
|
|
65
65
|
password: security.identifier.uid(50),
|
|
66
66
|
visibility: 'public',
|
|
67
67
|
status: 'active',
|
|
68
|
-
comment_notifications: true
|
|
68
|
+
comment_notifications: true,
|
|
69
|
+
free_member_signup_notification: true,
|
|
70
|
+
paid_subscription_started_notification: true,
|
|
71
|
+
paid_subscription_canceled_notification: false
|
|
69
72
|
};
|
|
70
73
|
},
|
|
71
74
|
|
|
@@ -485,6 +488,33 @@ User = ghostBookshelf.Model.extend({
|
|
|
485
488
|
return query.fetch(options);
|
|
486
489
|
},
|
|
487
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Returns users who should receive a specific type of alert
|
|
493
|
+
* @param {'free-signup'|'paid-started'|'paid-canceled'} type The type of alert to fetch users for
|
|
494
|
+
* @param {any} options
|
|
495
|
+
* @return {Promise<[Object]>} Array of users
|
|
496
|
+
*/
|
|
497
|
+
getEmailAlertUsers(type, options) {
|
|
498
|
+
options = options || {};
|
|
499
|
+
|
|
500
|
+
let filter = 'status:active';
|
|
501
|
+
if (type === 'free-signup') {
|
|
502
|
+
filter += '+free_member_signup_notification:true';
|
|
503
|
+
} else if (type === 'paid-started') {
|
|
504
|
+
filter += '+paid_subscription_started_notification:true';
|
|
505
|
+
} else if (type === 'paid-canceled') {
|
|
506
|
+
filter += '+paid_subscription_canceled_notification:true';
|
|
507
|
+
}
|
|
508
|
+
const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']});
|
|
509
|
+
return this.findAll(updatedOptions).then((users) => {
|
|
510
|
+
return users.toJSON().filter((user) => {
|
|
511
|
+
return user?.roles?.some((role) => {
|
|
512
|
+
return ['Owner', 'Administrator'].includes(role.name);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
},
|
|
517
|
+
|
|
488
518
|
/**
|
|
489
519
|
* ### Edit
|
|
490
520
|
*
|
|
@@ -1002,10 +1032,10 @@ User = ghostBookshelf.Model.extend({
|
|
|
1002
1032
|
let ownerRole;
|
|
1003
1033
|
let contextUser;
|
|
1004
1034
|
|
|
1005
|
-
return Promise.
|
|
1035
|
+
return Promise.all([
|
|
1006
1036
|
ghostBookshelf.model('Role').findOne({name: 'Owner'}),
|
|
1007
1037
|
User.findOne({id: options.context.user}, {withRelated: ['roles']})
|
|
1008
|
-
)
|
|
1038
|
+
])
|
|
1009
1039
|
.then((results) => {
|
|
1010
1040
|
ownerRole = results[0];
|
|
1011
1041
|
contextUser = results[1];
|
|
@@ -1018,8 +1048,10 @@ User = ghostBookshelf.Model.extend({
|
|
|
1018
1048
|
}));
|
|
1019
1049
|
}
|
|
1020
1050
|
|
|
1021
|
-
return Promise.
|
|
1022
|
-
|
|
1051
|
+
return Promise.all([
|
|
1052
|
+
ghostBookshelf.model('Role').findOne({name: 'Administrator'}),
|
|
1053
|
+
User.findOne({id: object.id}, {withRelated: ['roles']})
|
|
1054
|
+
]);
|
|
1023
1055
|
})
|
|
1024
1056
|
.then((results) => {
|
|
1025
1057
|
const adminRole = results[0];
|
|
@@ -1046,9 +1078,11 @@ User = ghostBookshelf.Model.extend({
|
|
|
1046
1078
|
}
|
|
1047
1079
|
|
|
1048
1080
|
// convert owner to admin
|
|
1049
|
-
return Promise.
|
|
1081
|
+
return Promise.all([
|
|
1082
|
+
contextUser.roles().updatePivot({role_id: adminRole.id}),
|
|
1050
1083
|
user.roles().updatePivot({role_id: ownerRole.id}),
|
|
1051
|
-
user.id
|
|
1084
|
+
user.id
|
|
1085
|
+
]);
|
|
1052
1086
|
})
|
|
1053
1087
|
.then((results) => {
|
|
1054
1088
|
return Users.forge()
|
|
@@ -144,9 +144,6 @@ function doReset(options, tokenParts, settingsAPI) {
|
|
|
144
144
|
updatedUser.set('status', 'active');
|
|
145
145
|
return updatedUser.save(options);
|
|
146
146
|
})
|
|
147
|
-
.catch(errors.ValidationError, (err) => {
|
|
148
|
-
return Promise.reject(err);
|
|
149
|
-
})
|
|
150
147
|
.catch((err) => {
|
|
151
148
|
if (errors.utils.isGhostError(err)) {
|
|
152
149
|
return Promise.reject(err);
|
|
@@ -26,18 +26,19 @@ module.exports = class ExploreService {
|
|
|
26
26
|
const totalMembers = await this.MembersService.stats.getTotalMembers();
|
|
27
27
|
const mrrStats = await this.StatsService.getMRRHistory();
|
|
28
28
|
|
|
29
|
-
const {description, icon, title, url, accent_color: accentColor} = this.PublicConfigService.site;
|
|
29
|
+
const {description, icon, title, url, accent_color: accentColor, locale} = this.PublicConfigService.site;
|
|
30
30
|
|
|
31
31
|
const exploreProperties = {
|
|
32
32
|
version: ghostVersion.full,
|
|
33
|
-
totalMembers,
|
|
34
|
-
mrrStats,
|
|
33
|
+
total_members: totalMembers,
|
|
34
|
+
mrr_stats: mrrStats,
|
|
35
35
|
site: {
|
|
36
36
|
description,
|
|
37
37
|
icon,
|
|
38
38
|
title,
|
|
39
39
|
url,
|
|
40
|
-
accentColor
|
|
40
|
+
accent_color: accentColor,
|
|
41
|
+
locale
|
|
41
42
|
},
|
|
42
43
|
stripe: {
|
|
43
44
|
configured: this.StripeService.api.configured,
|
|
@@ -46,10 +47,10 @@ module.exports = class ExploreService {
|
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
const mostRecentlyPublishedPost = await this.PostsService.getMostRecentlyPublishedPost();
|
|
49
|
-
exploreProperties.
|
|
50
|
+
exploreProperties.most_recently_published_at = mostRecentlyPublishedPost?.get('published_at') || null;
|
|
50
51
|
|
|
51
52
|
const owner = await this.UserModel.findOne({role: 'Owner', status: 'all'});
|
|
52
|
-
exploreProperties.
|
|
53
|
+
exploreProperties.owner_email = owner?.get('email') || null;
|
|
53
54
|
|
|
54
55
|
return exploreProperties;
|
|
55
56
|
}
|
|
@@ -1,23 +1,51 @@
|
|
|
1
1
|
const urlService = require('../url');
|
|
2
2
|
const labsService = require('../../../shared/labs');
|
|
3
|
+
const DomainEvents = require('@tryghost/domain-events');
|
|
4
|
+
const urlUtils = require('../../../shared/url-utils');
|
|
3
5
|
|
|
4
6
|
class MemberAttributionServiceWrapper {
|
|
5
7
|
init() {
|
|
6
|
-
if (this.
|
|
8
|
+
if (this.eventHandler) {
|
|
7
9
|
// Prevent creating duplicate DomainEvents subscribers
|
|
8
10
|
return;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
// Wire up all the dependencies
|
|
14
|
+
const {MemberAttributionService, UrlTranslator, AttributionBuilder, EventHandler} = require('@tryghost/member-attribution');
|
|
12
15
|
const models = require('../../models');
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
const urlTranslator = new UrlTranslator({
|
|
18
|
+
urlService,
|
|
19
|
+
urlUtils,
|
|
20
|
+
models: {
|
|
21
|
+
Post: models.Post,
|
|
22
|
+
User: models.User,
|
|
23
|
+
Tag: models.Tag
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.attributionBuilder = new AttributionBuilder({urlTranslator});
|
|
28
|
+
|
|
29
|
+
// Expose the service
|
|
15
30
|
this.service = new MemberAttributionService({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
models: {
|
|
32
|
+
MemberCreatedEvent: models.MemberCreatedEvent,
|
|
33
|
+
SubscriptionCreatedEvent: models.SubscriptionCreatedEvent
|
|
34
|
+
},
|
|
35
|
+
attributionBuilder: this.attributionBuilder,
|
|
36
|
+
labsService
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Listen for events and store them in the database
|
|
40
|
+
this.eventHandler = new EventHandler({
|
|
41
|
+
models: {
|
|
42
|
+
MemberCreatedEvent: models.MemberCreatedEvent,
|
|
43
|
+
SubscriptionCreatedEvent: models.SubscriptionCreatedEvent
|
|
44
|
+
},
|
|
45
|
+
DomainEvents,
|
|
19
46
|
labsService
|
|
20
47
|
});
|
|
48
|
+
this.eventHandler.subscribe();
|
|
21
49
|
}
|
|
22
50
|
}
|
|
23
51
|
|
|
@@ -13,6 +13,7 @@ const SingleUseTokenProvider = require('./SingleUseTokenProvider');
|
|
|
13
13
|
const urlUtils = require('../../../shared/url-utils');
|
|
14
14
|
const labsService = require('../../../shared/labs');
|
|
15
15
|
const offersService = require('../offers');
|
|
16
|
+
const staffService = require('../staff');
|
|
16
17
|
const newslettersService = require('../newsletters');
|
|
17
18
|
const memberAttributionService = require('../member-attribution');
|
|
18
19
|
|
|
@@ -185,6 +186,8 @@ function createApiInstance(config) {
|
|
|
185
186
|
MemberStatusEvent: models.MemberStatusEvent,
|
|
186
187
|
MemberProductEvent: models.MemberProductEvent,
|
|
187
188
|
MemberAnalyticEvent: models.MemberAnalyticEvent,
|
|
189
|
+
MemberCreatedEvent: models.MemberCreatedEvent,
|
|
190
|
+
SubscriptionCreatedEvent: models.SubscriptionCreatedEvent,
|
|
188
191
|
OfferRedemption: models.OfferRedemption,
|
|
189
192
|
Offer: models.Offer,
|
|
190
193
|
StripeProduct: models.StripeProduct,
|
|
@@ -195,6 +198,7 @@ function createApiInstance(config) {
|
|
|
195
198
|
},
|
|
196
199
|
stripeAPIService: stripeService.api,
|
|
197
200
|
offersAPI: offersService.api,
|
|
201
|
+
staffService: staffService.api,
|
|
198
202
|
labsService: labsService,
|
|
199
203
|
newslettersService: newslettersService,
|
|
200
204
|
memberAttributionService: memberAttributionService.service
|
|
@@ -105,10 +105,12 @@ module.exports = {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
verificationTrigger = new VerificationTrigger({
|
|
108
|
-
|
|
108
|
+
apiTriggerThreshold: _.get(config.get('hostSettings'), 'emailVerification.apiThreshold'),
|
|
109
|
+
adminTriggerThreshold: _.get(config.get('hostSettings'), 'emailVerification.adminThreshold'),
|
|
110
|
+
importTriggerThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
|
109
111
|
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
|
110
112
|
isVerificationRequired: () => settingsCache.get('email_verification_required') === true,
|
|
111
|
-
sendVerificationEmail: ({subject, message,
|
|
113
|
+
sendVerificationEmail: ({subject, message, amountTriggered}) => {
|
|
112
114
|
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
|
113
115
|
const fromAddress = config.get('user_email');
|
|
114
116
|
|
|
@@ -116,7 +118,7 @@ module.exports = {
|
|
|
116
118
|
ghostMailer.send({
|
|
117
119
|
subject,
|
|
118
120
|
html: tpl(message, {
|
|
119
|
-
|
|
121
|
+
amountTriggered: amountTriggered,
|
|
120
122
|
siteUrl: urlUtils.getSiteUrl()
|
|
121
123
|
}),
|
|
122
124
|
forceTextContent: true,
|
|
@@ -189,4 +191,5 @@ module.exports = {
|
|
|
189
191
|
stats: membersStats,
|
|
190
192
|
export: require('./exporter/query')
|
|
191
193
|
};
|
|
194
|
+
|
|
192
195
|
module.exports.middleware = require('./middleware');
|
|
@@ -10,6 +10,7 @@ module.exports = function getSiteProperties() {
|
|
|
10
10
|
logo: settingsCache.get('logo'),
|
|
11
11
|
icon: settingsCache.get('icon'),
|
|
12
12
|
accent_color: settingsCache.get('accent_color'),
|
|
13
|
+
locale: settingsCache.get('locale'),
|
|
13
14
|
url: urlUtils.urlFor('home', true),
|
|
14
15
|
version: ghostVersion.safe
|
|
15
16
|
};
|