ghost 6.1.0 → 6.3.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-i18n-6.3.0.tgz +0 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
- package/core/built/admin/assets/admin-x-activitypub/{index-DmCoswaX.mjs → index-C8tyOPu-.mjs} +2 -2
- package/core/built/admin/assets/admin-x-activitypub/{index-lT95Q15h.mjs → index-QqbAPyqT.mjs} +77 -76
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-Bu9qXr9c.mjs → CodeEditorView-CHa5Y-LX.mjs} +3 -3
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-o4Q9MNrB.mjs → index-8WxO2QXI.mjs} +3017 -2827
- package/core/built/admin/assets/admin-x-settings/{index-qEdfz2hd.mjs → index-CGFCkAXn.mjs} +10 -6
- package/core/built/admin/assets/admin-x-settings/{index-BEpRBH9g.mjs → index-Cg4zMcj4.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-BgCSf8S1.mjs → index-DD3HKlR3.mjs} +306 -315
- package/core/built/admin/assets/admin-x-settings/{modals-BtQORnS4.mjs → modals-DH5H9Tgk.mjs} +8801 -8807
- package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js → chunk.397.a720333cfffc99c47e71.js} +5 -4
- package/core/built/admin/assets/{chunk.524.2aa0847042f20c9a2a00.js → chunk.524.aac61953956de04feb53.js} +6 -6
- package/core/built/admin/assets/{chunk.582.9182c19afab95991771e.js → chunk.582.0a1461429ddbaef85ea9.js} +7 -7
- package/core/built/admin/assets/{ghost-9c47d152972b304cab0fb982dc3fccc1.js → ghost-1bfab97cb7f550726e894fae6650a808.js} +24 -22
- package/core/built/admin/assets/ghost-8ade80412a20088a4f0a9a1159f0bdba.css +1 -0
- package/core/built/admin/assets/ghost-dark-b128f29fc44b34b6cfb0fc8492266c2a.css +1 -0
- package/core/built/admin/assets/posts/posts.js +30617 -30330
- package/core/built/admin/assets/stats/stats.js +21342 -21272
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/ghost_head.js +2 -1
- package/core/server/api/endpoints/stats.js +37 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/settings-key-group-mapper.js +1 -0
- package/core/server/api/endpoints/utils/serializers/input/utils/settings-key-type-mapper.js +1 -0
- package/core/server/data/migrations/utils/schema.js +11 -6
- package/core/server/data/migrations/versions/6.2/2025-09-30-14-28-09-add-utm-fields.js +24 -0
- package/core/server/data/migrations/versions/6.3/2025-10-02-15-13-31-add-members-otc-secret-setting.js +9 -0
- package/core/server/data/schema/commands.js +21 -6
- package/core/server/data/schema/default-settings/default-settings.json +4 -0
- package/core/server/data/schema/schema.js +24 -0
- package/core/server/models/settings.js +1 -0
- package/core/server/services/donations/DonationBookshelfRepository.js +6 -1
- package/core/server/services/donations/DonationBookshelfRepository.ts +11 -1
- package/core/server/services/donations/DonationPaymentEvent.js +10 -0
- package/core/server/services/donations/DonationPaymentEvent.ts +10 -0
- package/core/server/services/email-service/EmailRenderer.js +1 -1
- package/core/server/services/lib/MailgunClient.js +4 -3
- package/core/server/services/lib/magic-link/MagicLink.js +9 -9
- package/core/server/services/mail/GhostMailer.js +4 -1
- package/core/server/services/member-attribution/AttributionBuilder.js +55 -10
- package/core/server/services/member-attribution/README.md +101 -0
- package/core/server/services/member-attribution/ReferrerTranslator.js +40 -3
- package/core/server/services/member-attribution/UrlHistory.js +5 -0
- package/core/server/services/members/MembersConfigProvider.js +0 -15
- package/core/server/services/members/SingleUseTokenProvider.js +8 -8
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/members-api/controllers/RouterController.js +26 -0
- package/core/server/services/members/members-api/repositories/MemberRepository.js +6 -1
- package/core/server/services/members-events/EventStorage.js +10 -0
- package/core/server/services/stats/ReferrersStatsService.js +143 -0
- package/core/server/services/stats/StatsService.js +17 -0
- package/core/server/services/stripe/StripeAPI.js +7 -2
- package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +6 -1
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/members/app.js +2 -0
- package/core/server/web/shared/middleware/api/spam-prevention.js +76 -0
- package/core/server/web/shared/middleware/brute.js +23 -0
- package/core/shared/config/defaults.json +13 -1
- package/core/shared/config/env/config.testing-browser.json +12 -0
- package/core/shared/config/env/config.testing-mysql.json +12 -0
- package/core/shared/config/env/config.testing.json +12 -0
- package/core/shared/labs.js +1 -0
- package/package.json +8 -8
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +288 -292
- package/components/tryghost-i18n-6.1.0.tgz +0 -0
- package/core/built/admin/assets/ghost-791574a9e2efe65c88412947d2e80170.css +0 -1
- package/core/built/admin/assets/ghost-dark-1a7d101d525c0fdcf406ac0abd98540f.css +0 -1
- /package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js.LICENSE.txt → chunk.397.a720333cfffc99c47e71.js.LICENSE.txt} +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Ghost</title>
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%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%226.
|
|
9
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%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%226.3%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%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%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2237bd1e3e4d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%2274f827c663%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%221e670e9cd7%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22c53b194bad%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%2265427ccad3%22%2C%22adminXActivitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D" />
|
|
10
10
|
|
|
11
11
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover" />
|
|
12
12
|
<meta name="pinterest" content="nopin" />
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
</style>
|
|
29
29
|
|
|
30
30
|
<link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
31
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
31
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-8ade80412a20088a4f0a9a1159f0bdba.css" title="light">
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
</head>
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
49
49
|
|
|
50
50
|
<script src="assets/vendor-aed0068cf9b67d042dd23a6343545b7b.js"></script>
|
|
51
|
-
<script src="assets/chunk.397.
|
|
52
|
-
<script src="assets/chunk.524.
|
|
53
|
-
<script src="assets/ghost-
|
|
51
|
+
<script src="assets/chunk.397.a720333cfffc99c47e71.js"></script>
|
|
52
|
+
<script src="assets/chunk.524.aac61953956de04feb53.js"></script>
|
|
53
|
+
<script src="assets/ghost-1bfab97cb7f550726e894fae6650a808.js"></script>
|
|
54
54
|
</body>
|
|
55
55
|
</html>
|
|
@@ -64,7 +64,8 @@ function getMembersHelper(data, frontendKey, excludeList) {
|
|
|
64
64
|
key: frontendKey,
|
|
65
65
|
api: urlUtils.urlFor('api', {type: 'content'}, true),
|
|
66
66
|
locale: settingsCache.get('locale') || 'en',
|
|
67
|
-
'members-signin-otc': labs.isSet('membersSigninOTC') // html.dataset converts dash-attrs to camelCase
|
|
67
|
+
'members-signin-otc': labs.isSet('membersSigninOTC'), // html.dataset converts dash-attrs to camelCase
|
|
68
|
+
'members-signin-otc-alpha': labs.isSet('membersSigninOTCAlpha') // html.dataset converts dash-attrs to camelCase
|
|
68
69
|
};
|
|
69
70
|
if (colorString) {
|
|
70
71
|
attributes['accent-color'] = colorString;
|
|
@@ -514,7 +514,7 @@ const controller = {
|
|
|
514
514
|
},
|
|
515
515
|
options: [
|
|
516
516
|
'order',
|
|
517
|
-
'limit',
|
|
517
|
+
'limit',
|
|
518
518
|
'date_from',
|
|
519
519
|
'date_to',
|
|
520
520
|
'timezone',
|
|
@@ -541,6 +541,42 @@ const controller = {
|
|
|
541
541
|
async query(frame) {
|
|
542
542
|
return await statsService.api.getTopSourcesWithRange(frame.options);
|
|
543
543
|
}
|
|
544
|
+
},
|
|
545
|
+
utmGrowth: {
|
|
546
|
+
headers: {
|
|
547
|
+
cacheInvalidate: false
|
|
548
|
+
},
|
|
549
|
+
options: [
|
|
550
|
+
'utm_type',
|
|
551
|
+
'order',
|
|
552
|
+
'limit',
|
|
553
|
+
'date_from',
|
|
554
|
+
'date_to',
|
|
555
|
+
'timezone',
|
|
556
|
+
'post_id'
|
|
557
|
+
],
|
|
558
|
+
permissions: {
|
|
559
|
+
docName: 'posts',
|
|
560
|
+
method: 'browse'
|
|
561
|
+
},
|
|
562
|
+
cache: statsService.cache,
|
|
563
|
+
generateCacheKeyData(frame) {
|
|
564
|
+
return {
|
|
565
|
+
method: 'utmGrowth',
|
|
566
|
+
options: {
|
|
567
|
+
utm_type: frame.options.utm_type,
|
|
568
|
+
order: frame.options.order,
|
|
569
|
+
limit: frame.options.limit,
|
|
570
|
+
date_from: frame.options.date_from,
|
|
571
|
+
date_to: frame.options.date_to,
|
|
572
|
+
timezone: frame.options.timezone,
|
|
573
|
+
post_id: frame.options.post_id
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
},
|
|
577
|
+
async query(frame) {
|
|
578
|
+
return await statsService.api.getUtmGrowthStats(frame.options);
|
|
579
|
+
}
|
|
544
580
|
}
|
|
545
581
|
|
|
546
582
|
};
|
|
@@ -31,6 +31,7 @@ const keyTypeMapping = {
|
|
|
31
31
|
members_public_key: 'string',
|
|
32
32
|
members_private_key: 'string',
|
|
33
33
|
members_email_auth_secret: 'string',
|
|
34
|
+
members_otc_secret: 'string',
|
|
34
35
|
default_content_visibility: 'string',
|
|
35
36
|
stripe_secret_key: 'string',
|
|
36
37
|
stripe_publishable_key: 'string',
|
|
@@ -11,7 +11,7 @@ const {createNonTransactionalMigration, createTransactionalMigration} = require(
|
|
|
11
11
|
*
|
|
12
12
|
* @returns {Migration}
|
|
13
13
|
*/
|
|
14
|
-
function createAddColumnMigration(table, column, columnDefinition) {
|
|
14
|
+
function createAddColumnMigration(table, column, columnDefinition, options = {}) {
|
|
15
15
|
return createNonTransactionalMigration(
|
|
16
16
|
// up
|
|
17
17
|
commands.createColumnMigration({
|
|
@@ -20,7 +20,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
|
|
|
20
20
|
dbIsInCorrectState: hasColumn => hasColumn === true,
|
|
21
21
|
operation: commands.addColumn,
|
|
22
22
|
operationVerb: 'Adding',
|
|
23
|
-
columnDefinition
|
|
23
|
+
columnDefinition,
|
|
24
|
+
options
|
|
24
25
|
}),
|
|
25
26
|
// down
|
|
26
27
|
commands.createColumnMigration({
|
|
@@ -29,7 +30,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
|
|
|
29
30
|
dbIsInCorrectState: hasColumn => hasColumn === false,
|
|
30
31
|
operation: commands.dropColumn,
|
|
31
32
|
operationVerb: 'Removing',
|
|
32
|
-
columnDefinition
|
|
33
|
+
columnDefinition,
|
|
34
|
+
options
|
|
33
35
|
})
|
|
34
36
|
);
|
|
35
37
|
}
|
|
@@ -41,7 +43,7 @@ function createAddColumnMigration(table, column, columnDefinition) {
|
|
|
41
43
|
*
|
|
42
44
|
* @returns {Migration}
|
|
43
45
|
*/
|
|
44
|
-
function createDropColumnMigration(table, column, columnDefinition) {
|
|
46
|
+
function createDropColumnMigration(table, column, columnDefinition, options = {}) {
|
|
45
47
|
return createNonTransactionalMigration(
|
|
46
48
|
// up
|
|
47
49
|
commands.createColumnMigration({
|
|
@@ -49,7 +51,9 @@ function createDropColumnMigration(table, column, columnDefinition) {
|
|
|
49
51
|
column,
|
|
50
52
|
dbIsInCorrectState: hasColumn => hasColumn === false,
|
|
51
53
|
operation: commands.dropColumn,
|
|
52
|
-
operationVerb: 'Removing'
|
|
54
|
+
operationVerb: 'Removing',
|
|
55
|
+
columnDefinition,
|
|
56
|
+
options
|
|
53
57
|
}),
|
|
54
58
|
// down
|
|
55
59
|
commands.createColumnMigration({
|
|
@@ -58,7 +62,8 @@ function createDropColumnMigration(table, column, columnDefinition) {
|
|
|
58
62
|
dbIsInCorrectState: hasColumn => hasColumn === true,
|
|
59
63
|
operation: commands.addColumn,
|
|
60
64
|
operationVerb: 'Adding',
|
|
61
|
-
columnDefinition
|
|
65
|
+
columnDefinition,
|
|
66
|
+
options
|
|
62
67
|
})
|
|
63
68
|
);
|
|
64
69
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const {combineNonTransactionalMigrations, createAddColumnMigration} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = combineNonTransactionalMigrations(
|
|
4
|
+
// members_created_events
|
|
5
|
+
createAddColumnMigration('members_created_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
6
|
+
createAddColumnMigration('members_created_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
7
|
+
createAddColumnMigration('members_created_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
8
|
+
createAddColumnMigration('members_created_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
9
|
+
createAddColumnMigration('members_created_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
10
|
+
|
|
11
|
+
// members_subscription_created_events
|
|
12
|
+
createAddColumnMigration('members_subscription_created_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
13
|
+
createAddColumnMigration('members_subscription_created_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
14
|
+
createAddColumnMigration('members_subscription_created_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
15
|
+
createAddColumnMigration('members_subscription_created_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
16
|
+
createAddColumnMigration('members_subscription_created_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
17
|
+
|
|
18
|
+
// donation_payment_events
|
|
19
|
+
createAddColumnMigration('donation_payment_events', 'utm_source', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
20
|
+
createAddColumnMigration('donation_payment_events', 'utm_medium', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
21
|
+
createAddColumnMigration('donation_payment_events', 'utm_campaign', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
22
|
+
createAddColumnMigration('donation_payment_events', 'utm_term', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'}),
|
|
23
|
+
createAddColumnMigration('donation_payment_events', 'utm_content', {type: 'string', maxlength: 191, nullable: true}, {algorithm: 'auto'})
|
|
24
|
+
);
|
|
@@ -96,8 +96,10 @@ function dropNullable(tableName, column, transaction = db.knex) {
|
|
|
96
96
|
* @param {string} column
|
|
97
97
|
* @param {import('knex').Knex.Transaction} [transaction]
|
|
98
98
|
* @param {object} columnSpec
|
|
99
|
+
* @param {object} [options]
|
|
100
|
+
* @param {'inplace'|'copy'|'auto'} [options.algorithm] - MySQL only
|
|
99
101
|
*/
|
|
100
|
-
async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
|
|
102
|
+
async function addColumn(tableName, column, transaction = db.knex, columnSpec, options = {}) {
|
|
101
103
|
const addColumnBuilder = transaction.schema.table(tableName, function (table) {
|
|
102
104
|
addTableColumn(tableName, table, column, columnSpec);
|
|
103
105
|
});
|
|
@@ -114,7 +116,12 @@ async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
|
|
|
114
116
|
|
|
115
117
|
if (DatabaseInfo.isMySQL(transaction)) {
|
|
116
118
|
// Guard against an ending semicolon
|
|
117
|
-
sql = sql.replace(/;\s*$/, '')
|
|
119
|
+
sql = sql.replace(/;\s*$/, '');
|
|
120
|
+
if (options?.algorithm !== 'auto') {
|
|
121
|
+
// default to copy if not specified
|
|
122
|
+
const algorithm = options?.algorithm || 'copy';
|
|
123
|
+
sql += `, algorithm=${algorithm}`;
|
|
124
|
+
}
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
await transaction.raw(sql);
|
|
@@ -126,8 +133,10 @@ async function addColumn(tableName, column, transaction = db.knex, columnSpec) {
|
|
|
126
133
|
* @param {string} column
|
|
127
134
|
* @param {import('knex').Knex} [transaction]
|
|
128
135
|
* @param {object} [columnSpec]
|
|
136
|
+
* @param {object} [options]
|
|
137
|
+
* @param {'inplace'|'copy'|'auto'} [options.algorithm] - MySQL only
|
|
129
138
|
*/
|
|
130
|
-
async function dropColumn(tableName, column, transaction = db.knex, columnSpec = {}) {
|
|
139
|
+
async function dropColumn(tableName, column, transaction = db.knex, columnSpec = {}, options = {}) {
|
|
131
140
|
if (Object.prototype.hasOwnProperty.call(columnSpec, 'references')) {
|
|
132
141
|
const [toTable, toColumn] = columnSpec.references.split('.');
|
|
133
142
|
await dropForeign({fromTable: tableName, fromColumn: column, toTable, toColumn, constraintName: columnSpec.constraintName, transaction});
|
|
@@ -149,7 +158,12 @@ async function dropColumn(tableName, column, transaction = db.knex, columnSpec =
|
|
|
149
158
|
|
|
150
159
|
if (DatabaseInfo.isMySQL(transaction)) {
|
|
151
160
|
// Guard against an ending semicolon
|
|
152
|
-
sql = sql.replace(/;\s*$/, '')
|
|
161
|
+
sql = sql.replace(/;\s*$/, '');
|
|
162
|
+
if (options?.algorithm !== 'auto') {
|
|
163
|
+
// default to copy if not specified
|
|
164
|
+
const algorithm = options?.algorithm || 'copy';
|
|
165
|
+
sql += `, algorithm=${algorithm}`;
|
|
166
|
+
}
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
await transaction.raw(sql);
|
|
@@ -561,7 +575,8 @@ function createColumnMigration(...migrations) {
|
|
|
561
575
|
dbIsInCorrectState,
|
|
562
576
|
operation,
|
|
563
577
|
operationVerb,
|
|
564
|
-
columnDefinition
|
|
578
|
+
columnDefinition,
|
|
579
|
+
options
|
|
565
580
|
} = migration;
|
|
566
581
|
|
|
567
582
|
const hasColumn = await conn.schema.hasColumn(table, column);
|
|
@@ -571,7 +586,7 @@ function createColumnMigration(...migrations) {
|
|
|
571
586
|
logging.warn(`${operationVerb} ${table}.${column} column - skipping as table is correct`);
|
|
572
587
|
} else {
|
|
573
588
|
logging.info(`${operationVerb} ${table}.${column} column`);
|
|
574
|
-
await operation(table, column, conn, columnDefinition);
|
|
589
|
+
await operation(table, column, conn, columnDefinition, options);
|
|
575
590
|
}
|
|
576
591
|
}
|
|
577
592
|
|
|
@@ -525,6 +525,7 @@ module.exports = {
|
|
|
525
525
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
526
526
|
created_at: {type: 'dateTime', nullable: false},
|
|
527
527
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
528
|
+
// attribution values from ghost-history (member attribution tracking script)
|
|
528
529
|
attribution_id: {type: 'string', maxlength: 24, nullable: true, index: true},
|
|
529
530
|
attribution_type: {
|
|
530
531
|
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
@@ -532,9 +533,16 @@ module.exports = {
|
|
|
532
533
|
}
|
|
533
534
|
},
|
|
534
535
|
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
536
|
+
// referrer values from browser, processed by our referrerParser library
|
|
535
537
|
referrer_source: {type: 'string', maxlength: 191, nullable: true},
|
|
536
538
|
referrer_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
537
539
|
referrer_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
540
|
+
// raw values from URL query parameters
|
|
541
|
+
utm_source: {type: 'string', maxlength: 191, nullable: true},
|
|
542
|
+
utm_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
543
|
+
utm_campaign: {type: 'string', maxlength: 191, nullable: true},
|
|
544
|
+
utm_term: {type: 'string', maxlength: 191, nullable: true},
|
|
545
|
+
utm_content: {type: 'string', maxlength: 191, nullable: true},
|
|
538
546
|
source: {
|
|
539
547
|
type: 'string', maxlength: 50, nullable: false, validations: {
|
|
540
548
|
isIn: [['member', 'import', 'system', 'api', 'admin']]
|
|
@@ -705,6 +713,7 @@ module.exports = {
|
|
|
705
713
|
created_at: {type: 'dateTime', nullable: false},
|
|
706
714
|
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
|
|
707
715
|
subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true},
|
|
716
|
+
// attribution values from ghost-history (member attribution tracking script)
|
|
708
717
|
attribution_id: {type: 'string', maxlength: 24, nullable: true, index: true},
|
|
709
718
|
attribution_type: {
|
|
710
719
|
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
@@ -712,9 +721,16 @@ module.exports = {
|
|
|
712
721
|
}
|
|
713
722
|
},
|
|
714
723
|
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
724
|
+
// referrer values from browser, processed by our referrerParser library
|
|
715
725
|
referrer_source: {type: 'string', maxlength: 191, nullable: true},
|
|
716
726
|
referrer_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
717
727
|
referrer_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
728
|
+
// raw values from URL query parameters
|
|
729
|
+
utm_source: {type: 'string', maxlength: 191, nullable: true},
|
|
730
|
+
utm_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
731
|
+
utm_campaign: {type: 'string', maxlength: 191, nullable: true},
|
|
732
|
+
utm_term: {type: 'string', maxlength: 191, nullable: true},
|
|
733
|
+
utm_content: {type: 'string', maxlength: 191, nullable: true},
|
|
718
734
|
batch_id: {type: 'string', maxlength: 24, nullable: true}
|
|
719
735
|
},
|
|
720
736
|
offer_redemptions: {
|
|
@@ -746,6 +762,7 @@ module.exports = {
|
|
|
746
762
|
member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
|
|
747
763
|
amount: {type: 'integer', nullable: false},
|
|
748
764
|
currency: {type: 'string', maxlength: 50, nullable: false},
|
|
765
|
+
// attribution values from ghost-history (member attribution tracking script)
|
|
749
766
|
attribution_id: {type: 'string', maxlength: 24, nullable: true},
|
|
750
767
|
attribution_type: {
|
|
751
768
|
type: 'string', maxlength: 50, nullable: true, validations: {
|
|
@@ -753,9 +770,16 @@ module.exports = {
|
|
|
753
770
|
}
|
|
754
771
|
},
|
|
755
772
|
attribution_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
773
|
+
// referrer values from browser, processed by our referrerParser library
|
|
756
774
|
referrer_source: {type: 'string', maxlength: 191, nullable: true},
|
|
757
775
|
referrer_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
758
776
|
referrer_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
777
|
+
// raw values from URL query parameters
|
|
778
|
+
utm_source: {type: 'string', maxlength: 191, nullable: true},
|
|
779
|
+
utm_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
780
|
+
utm_campaign: {type: 'string', maxlength: 191, nullable: true},
|
|
781
|
+
utm_term: {type: 'string', maxlength: 191, nullable: true},
|
|
782
|
+
utm_content: {type: 'string', maxlength: 191, nullable: true},
|
|
759
783
|
created_at: {type: 'dateTime', nullable: false},
|
|
760
784
|
donation_message: {type: 'string', maxlength: 255, nullable: true} // https://docs.stripe.com/payments/checkout/custom-fields
|
|
761
785
|
},
|
|
@@ -57,6 +57,7 @@ function parseDefaultSettings() {
|
|
|
57
57
|
members_public_key: () => getMembersKey('public'),
|
|
58
58
|
members_private_key: () => getMembersKey('private'),
|
|
59
59
|
members_email_auth_secret: () => crypto.randomBytes(64).toString('hex'),
|
|
60
|
+
members_otc_secret: () => crypto.randomBytes(64).toString('hex'),
|
|
60
61
|
ghost_public_key: () => getGhostKey('public'),
|
|
61
62
|
ghost_private_key: () => getGhostKey('private'),
|
|
62
63
|
site_uuid: () => getOrGenerateSiteUuid()
|
|
@@ -19,7 +19,12 @@ class DonationBookshelfRepository {
|
|
|
19
19
|
attribution_type: event.attributionType,
|
|
20
20
|
referrer_source: event.referrerSource,
|
|
21
21
|
referrer_medium: event.referrerMedium,
|
|
22
|
-
referrer_url: event.referrerUrl
|
|
22
|
+
referrer_url: event.referrerUrl,
|
|
23
|
+
utm_source: event.utmSource,
|
|
24
|
+
utm_medium: event.utmMedium,
|
|
25
|
+
utm_campaign: event.utmCampaign,
|
|
26
|
+
utm_term: event.utmTerm,
|
|
27
|
+
utm_content: event.utmContent
|
|
23
28
|
});
|
|
24
29
|
}
|
|
25
30
|
}
|
|
@@ -23,6 +23,11 @@ type DonationEventModelInstance = BookshelfModelInstance & {
|
|
|
23
23
|
referrer_source: string | null;
|
|
24
24
|
referrer_medium: string | null;
|
|
25
25
|
referrer_url: string | null;
|
|
26
|
+
utm_source: string | null;
|
|
27
|
+
utm_medium: string | null;
|
|
28
|
+
utm_campaign: string | null;
|
|
29
|
+
utm_term: string | null;
|
|
30
|
+
utm_content: string | null;
|
|
26
31
|
}
|
|
27
32
|
type DonationPaymentEventBookshelfModel = BookshelfModel<DonationEventModelInstance>;
|
|
28
33
|
|
|
@@ -47,7 +52,12 @@ export class DonationBookshelfRepository implements DonationRepository {
|
|
|
47
52
|
attribution_type: event.attributionType,
|
|
48
53
|
referrer_source: event.referrerSource,
|
|
49
54
|
referrer_medium: event.referrerMedium,
|
|
50
|
-
referrer_url: event.referrerUrl
|
|
55
|
+
referrer_url: event.referrerUrl,
|
|
56
|
+
utm_source: event.utmSource,
|
|
57
|
+
utm_medium: event.utmMedium,
|
|
58
|
+
utm_campaign: event.utmCampaign,
|
|
59
|
+
utm_term: event.utmTerm,
|
|
60
|
+
utm_content: event.utmContent
|
|
51
61
|
});
|
|
52
62
|
}
|
|
53
63
|
}
|
|
@@ -15,6 +15,11 @@ class DonationPaymentEvent {
|
|
|
15
15
|
referrerSource;
|
|
16
16
|
referrerMedium;
|
|
17
17
|
referrerUrl;
|
|
18
|
+
utmSource;
|
|
19
|
+
utmMedium;
|
|
20
|
+
utmCampaign;
|
|
21
|
+
utmTerm;
|
|
22
|
+
utmContent;
|
|
18
23
|
constructor(data, timestamp) {
|
|
19
24
|
this.timestamp = timestamp;
|
|
20
25
|
this.name = data.name;
|
|
@@ -29,6 +34,11 @@ class DonationPaymentEvent {
|
|
|
29
34
|
this.referrerSource = data.referrerSource;
|
|
30
35
|
this.referrerMedium = data.referrerMedium;
|
|
31
36
|
this.referrerUrl = data.referrerUrl;
|
|
37
|
+
this.utmSource = data.utmSource;
|
|
38
|
+
this.utmMedium = data.utmMedium;
|
|
39
|
+
this.utmCampaign = data.utmCampaign;
|
|
40
|
+
this.utmTerm = data.utmTerm;
|
|
41
|
+
this.utmContent = data.utmContent;
|
|
32
42
|
}
|
|
33
43
|
static create(data, timestamp) {
|
|
34
44
|
return new DonationPaymentEvent(data, timestamp ?? new Date());
|
|
@@ -13,6 +13,11 @@ export class DonationPaymentEvent {
|
|
|
13
13
|
referrerSource: string | null;
|
|
14
14
|
referrerMedium: string | null;
|
|
15
15
|
referrerUrl: string | null;
|
|
16
|
+
utmSource: string | null;
|
|
17
|
+
utmMedium: string | null;
|
|
18
|
+
utmCampaign: string | null;
|
|
19
|
+
utmTerm: string | null;
|
|
20
|
+
utmContent: string | null;
|
|
16
21
|
|
|
17
22
|
constructor(data: Omit<DonationPaymentEvent, 'timestamp'>, timestamp: Date) {
|
|
18
23
|
this.timestamp = timestamp;
|
|
@@ -30,6 +35,11 @@ export class DonationPaymentEvent {
|
|
|
30
35
|
this.referrerSource = data.referrerSource;
|
|
31
36
|
this.referrerMedium = data.referrerMedium;
|
|
32
37
|
this.referrerUrl = data.referrerUrl;
|
|
38
|
+
this.utmSource = data.utmSource;
|
|
39
|
+
this.utmMedium = data.utmMedium;
|
|
40
|
+
this.utmCampaign = data.utmCampaign;
|
|
41
|
+
this.utmTerm = data.utmTerm;
|
|
42
|
+
this.utmContent = data.utmContent;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
static create(data: Omit<DonationPaymentEvent, 'timestamp'>, timestamp?: Date) {
|
|
@@ -1241,7 +1241,7 @@ class EmailRenderer {
|
|
|
1241
1241
|
}, true) : null
|
|
1242
1242
|
},
|
|
1243
1243
|
preheader: this.#getEmailPreheader(post, segment, html),
|
|
1244
|
-
preheaderSpacing: '&
|
|
1244
|
+
preheaderSpacing: `${' ͏ '.repeat(150)}${'­ '.repeat(200)} `,
|
|
1245
1245
|
html,
|
|
1246
1246
|
|
|
1247
1247
|
post: {
|
|
@@ -67,7 +67,8 @@ module.exports = class MailgunClient {
|
|
|
67
67
|
subject: messageContent.subject,
|
|
68
68
|
html: messageContent.html,
|
|
69
69
|
text: messageContent.plaintext,
|
|
70
|
-
'recipient-variables': JSON.stringify(recipientData)
|
|
70
|
+
'recipient-variables': JSON.stringify(recipientData),
|
|
71
|
+
'h:Sender': message.from
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
// Do we have a custom List-Unsubscribe header set?
|
|
@@ -348,9 +349,9 @@ module.exports = class MailgunClient {
|
|
|
348
349
|
/**
|
|
349
350
|
* Returns the configured target delivery window in seconds
|
|
350
351
|
* Ghost will attempt to deliver emails evenly distributed over this window
|
|
351
|
-
*
|
|
352
|
+
*
|
|
352
353
|
* Defaults to 0 (no delay) if not set
|
|
353
|
-
*
|
|
354
|
+
*
|
|
354
355
|
* @returns {number}
|
|
355
356
|
*/
|
|
356
357
|
getTargetDeliveryWindow() {
|
|
@@ -23,7 +23,7 @@ const messages = {
|
|
|
23
23
|
* @typedef {Object} TokenProvider<T, D>
|
|
24
24
|
* @prop {(data: D) => Promise<T>} create
|
|
25
25
|
* @prop {(token: T, options?: TokenValidateOptions) => Promise<D>} validate
|
|
26
|
-
* @prop {(token: T) => Promise<string | null>} [
|
|
26
|
+
* @prop {(token: T) => Promise<string | null>} [getRefByToken]
|
|
27
27
|
* @prop {(otcRef: string, tokenValue: T) => string} [deriveOTC]
|
|
28
28
|
*/
|
|
29
29
|
|
|
@@ -108,7 +108,7 @@ class MagicLink {
|
|
|
108
108
|
let otcRef = null;
|
|
109
109
|
if (this.labsService?.isSet('membersSigninOTC') && otc) {
|
|
110
110
|
try {
|
|
111
|
-
otcRef = await this.
|
|
111
|
+
otcRef = await this.getRefFromToken(token);
|
|
112
112
|
} catch (err) {
|
|
113
113
|
this.sentry?.captureException?.(err);
|
|
114
114
|
otcRef = null;
|
|
@@ -135,17 +135,17 @@ class MagicLink {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
*
|
|
138
|
+
* getRefFromToken
|
|
139
139
|
*
|
|
140
|
-
* @param {Token} token - The token to get the
|
|
141
|
-
* @returns {Promise<string|null>}
|
|
140
|
+
* @param {Token} token - The token to get the ref from
|
|
141
|
+
* @returns {Promise<string|null>} ref - The ref of the token
|
|
142
142
|
*/
|
|
143
|
-
async
|
|
144
|
-
if (typeof this.tokenProvider.
|
|
143
|
+
async getRefFromToken(token) {
|
|
144
|
+
if (typeof this.tokenProvider.getRefByToken !== 'function') {
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const id = await this.tokenProvider.
|
|
148
|
+
const id = await this.tokenProvider.getRefByToken(token);
|
|
149
149
|
return id;
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -156,7 +156,7 @@ class MagicLink {
|
|
|
156
156
|
* @returns {Promise<string|null>} otc - The otc of the token
|
|
157
157
|
*/
|
|
158
158
|
async getOTCFromToken(token) {
|
|
159
|
-
const tokenId = await this.
|
|
159
|
+
const tokenId = await this.getRefFromToken(token);
|
|
160
160
|
|
|
161
161
|
if (!tokenId || typeof this.tokenProvider.deriveOTC !== 'function') {
|
|
162
162
|
return null;
|