ghost 5.11.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.11.0.tgz → 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-5.11.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.11.0.tgz → tryghost-email-analytics-service-5.12.0.tgz} +0 -0
- package/components/{tryghost-email-content-generator-5.11.0.tgz → 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-5.12.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.11.0.tgz → 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.11.0.tgz → tryghost-mailgun-client-5.12.0.tgz} +0 -0
- package/components/{tryghost-member-analytics-service-5.11.0.tgz → 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-5.11.0.tgz → tryghost-members-csv-5.12.0.tgz} +0 -0
- package/components/{tryghost-members-events-service-5.11.0.tgz → tryghost-members-events-service-5.12.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.11.0.tgz → 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.11.0.tgz → tryghost-members-stripe-service-5.12.0.tgz} +0 -0
- package/components/{tryghost-minifier-5.11.0.tgz → tryghost-minifier-5.12.0.tgz} +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.11.0.tgz → 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.11.0.tgz → tryghost-mw-error-handler-5.12.0.tgz} +0 -0
- package/components/{tryghost-mw-session-from-token-5.11.0.tgz → tryghost-mw-session-from-token-5.12.0.tgz} +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.11.0.tgz → 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.11.0.tgz → 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.11.0.tgz → tryghost-session-service-5.12.0.tgz} +0 -0
- package/components/{tryghost-settings-path-manager-5.11.0.tgz → 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.11.0.tgz → 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.11.0.tgz → tryghost-version-notifications-data-service-5.12.0.tgz} +0 -0
- package/core/boot.js +2 -0
- package/core/built/admin/assets/{chunk.143.14589cc066b8120b73e3.js → chunk.143.da85cf08f47cfe520bf6.js} +5 -5
- package/core/built/admin/assets/{chunk.178.131e85a10d2031148425.js → chunk.178.29d3fb3dc6811b673a00.js} +4 -4
- package/core/built/admin/assets/{ghost-40f5bd12d121c54bbc39e7939e78244f.js → ghost-0526c96b20843697927c1d06a9010197.js} +144 -154
- package/core/built/admin/assets/{ghost-dark-7b2825a050b0382630180f48aa78ea5d.css → ghost-dark-e4d6987343ee26af534a06c1b9639d97.css} +1 -1
- package/core/built/admin/assets/{ghost-1b0d7c731511bb738ec457d2932c43c0.css → ghost-f62b0e78ddcd947273873bdeba4abc3c.css} +1 -1
- 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/{vendor-741dc0e4078e044a0c9bfaad104de8b3.js → vendor-52613f40d62355e9ac64cbfa211169bb.js} +79 -55
- package/core/built/admin/index.html +5 -5
- 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/data/schema/schema.js +20 -4
- package/core/server/models/action.js +6 -9
- 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/subscription-created-event.js +10 -2
- package/core/server/models/user.js +37 -6
- package/core/server/services/member-attribution/index.js +2 -2
- package/core/server/services/members/api.js +4 -0
- package/core/server/services/members/service.js +6 -3
- package/core/server/services/staff/index.js +26 -0
- package/core/shared/labs.js +6 -5
- package/package.json +83 -81
- package/yarn.lock +13 -18
- package/components/tryghost-api-framework-5.11.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.11.0.tgz +0 -0
- package/components/tryghost-constants-5.11.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.11.0.tgz +0 -0
- package/components/tryghost-domain-events-5.11.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.11.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.11.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.11.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.11.0.tgz +0 -0
- package/components/tryghost-magic-link-5.11.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.11.0.tgz +0 -0
- package/components/tryghost-member-events-5.11.0.tgz +0 -0
- package/components/tryghost-members-analytics-ingress-5.11.0.tgz +0 -0
- package/components/tryghost-members-api-5.11.0.tgz +0 -0
- package/components/tryghost-members-offers-5.11.0.tgz +0 -0
- package/components/tryghost-members-payments-5.11.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.11.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.11.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.11.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.11.0.tgz +0 -0
- package/components/tryghost-security-5.11.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.11.0.tgz +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.12%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-bc9d2c9e5c8a33f0c92e81189d48e04c.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-f62b0e78ddcd947273873bdeba4abc3c.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
|
|
54
54
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
55
55
|
|
|
56
|
-
<script src="assets/vendor-
|
|
56
|
+
<script src="assets/vendor-52613f40d62355e9ac64cbfa211169bb.js"></script>
|
|
57
57
|
<script src="assets/chunk.579.65e09dd89eec70d059a0.js"></script>
|
|
58
|
-
<script src="assets/chunk.143.
|
|
59
|
-
<script src="assets/ghost-
|
|
58
|
+
<script src="assets/chunk.143.da85cf08f47cfe520bf6.js"></script>
|
|
59
|
+
<script src="assets/ghost-0526c96b20843697927c1d06a9010197.js"></script>
|
|
60
60
|
</body>
|
|
61
61
|
</html>
|
|
@@ -4,26 +4,11 @@ const {SafeString} = require('../services/handlebars');
|
|
|
4
4
|
const {labs} = require('../services/proxy');
|
|
5
5
|
|
|
6
6
|
function search() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
height: 32px;
|
|
13
|
-
padding: 0;
|
|
14
|
-
border: 0;
|
|
15
|
-
color: inherit;
|
|
16
|
-
background-color: transparent;
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
outline: none;
|
|
19
|
-
}</style>
|
|
20
|
-
<button class="gh-search-icon" aria-label="search" data-ghost-search>
|
|
21
|
-
<svg width="20" height="20" fill="none" viewBox="0 0 24 24">
|
|
22
|
-
<path d="M14.949 14.949a1 1 0 0 1 1.414 0l6.344 6.344a1 1 0 0 1-1.414 1.414l-6.344-6.344a1 1 0 0 1 0-1.414Z"
|
|
23
|
-
fill="currentColor"/>
|
|
24
|
-
<path d="M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm-9 7a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" fill="currentColor"/>
|
|
25
|
-
</svg>
|
|
26
|
-
</button>`;
|
|
7
|
+
// We want this to output as one line, but splitting for readability
|
|
8
|
+
const svg = '<button class="gh-search-icon" aria-label="search" data-ghost-search '
|
|
9
|
+
+ 'style="display: inline-flex; justify-content: center; align-items: center; width: 32px; height: 32px; padding: 0; border: 0; color: inherit; background-color: transparent; cursor: pointer; outline: none;">'
|
|
10
|
+
+ '<svg width="20" height="20" fill="none" viewBox="0 0 24 24"><path d="M14.949 14.949a1 1 0 0 1 1.414 0l6.344 6.344a1 1 0 0 1-1.414 1.414l-6.344-6.344a1 1 0 0 1 0-1.414Z" fill="currentColor"/>'
|
|
11
|
+
+ '<path d="M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm-9 7a9 9 0 1 1 18 0 9 9 0 0 1-18 0Z" fill="currentColor"/></svg></button>';
|
|
27
12
|
|
|
28
13
|
return new SafeString(svg);
|
|
29
14
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const Promise = require('bluebird');
|
|
2
1
|
const settingsCache = require('../../shared/settings-cache');
|
|
3
2
|
const urlUtils = require('../../shared/url-utils');
|
|
4
3
|
const logging = require('@tryghost/logging');
|
|
@@ -81,7 +80,7 @@ function getMetaData(data, root) {
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
// @TODO: wrap this in a utility function
|
|
84
|
-
return
|
|
83
|
+
return getImageDimensions(metaData).then(function () {
|
|
85
84
|
metaData.structuredData = getStructuredData(metaData);
|
|
86
85
|
metaData.schema = getSchema(metaData, data);
|
|
87
86
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
const Promise = require('bluebird');
|
|
2
1
|
const _ = require('lodash');
|
|
3
2
|
const imageSizeCache = require('../../server/lib/image').cachedImageSizeFromUrl;
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Get Image dimensions
|
|
7
6
|
* @param {object} metaData
|
|
8
|
-
* @returns {object} metaData
|
|
7
|
+
* @returns {Promise<object>} metaData
|
|
9
8
|
* @description for image properties in meta data (coverImage, authorImage and site.logo), `getCachedImageSizeFromUrl` is
|
|
10
9
|
* called to receive image width and height
|
|
11
10
|
*/
|
|
12
|
-
function getImageDimensions(metaData) {
|
|
11
|
+
async function getImageDimensions(metaData) {
|
|
13
12
|
const fetch = {
|
|
14
13
|
coverImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.coverImage.url),
|
|
15
14
|
authorImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.authorImage.url),
|
|
@@ -17,45 +16,54 @@ function getImageDimensions(metaData) {
|
|
|
17
16
|
logo: imageSizeCache.getCachedImageSizeFromUrl(metaData.site.logo.url)
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
19
|
+
const [coverImage, authorImage, ogImage, logo] = await Promise.all([
|
|
20
|
+
fetch.coverImage,
|
|
21
|
+
fetch.authorImage,
|
|
22
|
+
fetch.ogImage,
|
|
23
|
+
fetch.logo
|
|
24
|
+
]);
|
|
25
|
+
const imageObj = {
|
|
26
|
+
coverImage,
|
|
27
|
+
authorImage,
|
|
28
|
+
ogImage,
|
|
29
|
+
logo
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
_.forEach(imageObj, function (key, value) {
|
|
33
|
+
if (_.has(key, 'width') && _.has(key, 'height')) {
|
|
34
|
+
// We have some restrictions for publisher.logo:
|
|
35
|
+
// The image needs to be <=600px wide and <=60px high (ideally exactly 600px x 60px).
|
|
36
|
+
// Unless we have proper image-handling (see https://github.com/TryGhost/Ghost/issues/4453),
|
|
37
|
+
// we will fake it in some cases or not produce an imageObject at all.
|
|
38
|
+
if (value === 'logo') {
|
|
39
|
+
if (key.height <= 60 && key.width <= 600) {
|
|
40
|
+
_.assign(metaData.site[value], {
|
|
41
|
+
dimensions: {
|
|
42
|
+
width: key.width,
|
|
43
|
+
height: key.height
|
|
45
44
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
});
|
|
46
|
+
} else if (key.width === key.height) {
|
|
47
|
+
// CASE: the logo is too large, but it is a square. We fake it...
|
|
48
|
+
_.assign(metaData.site[value], {
|
|
49
|
+
dimensions: {
|
|
50
|
+
width: 60,
|
|
51
|
+
height: 60
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
} else {
|
|
56
|
+
_.assign(metaData[value], {
|
|
57
|
+
dimensions: {
|
|
58
|
+
width: key.width,
|
|
59
|
+
height: key.height
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
});
|
|
66
|
+
return metaData;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
module.exports = getImageDimensions;
|
|
@@ -483,9 +483,17 @@ module.exports = {
|
|
|
483
483
|
created_at: {type: 'dateTime', nullable: false},
|
|
484
484
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
485
485
|
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
486
|
-
attribution_type: {
|
|
486
|
+
attribution_type: {
|
|
487
|
+
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
488
|
+
isIn: [['url', 'post', 'page', 'author', 'tag']]
|
|
489
|
+
}
|
|
490
|
+
},
|
|
487
491
|
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
488
|
-
source: {
|
|
492
|
+
source: {
|
|
493
|
+
type: 'string', maxlength: 50, nullable: false, validations: {
|
|
494
|
+
isIn: [['member', 'import', 'system', 'api', 'admin']]
|
|
495
|
+
}
|
|
496
|
+
}
|
|
489
497
|
},
|
|
490
498
|
members_cancel_events: {
|
|
491
499
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -613,7 +621,11 @@ module.exports = {
|
|
|
613
621
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
614
622
|
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
|
|
615
623
|
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
616
|
-
attribution_type: {
|
|
624
|
+
attribution_type: {
|
|
625
|
+
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
626
|
+
isIn: [['url', 'post', 'page', 'author', 'tag']]
|
|
627
|
+
}
|
|
628
|
+
},
|
|
617
629
|
attribution_url: {type: 'string', maxlength: 2000, nullable: true}
|
|
618
630
|
},
|
|
619
631
|
offer_redemptions: {
|
|
@@ -628,7 +640,11 @@ module.exports = {
|
|
|
628
640
|
member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
|
|
629
641
|
subscribed: {type: 'bool', nullable: false, defaultTo: true},
|
|
630
642
|
created_at: {type: 'dateTime', nullable: false},
|
|
631
|
-
source: {
|
|
643
|
+
source: {
|
|
644
|
+
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
645
|
+
isIn: [['member', 'import', 'system', 'api', 'admin']]
|
|
646
|
+
}
|
|
647
|
+
},
|
|
632
648
|
newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id', cascadeDelete: false}
|
|
633
649
|
},
|
|
634
650
|
stripe_products: {
|
|
@@ -1,24 +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);
|
|
18
|
+
return this.morphTo('resource', ['resource_type', 'resource_id'], ...this.candidates());
|
|
22
19
|
}
|
|
23
20
|
}, {
|
|
24
21
|
orderDefaultOptions: function orderDefaultOptions() {
|
|
@@ -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) {
|
|
@@ -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() {
|
|
@@ -488,6 +488,33 @@ User = ghostBookshelf.Model.extend({
|
|
|
488
488
|
return query.fetch(options);
|
|
489
489
|
},
|
|
490
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
|
+
|
|
491
518
|
/**
|
|
492
519
|
* ### Edit
|
|
493
520
|
*
|
|
@@ -1005,10 +1032,10 @@ User = ghostBookshelf.Model.extend({
|
|
|
1005
1032
|
let ownerRole;
|
|
1006
1033
|
let contextUser;
|
|
1007
1034
|
|
|
1008
|
-
return Promise.
|
|
1035
|
+
return Promise.all([
|
|
1009
1036
|
ghostBookshelf.model('Role').findOne({name: 'Owner'}),
|
|
1010
1037
|
User.findOne({id: options.context.user}, {withRelated: ['roles']})
|
|
1011
|
-
)
|
|
1038
|
+
])
|
|
1012
1039
|
.then((results) => {
|
|
1013
1040
|
ownerRole = results[0];
|
|
1014
1041
|
contextUser = results[1];
|
|
@@ -1021,8 +1048,10 @@ User = ghostBookshelf.Model.extend({
|
|
|
1021
1048
|
}));
|
|
1022
1049
|
}
|
|
1023
1050
|
|
|
1024
|
-
return Promise.
|
|
1025
|
-
|
|
1051
|
+
return Promise.all([
|
|
1052
|
+
ghostBookshelf.model('Role').findOne({name: 'Administrator'}),
|
|
1053
|
+
User.findOne({id: object.id}, {withRelated: ['roles']})
|
|
1054
|
+
]);
|
|
1026
1055
|
})
|
|
1027
1056
|
.then((results) => {
|
|
1028
1057
|
const adminRole = results[0];
|
|
@@ -1049,9 +1078,11 @@ User = ghostBookshelf.Model.extend({
|
|
|
1049
1078
|
}
|
|
1050
1079
|
|
|
1051
1080
|
// convert owner to admin
|
|
1052
|
-
return Promise.
|
|
1081
|
+
return Promise.all([
|
|
1082
|
+
contextUser.roles().updatePivot({role_id: adminRole.id}),
|
|
1053
1083
|
user.roles().updatePivot({role_id: ownerRole.id}),
|
|
1054
|
-
user.id
|
|
1084
|
+
user.id
|
|
1085
|
+
]);
|
|
1055
1086
|
})
|
|
1056
1087
|
.then((results) => {
|
|
1057
1088
|
return Users.forge()
|
|
@@ -24,7 +24,7 @@ class MemberAttributionServiceWrapper {
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
this.attributionBuilder = new AttributionBuilder({urlTranslator});
|
|
28
28
|
|
|
29
29
|
// Expose the service
|
|
30
30
|
this.service = new MemberAttributionService({
|
|
@@ -32,7 +32,7 @@ class MemberAttributionServiceWrapper {
|
|
|
32
32
|
MemberCreatedEvent: models.MemberCreatedEvent,
|
|
33
33
|
SubscriptionCreatedEvent: models.SubscriptionCreatedEvent
|
|
34
34
|
},
|
|
35
|
-
attributionBuilder,
|
|
35
|
+
attributionBuilder: this.attributionBuilder,
|
|
36
36
|
labsService
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -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');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class StaffServiceWrapper {
|
|
2
|
+
init() {
|
|
3
|
+
const StaffService = require('@tryghost/staff-service');
|
|
4
|
+
|
|
5
|
+
const config = require('../../../shared/config');
|
|
6
|
+
const logging = require('@tryghost/logging');
|
|
7
|
+
const models = require('../../models');
|
|
8
|
+
const {GhostMailer} = require('../mail');
|
|
9
|
+
const mailer = new GhostMailer();
|
|
10
|
+
const settingsCache = require('../../../shared/settings-cache');
|
|
11
|
+
const urlService = require('../url');
|
|
12
|
+
const urlUtils = require('../../../shared/url-utils');
|
|
13
|
+
|
|
14
|
+
this.api = new StaffService({
|
|
15
|
+
config,
|
|
16
|
+
logging,
|
|
17
|
+
models,
|
|
18
|
+
mailer,
|
|
19
|
+
settingsCache,
|
|
20
|
+
urlService,
|
|
21
|
+
urlUtils
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = new StaffServiceWrapper();
|
package/core/shared/labs.js
CHANGED
|
@@ -17,21 +17,22 @@ const messages = {
|
|
|
17
17
|
const GA_FEATURES = [
|
|
18
18
|
'newsletterPaywall',
|
|
19
19
|
'freeTrial',
|
|
20
|
-
'compExpiring'
|
|
20
|
+
'compExpiring',
|
|
21
|
+
'searchHelper',
|
|
22
|
+
'emailAlerts'
|
|
21
23
|
];
|
|
22
24
|
|
|
23
25
|
// NOTE: this allowlist is meant to be used to filter out any unexpected
|
|
24
26
|
// input for the "labs" setting value
|
|
25
27
|
const BETA_FEATURES = [
|
|
26
|
-
'activitypub'
|
|
28
|
+
'activitypub',
|
|
29
|
+
'memberAttribution'
|
|
27
30
|
];
|
|
28
31
|
|
|
29
32
|
const ALPHA_FEATURES = [
|
|
30
33
|
'auditLog',
|
|
31
34
|
'urlCache',
|
|
32
|
-
'beforeAfterCard'
|
|
33
|
-
'memberAttribution',
|
|
34
|
-
'searchHelper'
|
|
35
|
+
'beforeAfterCard'
|
|
35
36
|
];
|
|
36
37
|
|
|
37
38
|
module.exports.GA_KEYS = [...GA_FEATURES];
|