ghost 6.1.0 → 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.1.0.tgz → tryghost-i18n-6.2.0.tgz} +0 -0
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-Bu9qXr9c.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-qEdfz2hd.mjs → index-B5r0jdJS.mjs} +5 -5
- package/core/built/admin/assets/admin-x-settings/{index-BEpRBH9g.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-BtQORnS4.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.2aa0847042f20c9a2a00.js → chunk.524.70595796c7b8c6003a2d.js} +5 -5
- package/core/built/admin/assets/{chunk.582.9182c19afab95991771e.js → chunk.582.d9b970b71da671ac1b7b.js} +8 -8
- package/core/built/admin/assets/{ghost-9c47d152972b304cab0fb982dc3fccc1.js → ghost-2066304fd0b166e1c16d397dd73ef7b2.js} +4 -4
- package/core/built/admin/assets/{ghost-791574a9e2efe65c88412947d2e80170.css → ghost-49475952d56ffe89bd47ab9d9c64ada8.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-1a7d101d525c0fdcf406ac0abd98540f.css → ghost-dark-27877727751b91f03261d449d74e33b9.css} +1 -1
- package/core/built/admin/assets/posts/posts.js +71 -62
- package/core/built/admin/assets/stats/stats.js +2 -2
- package/core/built/admin/index.html +5 -5
- 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/schema/commands.js +21 -6
- package/core/server/data/schema/schema.js +24 -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/members/MembersConfigProvider.js +0 -15
- package/core/server/services/members/SingleUseTokenProvider.js +8 -8
- package/core/shared/config/defaults.json +1 -1
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +195 -197
- /package/core/built/admin/assets/{chunk.397.e5d027e53a68dff31d76.js.LICENSE.txt → chunk.397.d5e25bb9baf088f52499.js.LICENSE.txt} +0 -0
|
@@ -39837,8 +39837,8 @@ const RY = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx(
|
|
|
39837
39837
|
t === "same" && "text-gray-700 bg-muted"
|
|
39838
39838
|
);
|
|
39839
39839
|
return /* @__PURE__ */ A.jsxs("div", { className: "relative flex flex-col items-start gap-2 lg:flex-row lg:gap-3", children: [
|
|
39840
|
-
/* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", children: e }),
|
|
39841
|
-
t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, children: [
|
|
39840
|
+
/* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", "data-testid": "kpi-card-header-value", children: e }),
|
|
39841
|
+
t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, "data-testid": "kpi-card-header-diff", children: [
|
|
39842
39842
|
/* @__PURE__ */ A.jsx("span", { className: "font-medium leading-none", children: a }),
|
|
39843
39843
|
t === "up" && /* @__PURE__ */ A.jsx(ni, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
|
|
39844
39844
|
t === "down" && /* @__PURE__ */ A.jsx(oi, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
|
|
@@ -119676,12 +119676,12 @@ const p_1 = "https://www.google.com/s2/favicons?domain=ghost.org&sz=64", dp1 = (
|
|
|
119676
119676
|
] });
|
|
119677
119677
|
}, eM2 = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx("section", { className: d1("flex gap-8 flex-col p-4 lg:p-8 size-full grow", t), ...a, children: e }), tM2 = ({ currentTab: e }) => /* @__PURE__ */ A.jsxs(W6, { variant: "inline-nav", children: [
|
|
119678
119678
|
/* @__PURE__ */ A.jsx(W6.Title, { children: "Tags" }),
|
|
119679
|
-
/* @__PURE__ */ A.jsx(W6.Nav, { children: /* @__PURE__ */ A.jsxs(ne1, { defaultValue: e, children: [
|
|
119679
|
+
/* @__PURE__ */ A.jsx(W6.Nav, { children: /* @__PURE__ */ A.jsxs(ne1, { "data-testid": "tags-header-tabs", defaultValue: e, children: [
|
|
119680
119680
|
/* @__PURE__ */ A.jsx(Dr, { value: "public", asChild: !0, children: /* @__PURE__ */ A.jsx(GH, { to: "/tags", children: "Public tags" }) }),
|
|
119681
119681
|
/* @__PURE__ */ A.jsx(Dr, { value: "internal", asChild: !0, children: /* @__PURE__ */ A.jsx(GH, { to: "/tags?type=internal", children: "Internal tags" }) })
|
|
119682
119682
|
] }) }),
|
|
119683
119683
|
/* @__PURE__ */ A.jsx(W6.Actions, { children: /* @__PURE__ */ A.jsx(J0, { asChild: !0, children: /* @__PURE__ */ A.jsx("a", { className: "font-bold", href: "#/tags/new", children: "New tag" }) }) })
|
|
119684
|
-
] }), aM2 = ({ children: e }) => /* @__PURE__ */ A.jsx(k_1, { children: /* @__PURE__ */ A.jsx("div", { className: "grid w-full grow", children: /* @__PURE__ */ A.jsx("div", { className: "flex h-full flex-col", children: e }) }) });
|
|
119684
|
+
] }), aM2 = ({ children: e }) => /* @__PURE__ */ A.jsx(k_1, { children: /* @__PURE__ */ A.jsx("div", { className: "grid w-full grow", children: /* @__PURE__ */ A.jsx("div", { className: "flex h-full flex-col", "data-testid": "tags-page", children: e }) }) });
|
|
119685
119685
|
function A_1(e) {
|
|
119686
119686
|
const a = e instanceof HTMLElement && window.getComputedStyle(e).overflowY, o = a !== "visible" && a !== "hidden";
|
|
119687
119687
|
if (e) {
|
|
@@ -120273,14 +120273,15 @@ function mM2({
|
|
|
120273
120273
|
spaceAfter: d
|
|
120274
120274
|
};
|
|
120275
120275
|
}
|
|
120276
|
-
const gp1 = ({ height: e }) => /* @__PURE__ */ A.jsx("tr", { className: "flex lg:table-row", children: /* @__PURE__ */ A.jsx("td", { className: "flex lg:table-cell", style: { height: e } }) }), MM2 = Q(function(t, a) {
|
|
120276
|
+
const gp1 = ({ height: e }) => /* @__PURE__ */ A.jsx("tr", { "aria-hidden": "true", className: "flex lg:table-row", children: /* @__PURE__ */ A.jsx("td", { className: "flex lg:table-cell", style: { height: e } }) }), MM2 = Q(function(t, a) {
|
|
120277
120277
|
return /* @__PURE__ */ A.jsx(
|
|
120278
120278
|
bl,
|
|
120279
120279
|
{
|
|
120280
120280
|
ref: a,
|
|
120281
120281
|
...t,
|
|
120282
|
+
"aria-hidden": "true",
|
|
120282
120283
|
className: "relative flex flex-col lg:table-row",
|
|
120283
|
-
children: /* @__PURE__ */ A.jsx(A5, { className: "relative z-10 h-24 animate-pulse", children: /* @__PURE__ */ A.jsx("div", { className: "h-full rounded-md bg-muted" }) })
|
|
120284
|
+
children: /* @__PURE__ */ A.jsx(A5, { className: "relative z-10 h-24 animate-pulse", children: /* @__PURE__ */ A.jsx("div", { className: "h-full rounded-md bg-muted", "data-testid": "loading-placeholder" }) })
|
|
120284
120285
|
}
|
|
120285
120286
|
);
|
|
120286
120287
|
});
|
|
@@ -120299,62 +120300,70 @@ function bM2({
|
|
|
120299
120300
|
fetchNextPage: n,
|
|
120300
120301
|
parentRef: r
|
|
120301
120302
|
});
|
|
120302
|
-
return /* @__PURE__ */ A.jsx("div", { ref: r, children: /* @__PURE__ */ A.jsxs(
|
|
120303
|
-
|
|
120304
|
-
|
|
120305
|
-
|
|
120306
|
-
|
|
120307
|
-
|
|
120308
|
-
|
|
120309
|
-
|
|
120310
|
-
|
|
120311
|
-
|
|
120312
|
-
|
|
120313
|
-
|
|
120314
|
-
|
|
120315
|
-
{
|
|
120316
|
-
|
|
120317
|
-
|
|
120318
|
-
|
|
120319
|
-
|
|
120320
|
-
|
|
120321
|
-
|
|
120322
|
-
|
|
120323
|
-
|
|
120324
|
-
|
|
120325
|
-
|
|
120326
|
-
|
|
120327
|
-
|
|
120328
|
-
|
|
120329
|
-
|
|
120330
|
-
|
|
120331
|
-
|
|
120332
|
-
|
|
120333
|
-
|
|
120334
|
-
|
|
120335
|
-
|
|
120336
|
-
|
|
120337
|
-
|
|
120338
|
-
|
|
120339
|
-
|
|
120340
|
-
|
|
120341
|
-
|
|
120342
|
-
|
|
120343
|
-
|
|
120344
|
-
|
|
120345
|
-
|
|
120346
|
-
|
|
120347
|
-
|
|
120348
|
-
|
|
120349
|
-
|
|
120350
|
-
|
|
120351
|
-
|
|
120352
|
-
|
|
120353
|
-
|
|
120354
|
-
|
|
120355
|
-
|
|
120356
|
-
|
|
120357
|
-
|
|
120303
|
+
return /* @__PURE__ */ A.jsx("div", { ref: r, children: /* @__PURE__ */ A.jsxs(
|
|
120304
|
+
pe1,
|
|
120305
|
+
{
|
|
120306
|
+
className: "flex table-fixed flex-col lg:table",
|
|
120307
|
+
"data-testid": "tags-list",
|
|
120308
|
+
children: [
|
|
120309
|
+
/* @__PURE__ */ A.jsx(de1, { className: "hidden lg:!visible lg:!table-header-group", children: /* @__PURE__ */ A.jsxs(bl, { children: [
|
|
120310
|
+
/* @__PURE__ */ A.jsx(R6, { className: "w-auto px-4", children: "Tag" }),
|
|
120311
|
+
/* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "Slug" }),
|
|
120312
|
+
/* @__PURE__ */ A.jsx(R6, { className: "w-1/5 px-4", children: "No. of posts" }),
|
|
120313
|
+
/* @__PURE__ */ A.jsx(R6, { className: "w-20 px-4" })
|
|
120314
|
+
] }) }),
|
|
120315
|
+
/* @__PURE__ */ A.jsxs(fe1, { className: "flex flex-col lg:table-row-group", children: [
|
|
120316
|
+
/* @__PURE__ */ A.jsx(gp1, { height: c }),
|
|
120317
|
+
i.map(({ key: s, virtualItem: p, item: d, props: f }) => {
|
|
120318
|
+
var h, m, M;
|
|
120319
|
+
return p.index > e.length - 1 ? /* @__PURE__ */ A.jsx(MM2, { ...f }, s) : /* @__PURE__ */ A.jsxs(
|
|
120320
|
+
bl,
|
|
120321
|
+
{
|
|
120322
|
+
...f,
|
|
120323
|
+
className: "relative grid w-full grid-cols-[1fr_5rem] items-center gap-x-4 p-2 md:grid-cols-[1fr_auto_5rem] lg:table-row lg:p-0",
|
|
120324
|
+
"data-testid": "tag-list-row",
|
|
120325
|
+
children: [
|
|
120326
|
+
/* @__PURE__ */ A.jsxs(A5, { className: "static col-start-1 col-end-1 row-start-1 row-end-1 flex min-w-0 flex-col p-0 lg:table-cell lg:w-1/2 lg:p-4 xl:w-3/5", children: [
|
|
120327
|
+
/* @__PURE__ */ A.jsx(
|
|
120328
|
+
"a",
|
|
120329
|
+
{
|
|
120330
|
+
className: "block truncate pb-1 text-lg font-medium before:absolute before:inset-0 before:z-10",
|
|
120331
|
+
href: `#/tags/${d.slug}`,
|
|
120332
|
+
children: d.name
|
|
120333
|
+
}
|
|
120334
|
+
),
|
|
120335
|
+
/* @__PURE__ */ A.jsx("span", { className: "block truncate text-muted-foreground", children: d.description })
|
|
120336
|
+
] }),
|
|
120337
|
+
/* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-2 row-end-2 flex p-0 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx("span", { className: "block truncate", children: d.slug }) }),
|
|
120338
|
+
/* @__PURE__ */ A.jsx(A5, { className: "col-start-1 col-end-1 row-start-3 row-end-3 flex p-0 md:col-start-2 md:col-end-2 md:row-start-1 md:row-end-3 lg:table-cell lg:p-4", children: (h = d.count) != null && h.posts ? /* @__PURE__ */ A.jsx(
|
|
120339
|
+
"a",
|
|
120340
|
+
{
|
|
120341
|
+
className: "relative z-10 -m-4 inline-block p-4 hover:underline",
|
|
120342
|
+
href: `#/posts?tag=${d.slug}`,
|
|
120343
|
+
children: `${R2((m = d.count) == null ? void 0 : m.posts)} ${((M = d.count) == null ? void 0 : M.posts) === 1 ? "post" : "posts"}`
|
|
120344
|
+
}
|
|
120345
|
+
) : /* @__PURE__ */ A.jsx("span", { className: "text-muted-foreground", children: "0 posts" }) }),
|
|
120346
|
+
/* @__PURE__ */ A.jsx(A5, { className: "col-start-2 col-end-2 row-start-1 row-end-3 p-0 md:col-start-3 md:col-end-3 lg:table-cell lg:p-4", children: /* @__PURE__ */ A.jsx(
|
|
120347
|
+
J0,
|
|
120348
|
+
{
|
|
120349
|
+
"aria-hidden": "true",
|
|
120350
|
+
className: "w-12",
|
|
120351
|
+
size: "icon",
|
|
120352
|
+
tabIndex: -1,
|
|
120353
|
+
variant: "outline",
|
|
120354
|
+
children: /* @__PURE__ */ A.jsx(Dp, {})
|
|
120355
|
+
}
|
|
120356
|
+
) })
|
|
120357
|
+
]
|
|
120358
|
+
},
|
|
120359
|
+
s
|
|
120360
|
+
);
|
|
120361
|
+
}),
|
|
120362
|
+
/* @__PURE__ */ A.jsx(gp1, { height: l })
|
|
120363
|
+
] })
|
|
120364
|
+
]
|
|
120365
|
+
}
|
|
120366
|
+
) });
|
|
120358
120367
|
}
|
|
120359
120368
|
const zM2 = "TagsResponseType", vM2 = Hf2({
|
|
120360
120369
|
dataType: zM2,
|
|
@@ -96030,8 +96030,8 @@ const EV1 = ({ children: e, className: t, ...a }) => /* @__PURE__ */ A.jsx(
|
|
|
96030
96030
|
t === "same" && "text-gray-700 bg-muted"
|
|
96031
96031
|
);
|
|
96032
96032
|
return /* @__PURE__ */ A.jsxs("div", { className: "relative flex flex-col items-start gap-2 lg:flex-row lg:gap-3", children: [
|
|
96033
|
-
/* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", children: e }),
|
|
96034
|
-
t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, children: [
|
|
96033
|
+
/* @__PURE__ */ A.jsx("div", { className: "text-[2.2rem] font-semibold leading-none tracking-tighter", "data-testid": "kpi-card-header-value", children: e }),
|
|
96034
|
+
t && t !== "hidden" && /* @__PURE__ */ A.jsx(A.Fragment, { children: /* @__PURE__ */ A.jsxs("div", { className: n, "data-testid": "kpi-card-header-diff", children: [
|
|
96035
96035
|
/* @__PURE__ */ A.jsx("span", { className: "font-medium leading-none", children: a }),
|
|
96036
96036
|
t === "up" && /* @__PURE__ */ A.jsx(Fc, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
|
|
96037
96037
|
t === "down" && /* @__PURE__ */ A.jsx(jc, { className: "!size-[12px]", size: 14, strokeWidth: 2 }),
|
|
@@ -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>
|
|
@@ -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
|
},
|
|
@@ -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;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const logging = require('@tryghost/logging');
|
|
2
2
|
const {URL} = require('url');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
3
|
const createKeypair = require('keypair');
|
|
5
4
|
|
|
6
5
|
class MembersConfigProvider {
|
|
@@ -42,20 +41,6 @@ class MembersConfigProvider {
|
|
|
42
41
|
return this._settingsHelpers.isStripeConnected();
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
getAuthSecret() {
|
|
46
|
-
const hexSecret = this._settingsCache.get('members_email_auth_secret');
|
|
47
|
-
if (!hexSecret) {
|
|
48
|
-
logging.warn('Could not find members_email_auth_secret, using dynamically generated secret');
|
|
49
|
-
return crypto.randomBytes(64);
|
|
50
|
-
}
|
|
51
|
-
const secret = Buffer.from(hexSecret, 'hex');
|
|
52
|
-
if (secret.length < 64) {
|
|
53
|
-
logging.warn('members_email_auth_secret not large enough (64 bytes), using dynamically generated secret');
|
|
54
|
-
return crypto.randomBytes(64);
|
|
55
|
-
}
|
|
56
|
-
return secret;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
44
|
getAllowSelfSignup() {
|
|
60
45
|
// Free signups are allowed only if the site subscription is set to "Full-access"
|
|
61
46
|
// It is blocked for "Invite-only", "Paid-members-only" and "None" accesses
|
|
@@ -193,7 +193,7 @@ class SingleUseTokenProvider {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
try {
|
|
196
|
-
const model = await this.model.findOne({
|
|
196
|
+
const model = await this.model.findOne({uuid: otcRef});
|
|
197
197
|
|
|
198
198
|
if (!model) {
|
|
199
199
|
return false;
|
|
@@ -208,16 +208,16 @@ class SingleUseTokenProvider {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* @method
|
|
212
|
-
* Retrieves the
|
|
211
|
+
* @method getRefByToken
|
|
212
|
+
* Retrieves the ref associated with a given token.
|
|
213
213
|
*
|
|
214
214
|
* @param {string} token - The token to look up.
|
|
215
|
-
* @returns {Promise<string|null>} The
|
|
215
|
+
* @returns {Promise<string|null>} The ref if found, or null if not found or on error.
|
|
216
216
|
*/
|
|
217
|
-
async
|
|
217
|
+
async getRefByToken(token) {
|
|
218
218
|
try {
|
|
219
219
|
const model = await this.model.findOne({token});
|
|
220
|
-
return model ? model.get('
|
|
220
|
+
return model ? model.get('uuid') : null;
|
|
221
221
|
} catch (err) {
|
|
222
222
|
return null;
|
|
223
223
|
}
|
|
@@ -232,7 +232,7 @@ class SingleUseTokenProvider {
|
|
|
232
232
|
*/
|
|
233
233
|
async getTokenByRef(ref) {
|
|
234
234
|
try {
|
|
235
|
-
const model = await this.model.findOne({
|
|
235
|
+
const model = await this.model.findOne({uuid: ref});
|
|
236
236
|
return model ? model.get('token') : null;
|
|
237
237
|
} catch (err) {
|
|
238
238
|
return null;
|
|
@@ -300,7 +300,7 @@ class SingleUseTokenProvider {
|
|
|
300
300
|
return false;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
const tokenId = await this.
|
|
303
|
+
const tokenId = await this.getRefByToken(token);
|
|
304
304
|
if (!tokenId) {
|
|
305
305
|
return false;
|
|
306
306
|
}
|