ghost 6.0.10 → 6.2.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.2.0.tgz +0 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +1 -1
- package/core/built/admin/assets/admin-x-activitypub/{index-wBqnq7A5.mjs → index-DmCoswaX.mjs} +2 -2
- package/core/built/admin/assets/admin-x-activitypub/{index-XhNX0QuF.mjs → index-lT95Q15h.mjs} +8212 -8183
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-BDBDWpWl.mjs → CodeEditorView-UxqLGRTu.mjs} +3 -3
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- 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-DsbJfrQ7.mjs → index-B5r0jdJS.mjs} +95 -95
- package/core/built/admin/assets/admin-x-settings/{index-BB7hgOf0.mjs → index-Co907MFn.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-CCpr5VWU.mjs → modals-B7j9sxR4.mjs} +8799 -8807
- package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js → chunk.397.d5e25bb9baf088f52499.js} +2 -2
- package/core/built/admin/assets/{chunk.524.695215c994f8cbf547d3.js → chunk.524.70595796c7b8c6003a2d.js} +7 -7
- package/core/built/admin/assets/{chunk.582.a949b80543caba37906c.js → chunk.582.d9b970b71da671ac1b7b.js} +8 -8
- package/core/built/admin/assets/{ghost-9c608430440a10746540adb7d2cd0f31.js → ghost-2066304fd0b166e1c16d397dd73ef7b2.js} +39 -35
- package/core/built/admin/assets/ghost-49475952d56ffe89bd47ab9d9c64ada8.css +1 -0
- package/core/built/admin/assets/ghost-dark-27877727751b91f03261d449d74e33b9.css +1 -0
- package/core/built/admin/assets/posts/posts.js +27400 -27361
- package/core/built/admin/assets/stats/stats.js +28701 -28674
- package/core/built/admin/index.html +5 -5
- package/core/frontend/public/member-attribution.min.js +1 -1
- package/core/server/api/endpoints/search-index.js +2 -2
- package/core/server/api/endpoints/stats.js +10 -4
- package/core/server/data/migrations/utils/schema.js +11 -6
- package/core/server/data/migrations/versions/6.1/2025-09-11-00-38-13-add-uuid-column-to-tokens.js +8 -0
- package/core/server/data/migrations/versions/6.1/2025-09-11-00-39-08-backfill-tokens-uuid.js +19 -0
- package/core/server/data/migrations/versions/6.1/2025-09-11-00-39-36-tokens-drop-nullable-uuid.js +4 -0
- package/core/server/data/migrations/versions/6.2/2025-09-30-14-28-09-add-utm-fields.js +24 -0
- package/core/server/data/schema/commands.js +21 -6
- package/core/server/data/schema/schema.js +25 -0
- package/core/server/data/tinybird/datasources/_mv_hits.datasource +7 -4
- package/core/server/data/tinybird/endpoints/api_top_utm_campaigns.pipe +2 -8
- package/core/server/data/tinybird/endpoints/api_top_utm_contents.pipe +2 -8
- package/core/server/data/tinybird/endpoints/api_top_utm_mediums.pipe +2 -8
- package/core/server/data/tinybird/endpoints/api_top_utm_sources.pipe +2 -8
- package/core/server/data/tinybird/endpoints/api_top_utm_terms.pipe +2 -8
- package/core/server/data/tinybird/fixtures/analytics_events.ndjson +11 -11
- package/core/server/data/tinybird/pipes/mv_hits.pipe +12 -2
- package/core/server/data/tinybird/pipes/mv_session_data.pipe +16 -6
- package/core/server/data/tinybird/tests/api_top_utm_campaigns.yaml +35 -34
- package/core/server/data/tinybird/tests/api_top_utm_contents.yaml +57 -48
- package/core/server/data/tinybird/tests/api_top_utm_mediums.yaml +40 -38
- package/core/server/data/tinybird/tests/api_top_utm_sources.yaml +59 -39
- package/core/server/data/tinybird/tests/api_top_utm_terms.yaml +55 -48
- package/core/server/models/single-use-token.js +1 -0
- package/core/server/services/email-service/EmailRenderer.js +1 -0
- package/core/server/services/email-service/email-templates/template.hbs +6 -0
- 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/members/MembersConfigProvider.js +0 -15
- package/core/server/services/members/SingleUseTokenProvider.js +8 -8
- package/core/server/services/members/emails/signin.js +4 -4
- package/core/server/services/stats/MrrStatsService.js +10 -5
- package/core/server/services/stats/StatsService.js +2 -2
- package/core/shared/config/defaults.json +1 -1
- package/package.json +9 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +1076 -495
- package/components/tryghost-i18n-6.0.10.tgz +0 -0
- package/core/built/admin/assets/ghost-a7a53bf80dc45c37ae9c174a0d02a882.css +0 -1
- package/core/built/admin/assets/ghost-dark-6e0062029f988d8676e87f22d8e7f4a3.css +0 -1
- /package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js.LICENSE.txt → chunk.397.d5e25bb9baf088f52499.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.2%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%2213355f26c2%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22d668621a75%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22cf5b4df358%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%22f5dea8dc06%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-49475952d56ffe89bd47ab9d9c64ada8.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.d5e25bb9baf088f52499.js"></script>
|
|
52
|
+
<script src="assets/chunk.524.70595796c7b8c6003a2d.js"></script>
|
|
53
|
+
<script src="assets/ghost-2066304fd0b166e1c16d397dd73ef7b2.js"></script>
|
|
54
54
|
</body>
|
|
55
55
|
</html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{var m=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var M=(e,r)=>()=>(e&&(r=e(e=0)),r);var _=(e,r)=>{for(var u in r)m(e,u,{get:r[u],enumerable:!0})},P=(e,r,u,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of S(r))!R.call(e,o)&&o!==u&&m(e,o,{get:()=>r[o],enumerable:!(t=y(r,o))||t.enumerable});return e};var T=e=>P(m({},"__esModule",{value:!0}),e);var g={};_(g,{getReferrer:()=>A,parseReferrerData:()=>h});function U(e){let r=e.get("ref"),u=e.get("source"),t=e.get("utm_source"),o=e.get("utm_medium"),i=e.get("utm_term"),s=e.get("utm_campaign"),c=e.get("utm_content");return{source:r||u||t||null,medium:o||null,url:window.document.referrer||null,utmSource:t||null,utmMedium:o||null,utmTerm:i||null,utmCampaign:s||null,utmContent:c||null}}function h(e){let r=new URL(e||window.location.href),u=r.searchParams;return r.hash&&r.hash.includes("#/portal")&&(u=new URL(r.href.replace("/#/portal","")).searchParams),U(u)}function C(e){let{source:r,medium:u,url:t}=e,o=r||u||t||null;if(o)try{let i=new URL(o).hostname,s=window.location.hostname;if(i===s)return null}catch{return o}return o}function A(e){let r=h(e);return C(r)}var d=M(()=>{"use strict"});var b=(d(),T(g)),D=b.parseReferrerData,E=b.getReferrer,p="ghost-history",I=
|
|
1
|
+
"use strict";(()=>{var m=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var M=(e,r)=>()=>(e&&(r=e(e=0)),r);var _=(e,r)=>{for(var u in r)m(e,u,{get:r[u],enumerable:!0})},P=(e,r,u,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of S(r))!R.call(e,o)&&o!==u&&m(e,o,{get:()=>r[o],enumerable:!(t=y(r,o))||t.enumerable});return e};var T=e=>P(m({},"__esModule",{value:!0}),e);var g={};_(g,{getReferrer:()=>A,parseReferrerData:()=>h});function U(e){let r=e.get("ref"),u=e.get("source"),t=e.get("utm_source"),o=e.get("utm_medium"),i=e.get("utm_term"),s=e.get("utm_campaign"),c=e.get("utm_content");return{source:r||u||t||null,medium:o||null,url:window.document.referrer||null,utmSource:t||null,utmMedium:o||null,utmTerm:i||null,utmCampaign:s||null,utmContent:c||null}}function h(e){let r=new URL(e||window.location.href),u=r.searchParams;return r.hash&&r.hash.includes("#/portal")&&(u=new URL(r.href.replace("/#/portal","")).searchParams),U(u)}function C(e){let{source:r,medium:u,url:t}=e,o=r||u||t||null;if(o)try{let i=new URL(o).hostname,s=window.location.hostname;if(i===s)return null}catch{return o}return o}function A(e){let r=h(e);return C(r)}var d=M(()=>{"use strict"});var b=(d(),T(g)),D=b.parseReferrerData,E=b.getReferrer,p="ghost-history",I=1440*60*1e3,w=15;(async function(){try{let e=window.sessionStorage,r=e.getItem(p),u=new Date().getTime(),t=[];if(r)try{t=JSON.parse(r)}catch(n){console.warn("[Member Attribution] Error while parsing history",n)}let o=t.findIndex(n=>{if(!n.time||typeof n.time!="number")return!1;let a=u-n.time;return!(isNaN(n.time)||a>I)});o>0?t.splice(0,o):o===-1&&(t=[]);let i;try{i=D(window.location.href)}catch(n){console.error("[Member Attribution] Parsing referrer failed",n),i={source:null,medium:null,url:null}}let s={referrerSource:i.source,referrerMedium:i.medium,utmSource:i.utmSource,utmMedium:i.utmMedium,utmCampaign:i.utmCampaign,utmTerm:i.utmTerm,utmContent:i.utmContent},c;try{c=E(window.location.href),!c&&i.url&&(c=i.url)}catch(n){console.error("[Member Attribution] Getting final referrer failed",n),c=i.url}try{let n=new URL(window.location.href),a=n.searchParams;a.get("attribution_id")&&a.get("attribution_type")&&(t.push({time:u,id:a.get("attribution_id"),type:a.get("attribution_type"),...s,referrerUrl:c}),a.delete("attribution_id"),a.delete("attribution_type"),n.search="?"+a.toString(),window.history.replaceState({},"",`${n.pathname}${n.search}${n.hash}`))}catch(n){console.error("[Member Attribution] Parsing attribution from querystring failed",n)}let l=window.location.pathname;if(t.length===0||t[t.length-1].path!==l)t.push({path:l,time:u,...s,referrerUrl:c});else if(t.length>0){let n=t[t.length-1];n.time=u,Object.entries(s).forEach(([a,f])=>{f&&(n[a]=f)}),c&&(n.referrerUrl=c)}t.length>w&&(t=t.slice(-w)),e.setItem(p,JSON.stringify(t))}catch(e){console.error("[Member Attribution] Failed with error",e)}})();})();
|
|
@@ -15,7 +15,7 @@ const controller = {
|
|
|
15
15
|
},
|
|
16
16
|
query() {
|
|
17
17
|
const options = {
|
|
18
|
-
filter: 'type:post',
|
|
18
|
+
filter: 'type:post+status:[draft,published,scheduled,sent]',
|
|
19
19
|
limit: '10000',
|
|
20
20
|
order: 'updated_at DESC',
|
|
21
21
|
columns: ['id', 'url', 'title', 'status', 'published_at', 'visibility']
|
|
@@ -34,7 +34,7 @@ const controller = {
|
|
|
34
34
|
},
|
|
35
35
|
query() {
|
|
36
36
|
const options = {
|
|
37
|
-
filter: 'type:page',
|
|
37
|
+
filter: 'type:page+status:[draft,published,scheduled]',
|
|
38
38
|
limit: '10000',
|
|
39
39
|
order: 'updated_at DESC',
|
|
40
40
|
columns: ['id', 'url', 'title', 'status', 'published_at', 'visibility']
|
|
@@ -35,14 +35,20 @@ const controller = {
|
|
|
35
35
|
docName: 'members',
|
|
36
36
|
method: 'browse'
|
|
37
37
|
},
|
|
38
|
+
options: [
|
|
39
|
+
'date_from'
|
|
40
|
+
],
|
|
38
41
|
cache: statsService.cache,
|
|
39
|
-
generateCacheKeyData() {
|
|
42
|
+
generateCacheKeyData(frame) {
|
|
40
43
|
return {
|
|
41
|
-
method: 'mrr'
|
|
44
|
+
method: 'mrr',
|
|
45
|
+
options: frame.options
|
|
42
46
|
};
|
|
43
47
|
},
|
|
44
|
-
async query() {
|
|
45
|
-
return await statsService.api.getMRRHistory(
|
|
48
|
+
async query(frame) {
|
|
49
|
+
return await statsService.api.getMRRHistory({
|
|
50
|
+
dateFrom: frame?.options?.date_from
|
|
51
|
+
});
|
|
46
52
|
}
|
|
47
53
|
},
|
|
48
54
|
subscriptions: {
|
|
@@ -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,19 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
5
|
+
|
|
6
|
+
module.exports = createTransactionalMigration(
|
|
7
|
+
async function up(knex) {
|
|
8
|
+
const tokensWithoutUUID = await knex.select('id').from('tokens').whereNull('uuid');
|
|
9
|
+
|
|
10
|
+
logging.info(`Adding uuid field value to ${tokensWithoutUUID.length} tokens.`);
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
13
|
+
for (const token of tokensWithoutUUID) {
|
|
14
|
+
await knex('tokens').update('uuid', crypto.randomUUID()).where('id', token.id);
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
// down is a no-op
|
|
18
|
+
async function down() {}
|
|
19
|
+
);
|
|
@@ -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
|
},
|
|
@@ -896,6 +920,7 @@ module.exports = {
|
|
|
896
920
|
tokens: {
|
|
897
921
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
898
922
|
token: {type: 'string', maxlength: 32, nullable: false, index: true},
|
|
923
|
+
uuid: {type: 'string', maxlength: 36, nullable: false, unique: true, validations: {isUUID: true}},
|
|
899
924
|
data: {type: 'string', maxlength: 2000, nullable: true},
|
|
900
925
|
created_at: {type: 'dateTime', nullable: false},
|
|
901
926
|
updated_at: {type: 'dateTime', nullable: true},
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
SCHEMA >
|
|
4
2
|
`site_uuid` LowCardinality(String),
|
|
5
3
|
`timestamp` DateTime,
|
|
@@ -16,8 +14,13 @@ SCHEMA >
|
|
|
16
14
|
`href` String,
|
|
17
15
|
`device` String,
|
|
18
16
|
`os` String,
|
|
19
|
-
`browser` String
|
|
17
|
+
`browser` String,
|
|
18
|
+
`utm_source` String,
|
|
19
|
+
`utm_medium` String,
|
|
20
|
+
`utm_campaign` String,
|
|
21
|
+
`utm_term` String,
|
|
22
|
+
`utm_content` String
|
|
20
23
|
|
|
21
24
|
ENGINE "MergeTree"
|
|
22
25
|
ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"
|
|
23
|
-
ENGINE_SORTING_KEY "site_uuid, timestamp, session_id"
|
|
26
|
+
ENGINE_SORTING_KEY "site_uuid, timestamp, session_id"
|
|
@@ -4,20 +4,14 @@ NODE top_utm_campaigns
|
|
|
4
4
|
SQL >
|
|
5
5
|
%
|
|
6
6
|
select
|
|
7
|
-
|
|
8
|
-
when length(source) % 6 = 0 then 'summer_sale_2024'
|
|
9
|
-
when length(source) % 6 = 1 then 'newsletter_weekly'
|
|
10
|
-
when length(source) % 6 = 2 then 'product_launch'
|
|
11
|
-
when length(source) % 6 = 3 then 'holiday_promo'
|
|
12
|
-
when length(source) % 6 = 4 then 'brand_awareness'
|
|
13
|
-
when length(source) % 6 = 5 then 'retention_q4'
|
|
14
|
-
end as utm_campaign,
|
|
7
|
+
utm_campaign,
|
|
15
8
|
count() as visits
|
|
16
9
|
from mv_session_data sd
|
|
17
10
|
inner join filtered_sessions fs
|
|
18
11
|
on fs.session_id = sd.session_id
|
|
19
12
|
where
|
|
20
13
|
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
14
|
+
and utm_campaign != ''
|
|
21
15
|
{% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
|
|
22
16
|
and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
|
|
23
17
|
{% else %}
|
|
@@ -4,20 +4,14 @@ NODE top_utm_content
|
|
|
4
4
|
SQL >
|
|
5
5
|
%
|
|
6
6
|
select
|
|
7
|
-
|
|
8
|
-
when length(source) % 6 = 0 then 'hero-banner'
|
|
9
|
-
when length(source) % 6 = 1 then 'sidebar-cta'
|
|
10
|
-
when length(source) % 6 = 2 then 'footer-link'
|
|
11
|
-
when length(source) % 6 = 3 then 'button-primary'
|
|
12
|
-
when length(source) % 6 = 4 then 'text-link'
|
|
13
|
-
when length(source) % 6 = 5 then 'nav-menu'
|
|
14
|
-
end as utm_content,
|
|
7
|
+
utm_content,
|
|
15
8
|
count() as visits
|
|
16
9
|
from mv_session_data sd
|
|
17
10
|
inner join filtered_sessions fs
|
|
18
11
|
on fs.session_id = sd.session_id
|
|
19
12
|
where
|
|
20
13
|
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
14
|
+
and utm_content != ''
|
|
21
15
|
{% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
|
|
22
16
|
and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
|
|
23
17
|
{% else %}
|
|
@@ -4,20 +4,14 @@ NODE top_utm_mediums
|
|
|
4
4
|
SQL >
|
|
5
5
|
%
|
|
6
6
|
select
|
|
7
|
-
|
|
8
|
-
when length(source) % 6 = 0 then 'email'
|
|
9
|
-
when length(source) % 6 = 1 then 'social'
|
|
10
|
-
when length(source) % 6 = 2 then 'cpc'
|
|
11
|
-
when length(source) % 6 = 3 then 'organic'
|
|
12
|
-
when length(source) % 6 = 4 then 'referral'
|
|
13
|
-
when length(source) % 6 = 5 then 'display'
|
|
14
|
-
end as utm_medium,
|
|
7
|
+
utm_medium,
|
|
15
8
|
count() as visits
|
|
16
9
|
from mv_session_data sd
|
|
17
10
|
inner join filtered_sessions fs
|
|
18
11
|
on fs.session_id = sd.session_id
|
|
19
12
|
where
|
|
20
13
|
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
14
|
+
and utm_medium != ''
|
|
21
15
|
{% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
|
|
22
16
|
and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
|
|
23
17
|
{% else %}
|
|
@@ -4,20 +4,14 @@ NODE top_utm_sources
|
|
|
4
4
|
SQL >
|
|
5
5
|
%
|
|
6
6
|
select
|
|
7
|
-
|
|
8
|
-
when length(source) % 6 = 0 then 'google'
|
|
9
|
-
when length(source) % 6 = 1 then 'facebook'
|
|
10
|
-
when length(source) % 6 = 2 then 'twitter'
|
|
11
|
-
when length(source) % 6 = 3 then 'linkedin'
|
|
12
|
-
when length(source) % 6 = 4 then 'newsletter'
|
|
13
|
-
when length(source) % 6 = 5 then 'instagram'
|
|
14
|
-
end as utm_source,
|
|
7
|
+
utm_source,
|
|
15
8
|
count() as visits
|
|
16
9
|
from mv_session_data sd
|
|
17
10
|
inner join filtered_sessions fs
|
|
18
11
|
on fs.session_id = sd.session_id
|
|
19
12
|
where
|
|
20
13
|
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
14
|
+
and utm_source != ''
|
|
21
15
|
{% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
|
|
22
16
|
and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
|
|
23
17
|
{% else %}
|
|
@@ -4,20 +4,14 @@ NODE top_utm_terms
|
|
|
4
4
|
SQL >
|
|
5
5
|
%
|
|
6
6
|
select
|
|
7
|
-
|
|
8
|
-
when length(source) % 6 = 0 then 'ghost cms'
|
|
9
|
-
when length(source) % 6 = 1 then 'headless publishing'
|
|
10
|
-
when length(source) % 6 = 2 then 'content management'
|
|
11
|
-
when length(source) % 6 = 3 then 'newsletter platform'
|
|
12
|
-
when length(source) % 6 = 4 then 'blog software'
|
|
13
|
-
when length(source) % 6 = 5 then 'membership site'
|
|
14
|
-
end as utm_term,
|
|
7
|
+
utm_term,
|
|
15
8
|
count() as visits
|
|
16
9
|
from mv_session_data sd
|
|
17
10
|
inner join filtered_sessions fs
|
|
18
11
|
on fs.session_id = sd.session_id
|
|
19
12
|
where
|
|
20
13
|
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
14
|
+
and utm_term != ''
|
|
21
15
|
{% if defined(date_from) and day_diff(date_from, date_to) == 0 %}
|
|
22
16
|
and toDate(toTimezone(first_pageview, {{String(timezone, 'Etc/UTC', description="Site timezone", required=True)}})) = {{ Date(date_from) }}
|
|
23
17
|
{% else %}
|