ghost 6.21.0 → 6.21.2
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.21.2.tgz +0 -0
- package/components/{tryghost-parse-email-address-6.21.0.tgz → tryghost-parse-email-address-6.21.2.tgz} +0 -0
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +7 -2
- package/content/themes/casper/assets/built/casper.js +1 -2
- package/content/themes/casper/assets/built/casper.js.map +1 -1
- package/content/themes/casper/assets/built/global.css +1 -2
- package/content/themes/casper/assets/built/global.css.map +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -2
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/global.css +4 -0
- package/content/themes/casper/default.hbs +5 -5
- package/content/themes/casper/error-404.hbs +1 -1
- package/content/themes/casper/error.hbs +2 -2
- package/content/themes/casper/gulpfile.js +13 -2
- package/content/themes/casper/locales/context.json +112 -0
- package/content/themes/casper/locales/de-CH.json +112 -0
- package/content/themes/casper/locales/de.json +112 -0
- package/content/themes/casper/locales/en.json +112 -0
- package/content/themes/casper/locales/fr.json +112 -0
- package/content/themes/casper/locales/ga.json +112 -0
- package/content/themes/casper/locales/gd.json +112 -0
- package/content/themes/casper/locales/nl.json +112 -0
- package/content/themes/casper/locales/pt-BR.json +112 -0
- package/content/themes/casper/locales/sv.json +112 -0
- package/content/themes/casper/locales/uk.json +112 -0
- package/content/themes/casper/locales/zh.json +112 -0
- package/content/themes/casper/package.json +12 -11
- package/content/themes/casper/partials/post-card.hbs +4 -4
- package/content/themes/casper/post.hbs +4 -4
- package/content/themes/casper/tag.hbs +1 -1
- package/content/themes/source/LICENSE +1 -1
- package/content/themes/source/README.md +4 -1
- package/content/themes/source/assets/built/screen.css +1 -2
- package/content/themes/source/assets/built/screen.css.map +1 -1
- package/content/themes/source/assets/built/source.js +1 -2
- package/content/themes/source/assets/built/source.js.map +1 -1
- package/content/themes/source/assets/css/screen.css +18 -16
- package/content/themes/source/default.hbs +1 -1
- package/content/themes/source/gulpfile.js +5 -3
- package/content/themes/source/locales/context.json +112 -0
- package/content/themes/source/locales/de-CH.json +112 -0
- package/content/themes/source/locales/de.json +112 -0
- package/content/themes/source/locales/en.json +112 -0
- package/content/themes/source/locales/fr.json +112 -0
- package/content/themes/source/locales/ga.json +112 -0
- package/content/themes/source/locales/gd.json +112 -0
- package/content/themes/source/locales/nl.json +112 -0
- package/content/themes/source/locales/pt-BR.json +112 -0
- package/content/themes/source/locales/sv.json +112 -0
- package/content/themes/source/locales/uk.json +112 -0
- package/content/themes/source/locales/zh.json +112 -0
- package/content/themes/source/package.json +13 -11
- package/content/themes/source/partials/components/featured.hbs +1 -1
- package/content/themes/source/partials/components/footer.hbs +1 -1
- package/content/themes/source/partials/components/header-content.hbs +1 -1
- package/content/themes/source/partials/components/navigation.hbs +5 -5
- package/content/themes/source/partials/components/post-list.hbs +7 -7
- package/content/themes/source/partials/email-subscription.hbs +5 -5
- package/content/themes/source/partials/lightbox.hbs +6 -6
- package/content/themes/source/partials/post-card.hbs +1 -1
- package/content/themes/source/partials/search-toggle.hbs +1 -1
- package/content/themes/source/post.hbs +2 -2
- package/core/built/admin/assets/{PolarAngleAxis-a31TecIQ.js → PolarAngleAxis-D8t9UwHk.js} +1 -1
- package/core/built/admin/assets/{_baseAssignValue-Dw5pcaUJ.js → _baseAssignValue-DPyNU5o6.js} +1 -1
- package/core/built/admin/assets/{a-large-small-Cf_hUupE.js → a-large-small-BijOKCoa.js} +1 -1
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{code-editor-view-C1ZFtR6M.mjs → code-editor-view-DJsXOcZ2.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-zyVbQw03.mjs → index-BnhIi5NU.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-D96qFjP9.mjs → index-CnwJhGbs.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-ILVAmH8Q.mjs → index-DaGaokMn.mjs} +1067 -1063
- package/core/built/admin/assets/admin-x-settings/{modals-BbDVa22r.mjs → modals-D02VJnTV.mjs} +2838 -2840
- package/core/built/admin/assets/{at-sign-DyMz5le1.js → at-sign-_JfhlimD.js} +1 -1
- package/core/built/admin/assets/{audience-BevM-Udw.js → audience-BdrhpNZ4.js} +1 -1
- package/core/built/admin/assets/{avatar-flipboard-DiGYhH_s.js → avatar-flipboard-BdmM8eOn.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-Dj4VOEDi.js → bluesky-sharing-yeM8KkY8.js} +1 -1
- package/core/built/admin/assets/{chart-BDdnkPgC.js → chart-CULaDoVB.js} +1 -1
- package/core/built/admin/assets/{chunk.138.5c4b0d0ae3f21c9c726d.js → chunk.482.c45c97fe666cd1a6b060.js} +4 -4
- package/core/built/admin/assets/{chunk.524.54ba5b50c191627c46f2.js → chunk.524.7fc2692374515780e66e.js} +4 -4
- package/core/built/admin/assets/{chunk.582.3232d44081d84d135e3d.js → chunk.582.fd8c4fa8e637e2d5611e.js} +8 -8
- package/core/built/admin/assets/{code-editor-view-V0wBeYuE.js → code-editor-view-UA2xKjk1.js} +1 -1
- package/core/built/admin/assets/{comments-CqurDRGu.js → comments-DoNjlMYA.js} +1 -1
- package/core/built/admin/assets/{content-helpers-C8lVEp2H.js → content-helpers-BRzxAgjP.js} +1 -1
- package/core/built/admin/assets/{copy-BdseBMKx.js → copy-CbX-_mHj.js} +1 -1
- package/core/built/admin/assets/{data-list--5Xfh5gm.js → data-list-nZOiCWV9.js} +1 -1
- package/core/built/admin/assets/{deleted-feed-item-Bmxp6PAR.js → deleted-feed-item-DjkZpKDC.js} +1 -1
- package/core/built/admin/assets/{edit-profile-Bi-G8vrq.js → edit-profile-BY_oJrQn.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-C1cLHSRX.js → empty-indicator-DoxAratw.js} +1 -1
- package/core/built/admin/assets/{en-DPpVO1wK.js → en-B9ScC5j9.js} +1 -1
- package/core/built/admin/assets/{feed-lCu_EJ0a.js → feed-BsgqABLQ.js} +1 -1
- package/core/built/admin/assets/{filters-BYYV0fmK.js → filters-CscMb_Gl.js} +1 -1
- package/core/built/admin/assets/{gh-chart-BYWaeEuY.js → gh-chart-GokHrG5_.js} +1 -1
- package/core/built/admin/assets/{ghost-565670d46ad5b30935426b5dfa95bc81.js → ghost-d1843f84a9476ccbdf2a350848ac88aa.js} +2 -2
- package/core/built/admin/assets/{growth-DXHnB1EQ.js → growth-CKOWA1_V.js} +1 -1
- package/core/built/admin/assets/{hash-tZATAzZa.js → hash-twG7-iIB.js} +1 -1
- package/core/built/admin/assets/{inbox-B-LuWyHY.js → inbox-BdkIJbSq.js} +1 -1
- package/core/built/admin/assets/{index-BDrJ3N3y.js → index-B7OzWwwP.js} +1 -1
- package/core/built/admin/assets/{index-B8tGZsrG.js → index-BFySh9qo.js} +1 -1
- package/core/built/admin/assets/{index-EiGE3Aiy.js → index-BOWUajrT.js} +1 -1
- package/core/built/admin/assets/{index-ig4J4RpO.js → index-BQLCImHQ.js} +1 -1
- package/core/built/admin/assets/{index-BmKPrbBK.js → index-C398bG84.js} +1 -1
- package/core/built/admin/assets/{index-BX0_v5NB.js → index-C5YCjM6z.js} +1 -1
- package/core/built/admin/assets/{index-DEwhVcR5.js → index-CHpxzPRR.js} +1 -1
- package/core/built/admin/assets/{index-CX1A5TwO.js → index-CM-iA6b-.js} +3 -3
- package/core/built/admin/assets/{index-9bj69FKq.js → index-CgCHCdle.js} +1 -1
- package/core/built/admin/assets/{index-BocVu6WC.js → index-Cw441AFq.js} +1 -1
- package/core/built/admin/assets/{index-jHIJgh6L.js → index-D9S88kRl.js} +1 -1
- package/core/built/admin/assets/{index-CqvBC0jL.js → index-DbgA2vK8.js} +1 -1
- package/core/built/admin/assets/{index-2H-Jp89Z.js → index-ghPdV1ln.js} +1 -1
- package/core/built/admin/assets/{index-BcOzgmTC.js → index-tX0pajfP.js} +1 -1
- package/core/built/admin/assets/{index-BxVUX0dN.js → index-upXLfQxx.js} +1 -1
- package/core/built/admin/assets/{koenig-lexical-BdX9U-oB.js → koenig-lexical-GZS7bHDz.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DQ1AbLJB.js → kpi-card-B6fmS7IH.js} +1 -1
- package/core/built/admin/assets/{kpis-vcbQvnLh.js → kpis-B-JXQ-4C.js} +1 -1
- package/core/built/admin/assets/{label-CNmRJkLy.js → label-ChHoKmj0.js} +1 -1
- package/core/built/admin/assets/{links-BE51SJxR.js → links-DQSOljcR.js} +1 -1
- package/core/built/admin/assets/{list-filter-BwnQ0Xlp.js → list-filter-cWuCDOLb.js} +1 -1
- package/core/built/admin/assets/{loader-circle-CcLEGvsN.js → loader-circle-B5ILzHHu.js} +1 -1
- package/core/built/admin/assets/{lucide-react-DHFgHS1l.js → lucide-react-CM0rB7CS.js} +1 -1
- package/core/built/admin/assets/{main-layout-DsxBNVo-.js → main-layout-BE7vZmy0.js} +1 -1
- package/core/built/admin/assets/{members-3okEkvWv.js → members-C10Z_BuG.js} +1 -1
- package/core/built/admin/assets/{message-square-text-BkERj4f5.js → message-square-text-DVZjnBBF.js} +1 -1
- package/core/built/admin/assets/{minus-C6oMPJo2.js → minus-BWrVDnd-.js} +1 -1
- package/core/built/admin/assets/{modals-CfUec0CL.js → modals-Bx7uDUHI.js} +15 -15
- package/core/built/admin/assets/{moderation-DEHUqi1e.js → moderation-DwlKZoB5.js} +1 -1
- package/core/built/admin/assets/{newsletter-Cp-39ksw.js → newsletter-DkOl42UB.js} +1 -1
- package/core/built/admin/assets/{newsletters-Bj58VQa2.js → newsletters-DNt9qd4h.js} +1 -1
- package/core/built/admin/assets/{note-9ysQWJz6.js → note-C-BLqULD.js} +1 -1
- package/core/built/admin/assets/{offers-DORdeAfE.js → offers-CU0KFuyW.js} +1 -1
- package/core/built/admin/assets/{overview-aZmI_71o.js → overview-DAEelSKq.js} +1 -1
- package/core/built/admin/assets/{pagemenu-DK1tbdsq.js → pagemenu-Cr7_YYR8.js} +1 -1
- package/core/built/admin/assets/{post-analytics-context-D8nk51yA.js → post-analytics-context-B2ytyOU0.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-qh1LEasb.js → post-analytics-header-CvR25xxU.js} +1 -1
- package/core/built/admin/assets/{post-analytics-HdZI-1J7.js → post-analytics-qpfoP-57.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-CvTf3PP9.js → post-share-modal-DDhiG0X4.js} +1 -1
- package/core/built/admin/assets/{posts-mIxckqHa.js → posts-DTy4lguT.js} +1 -1
- package/core/built/admin/assets/{referrers-qhD64pmp.js → referrers-BzKz9fT9.js} +1 -1
- package/core/built/admin/assets/{repeat-Idtsb62O.js → repeat-C_xWSLK1.js} +1 -1
- package/core/built/admin/assets/{reply-DXS-C-Rk.js → reply-CXnb6Juu.js} +1 -1
- package/core/built/admin/assets/{select-CeD8IhXy.js → select-WZUCQgjI.js} +1 -1
- package/core/built/admin/assets/{settings-BHfStwmS.js → settings-BC82mM6N.js} +1 -1
- package/core/built/admin/assets/{settings-M0QTt_tT.js → settings-EbntXZw9.js} +7 -7
- package/core/built/admin/assets/{sort-button-BoTFsBYE.js → sort-button-CFYf_NOQ.js} +1 -1
- package/core/built/admin/assets/{source-icon-opy8g_Nk.js → source-icon-xS8eeWX-.js} +1 -1
- package/core/built/admin/assets/{sprout-BKOFkdV8.js → sprout-CLmu2pln.js} +1 -1
- package/core/built/admin/assets/{square-BjZk7vJx.js → square-D0Y5-h0C.js} +1 -1
- package/core/built/admin/assets/{stats-DXXxmfRV.js → stats-B5O9DjYi.js} +1 -1
- package/core/built/admin/assets/{stats-view-D_5J0Nf7.js → stats-view-Cr6M1ycv.js} +1 -1
- package/core/built/admin/assets/{step-1-BcaR_MDC.js → step-1-DGbl6AAj.js} +1 -1
- package/core/built/admin/assets/{step-2-BR0jqEO_.js → step-2-Rf8oissi.js} +1 -1
- package/core/built/admin/assets/{step-3-CMPME0ky.js → step-3-tMPmbeBc.js} +1 -1
- package/core/built/admin/assets/{table-CwbnKMYe.js → table-CXo8NKU2.js} +1 -1
- package/core/built/admin/assets/{tabs-hfT89L_O.js → tabs-BMk71Tf2.js} +1 -1
- package/core/built/admin/assets/{tags-BSwYlj_e.js → tags-CSl0BjsA.js} +1 -1
- package/core/built/admin/assets/{tags-C3b5AHTm.js → tags-CqHx8i6Q.js} +1 -1
- package/core/built/admin/assets/{ticket-BnxPoED1.js → ticket-BtYGyQvH.js} +1 -1
- package/core/built/admin/assets/{tiers-DHKKzaBK.js → tiers-CPwRaPSt.js} +1 -1
- package/core/built/admin/assets/{toggle-group-U-Oqm2AA.js → toggle-group-QFge4qQs.js} +1 -1
- package/core/built/admin/assets/{topic-filter-28veMlW4.js → topic-filter-DnYtZ5e5.js} +1 -1
- package/core/built/admin/assets/{trash-D6TuYuIs.js → trash-B4ncKmRu.js} +1 -1
- package/core/built/admin/assets/{use-growth-stats-COmCRCzY.js → use-growth-stats-eFO-f1o1.js} +1 -1
- package/core/built/admin/assets/{use-infinite-virtual-scroll-mIEgtLoq.js → use-infinite-virtual-scroll-DTqnBtHM.js} +1 -1
- package/core/built/admin/assets/{use-scroll-restoration-CXVseKDj.js → use-scroll-restoration-o1DrSdlb.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-5-LKveLR.js → use-simple-pagination-BsBGNcxc.js} +1 -1
- package/core/built/admin/assets/{user-plus-CKQ-d0d4.js → user-plus-BS8SGBmk.js} +1 -1
- package/core/built/admin/assets/{user-round-check-BWc7Da4S.js → user-round-check-BgOPeBFs.js} +1 -1
- package/core/built/admin/assets/{wallet-cards-BlDtHc6_.js → wallet-cards-Be7G77lR.js} +1 -1
- package/core/built/admin/assets/{web-Bvvtl44p.js → web-BNs01IAt.js} +1 -1
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/cancel_link.js +2 -2
- package/core/server/api/endpoints/authors-public.js +3 -3
- package/core/server/api/endpoints/files.js +2 -1
- package/core/server/api/endpoints/pages-public.js +3 -3
- package/core/server/api/endpoints/posts-public.js +3 -3
- package/core/server/api/endpoints/users.js +6 -2
- package/core/server/api/endpoints/utils/api-filter-utils.js +22 -0
- package/core/server/api/endpoints/utils/api-filter-utils.ts +22 -0
- package/core/server/lib/lexical.js +2 -1
- package/core/server/lib/mobiledoc.js +1 -1
- package/core/server/lib/request-external.js +78 -2
- package/core/server/services/email-service/email-templates/partials/styles.hbs +3 -1
- package/core/server/services/koenig/node-renderers/image-renderer.js +80 -10
- package/core/server/services/koenig/render-utils/srcset-attribute.js +15 -3
- package/core/server/services/members/members-api/controllers/router-controller.js +5 -5
- package/core/server/services/members/members-api/repositories/member-repository.js +1 -1
- package/core/server/services/members/members-api/utils/get-discount-window.js +70 -4
- package/core/server/services/members/members-api/utils/has-active-offer.js +10 -14
- package/core/server/services/oembed/oembed-service.js +9 -1
- package/core/server/services/offers/application/offer-mapper.js +48 -0
- package/core/server/services/offers/application/offers-api.js +2 -2
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/labs.js +2 -1
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +148 -16
- package/components/tryghost-i18n-6.21.0.tgz +0 -0
- package/core/server/api/endpoints/utils/public-endpoint-utils.js +0 -18
- package/core/server/api/endpoints/utils/public-endpoint-utils.ts +0 -17
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const dns = require('node:dns/promises');
|
|
2
|
+
const crypto = require('node:crypto');
|
|
2
3
|
const tpl = require('@tryghost/tpl');
|
|
3
4
|
const logging = require('@tryghost/logging');
|
|
4
5
|
const sanitizeHtml = require('sanitize-html');
|
|
@@ -26,7 +27,6 @@ const messages = {
|
|
|
26
27
|
inviteOnly: 'This site is invite-only, contact the owner for access.',
|
|
27
28
|
paidOnly: 'This site only accepts paid members.',
|
|
28
29
|
memberNotFound: 'No member exists with this e-mail address.',
|
|
29
|
-
memberNotFoundSignUp: 'No member exists with this e-mail address. Please sign up first.',
|
|
30
30
|
invalidType: 'Invalid checkout type.',
|
|
31
31
|
notConfigured: 'This site is not accepting payments at the moment.',
|
|
32
32
|
invalidNewsletters: 'Cannot subscribe to invalid newsletters {newsletters}',
|
|
@@ -799,7 +799,7 @@ module.exports = class RouterController {
|
|
|
799
799
|
if (!tokenValue) {
|
|
800
800
|
throw new errors.BadRequestError({
|
|
801
801
|
message: tpl(messages.invalidCode),
|
|
802
|
-
code: '
|
|
802
|
+
code: 'INVALID_OTC'
|
|
803
803
|
});
|
|
804
804
|
}
|
|
805
805
|
|
|
@@ -887,9 +887,9 @@ module.exports = class RouterController {
|
|
|
887
887
|
const member = await this._memberRepository.get({email: normalizedEmail});
|
|
888
888
|
|
|
889
889
|
if (!member) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
890
|
+
// Return a fake otcRef when OTC was requested so the response
|
|
891
|
+
// shape is identical regardless of whether a member exists
|
|
892
|
+
return includeOTC ? {otcRef: crypto.randomUUID()} : {};
|
|
893
893
|
}
|
|
894
894
|
|
|
895
895
|
const tokenData = {};
|
|
@@ -1725,7 +1725,7 @@ module.exports = class MemberRepository {
|
|
|
1725
1725
|
}
|
|
1726
1726
|
|
|
1727
1727
|
// Check subscription doesn't already have an active offer
|
|
1728
|
-
if (await hasActiveOffer(subscriptionModel, this._offersAPI)) {
|
|
1728
|
+
if (await hasActiveOffer(subscriptionModel, this._offersAPI, options)) {
|
|
1729
1729
|
throw new errors.BadRequestError({
|
|
1730
1730
|
message: tpl(messages.subscriptionHasOffer)
|
|
1731
1731
|
});
|
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
function getLastDayOfMonth(year, month) {
|
|
2
|
+
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function isLastDayOfMonth(date) {
|
|
6
|
+
return date.getUTCDate() === getLastDayOfMonth(date.getUTCFullYear(), date.getUTCMonth());
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getAnchoredBillingDate(anchorDate, monthOffset) {
|
|
10
|
+
const targetMonthIndex = anchorDate.getUTCMonth() + monthOffset;
|
|
11
|
+
const targetYear = anchorDate.getUTCFullYear() + Math.floor(targetMonthIndex / 12);
|
|
12
|
+
const targetMonth = ((targetMonthIndex % 12) + 12) % 12;
|
|
13
|
+
const targetLastDay = getLastDayOfMonth(targetYear, targetMonth);
|
|
14
|
+
const targetDay = isLastDayOfMonth(anchorDate) ? targetLastDay : Math.min(anchorDate.getUTCDate(), targetLastDay);
|
|
15
|
+
|
|
16
|
+
return new Date(Date.UTC(
|
|
17
|
+
targetYear,
|
|
18
|
+
targetMonth,
|
|
19
|
+
targetDay,
|
|
20
|
+
anchorDate.getUTCHours(),
|
|
21
|
+
anchorDate.getUTCMinutes(),
|
|
22
|
+
anchorDate.getUTCSeconds(),
|
|
23
|
+
anchorDate.getUTCMilliseconds()
|
|
24
|
+
));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getLastDiscountedPayment(nextBillingDate, discountEnd) {
|
|
28
|
+
const monthOffset =
|
|
29
|
+
((discountEnd.getUTCFullYear() - nextBillingDate.getUTCFullYear()) * 12) +
|
|
30
|
+
(discountEnd.getUTCMonth() - nextBillingDate.getUTCMonth());
|
|
31
|
+
|
|
32
|
+
let lastDiscountedBillingDate = getAnchoredBillingDate(nextBillingDate, monthOffset);
|
|
33
|
+
|
|
34
|
+
if (lastDiscountedBillingDate > discountEnd) {
|
|
35
|
+
lastDiscountedBillingDate = getAnchoredBillingDate(nextBillingDate, monthOffset - 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return lastDiscountedBillingDate;
|
|
39
|
+
}
|
|
40
|
+
|
|
1
41
|
/**
|
|
2
42
|
* Computes the discount window for a subscription based on available data.
|
|
3
43
|
* Returns {start, end} if a discount window can be determined, null otherwise.
|
|
@@ -6,14 +46,35 @@
|
|
|
6
46
|
* 1. Stripe coupon discounts (post-6.16) - uses discount_start / discount_end
|
|
7
47
|
* 2. Legacy fallback - computes from offer duration and start_date
|
|
8
48
|
*
|
|
9
|
-
* @param {object} subscription - plain object with: discount_start, discount_end, start_date
|
|
49
|
+
* @param {object} subscription - plain object with: discount_start, discount_end, start_date, current_period_end, plan.interval, plan_interval
|
|
10
50
|
* @param {object|null} offer - offer data with: duration, duration_in_months.
|
|
11
51
|
* Pass null to skip offer-dependent checks.
|
|
12
52
|
* @returns {{start: *, end: *}|null}
|
|
13
53
|
*/
|
|
54
|
+
|
|
14
55
|
module.exports = function getDiscountWindow(subscription, offer) {
|
|
15
56
|
// Stripe coupon discount (post-6.16 data)
|
|
16
57
|
if (subscription.discount_start) {
|
|
58
|
+
if (subscription.discount_end && offer?.duration === 'repeating') {
|
|
59
|
+
const discountEnd = new Date(subscription.discount_end);
|
|
60
|
+
const currentPeriodEnd = new Date(subscription.current_period_end);
|
|
61
|
+
|
|
62
|
+
if (discountEnd <= new Date()) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A discount ending at, or before, the current billing period end won't affect the next payment
|
|
67
|
+
if (discountEnd <= currentPeriodEnd) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Match the end date with the last discounted payment
|
|
72
|
+
return {
|
|
73
|
+
start: subscription.discount_start,
|
|
74
|
+
end: getLastDiscountedPayment(currentPeriodEnd, discountEnd)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
17
78
|
return {
|
|
18
79
|
start: subscription.discount_start,
|
|
19
80
|
end: subscription.discount_end || null
|
|
@@ -34,10 +95,15 @@ module.exports = function getDiscountWindow(subscription, offer) {
|
|
|
34
95
|
}
|
|
35
96
|
|
|
36
97
|
if (offer.duration === 'repeating' && offer.duration_in_months > 0) {
|
|
37
|
-
const end = new Date(subscription.start_date);
|
|
38
|
-
|
|
98
|
+
const end = getAnchoredBillingDate(new Date(subscription.start_date), offer.duration_in_months - 1);
|
|
99
|
+
const currentPeriodEnd = new Date(subscription.current_period_end);
|
|
100
|
+
|
|
101
|
+
if (end <= new Date()) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
39
104
|
|
|
40
|
-
|
|
105
|
+
// A discount ending before the end of the current billing period won't affect the next payment
|
|
106
|
+
if (end < currentPeriodEnd) {
|
|
41
107
|
return null;
|
|
42
108
|
}
|
|
43
109
|
|
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
const getDiscountWindow = require('./get-discount-window');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Determines if a subscription
|
|
4
|
+
* Determines if a subscription has an offer that still affects the next payment,
|
|
5
|
+
* or an active trial.
|
|
5
6
|
* Uses discount_start/discount_end (synced from Stripe) when available,
|
|
6
7
|
* falls back to offer duration lookup for legacy data (pre-6.16).
|
|
7
8
|
*
|
|
8
9
|
* @param {object} subscriptionModel - Bookshelf model for members_stripe_customers_subscriptions
|
|
9
10
|
* @param {object} offersAPI - OffersAPI instance with getOffer()
|
|
11
|
+
* @param {object} [options] - Optional query options such as transacting
|
|
10
12
|
* @returns {Promise<boolean>}
|
|
11
13
|
*/
|
|
12
|
-
module.exports = async function hasActiveOffer(subscriptionModel, offersAPI) {
|
|
14
|
+
module.exports = async function hasActiveOffer(subscriptionModel, offersAPI, options = {}) {
|
|
13
15
|
const subscriptionData = {
|
|
14
16
|
discount_start: subscriptionModel.get('discount_start'),
|
|
15
17
|
discount_end: subscriptionModel.get('discount_end'),
|
|
16
|
-
start_date: subscriptionModel.get('start_date')
|
|
18
|
+
start_date: subscriptionModel.get('start_date'),
|
|
19
|
+
current_period_end: subscriptionModel.get('current_period_end')
|
|
17
20
|
};
|
|
18
21
|
|
|
19
|
-
// Check for active Stripe discount (post-6.16 data)
|
|
20
|
-
// discount_start takes precedence over trial and legacy fallback
|
|
21
|
-
const discountWindow = getDiscountWindow(subscriptionData, null);
|
|
22
|
-
if (discountWindow) {
|
|
23
|
-
return !discountWindow.end || new Date(discountWindow.end) > new Date();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
22
|
// Check for active trial (trial offers)
|
|
27
23
|
const trialEndAt = subscriptionModel.get('trial_end_at');
|
|
28
24
|
if (trialEndAt && new Date(trialEndAt) > new Date()) {
|
|
@@ -37,14 +33,14 @@ module.exports = async function hasActiveOffer(subscriptionModel, offersAPI) {
|
|
|
37
33
|
|
|
38
34
|
// Look up the offer to determine if it's still active based on duration
|
|
39
35
|
try {
|
|
40
|
-
const offer = await offersAPI.getOffer({id: offerId});
|
|
36
|
+
const offer = await offersAPI.getOffer({id: offerId}, options);
|
|
41
37
|
if (!offer) {
|
|
42
38
|
return false;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
return !
|
|
41
|
+
const discountWindow = getDiscountWindow(subscriptionData, offer);
|
|
42
|
+
if (discountWindow) {
|
|
43
|
+
return !discountWindow.end || new Date(discountWindow.end) > new Date();
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
return false;
|
|
@@ -262,7 +262,15 @@ class OEmbedService {
|
|
|
262
262
|
* @param {string} url
|
|
263
263
|
* @param {string} html
|
|
264
264
|
*
|
|
265
|
-
* @returns {Promise<
|
|
265
|
+
* @returns {Promise<{
|
|
266
|
+
* version: '1.0',
|
|
267
|
+
* type: 'bookmark',
|
|
268
|
+
* url: string,
|
|
269
|
+
* metadata: Omit<import('metascraper').Metadata, 'image'|'logo'> & {
|
|
270
|
+
* thumbnail?: string,
|
|
271
|
+
* icon?: string
|
|
272
|
+
* }
|
|
273
|
+
* }>}
|
|
266
274
|
*/
|
|
267
275
|
async fetchBookmarkData(url, html, type) {
|
|
268
276
|
const got = require('got');
|
|
@@ -33,6 +33,29 @@
|
|
|
33
33
|
* @prop {string|null} last_redeemed
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} PublicOfferDTO
|
|
38
|
+
* @prop {string} id
|
|
39
|
+
*
|
|
40
|
+
* @prop {string} display_title
|
|
41
|
+
* @prop {string} display_description
|
|
42
|
+
*
|
|
43
|
+
* @prop {'percent'|'fixed'|'trial'} type
|
|
44
|
+
*
|
|
45
|
+
* @prop {'month'|'year'} cadence
|
|
46
|
+
* @prop {number} amount
|
|
47
|
+
*
|
|
48
|
+
* @prop {'once'|'repeating'|'forever'|'trial'} duration
|
|
49
|
+
* @prop {null|number} duration_in_months
|
|
50
|
+
*
|
|
51
|
+
* @prop {string|null} currency
|
|
52
|
+
*
|
|
53
|
+
* @prop {'active'|'archived'} status
|
|
54
|
+
* @prop {'signup'|'retention'} redemption_type
|
|
55
|
+
*
|
|
56
|
+
* @prop {{id: string}|null} tier
|
|
57
|
+
*/
|
|
58
|
+
|
|
36
59
|
class OfferMapper {
|
|
37
60
|
/**
|
|
38
61
|
* @param {Offer} offer
|
|
@@ -62,6 +85,31 @@ class OfferMapper {
|
|
|
62
85
|
last_redeemed: offer.lastRedeemed
|
|
63
86
|
};
|
|
64
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns a DTO for a public facing offer (e.g. Portal's retention offer UI)
|
|
91
|
+
*
|
|
92
|
+
* @param {Offer} offer
|
|
93
|
+
* @returns {PublicOfferDTO}
|
|
94
|
+
*/
|
|
95
|
+
static toPublicDTO(offer) {
|
|
96
|
+
return {
|
|
97
|
+
id: offer.id,
|
|
98
|
+
display_title: offer.displayTitle.value,
|
|
99
|
+
display_description: offer.displayDescription.value,
|
|
100
|
+
type: offer.type.value,
|
|
101
|
+
cadence: offer.cadence.value,
|
|
102
|
+
amount: offer.amount.value,
|
|
103
|
+
duration: offer.duration.value.type,
|
|
104
|
+
duration_in_months: offer.duration.value.type === 'repeating' ? offer.duration.value.months : null,
|
|
105
|
+
currency: offer.type.value === 'fixed' ? offer.currency.value : null,
|
|
106
|
+
status: offer.status.value,
|
|
107
|
+
redemption_type: offer.redemptionType.value,
|
|
108
|
+
tier: offer.tier
|
|
109
|
+
? {id: offer.tier.id}
|
|
110
|
+
: null
|
|
111
|
+
};
|
|
112
|
+
}
|
|
65
113
|
}
|
|
66
114
|
|
|
67
115
|
module.exports = OfferMapper;
|
|
@@ -180,7 +180,7 @@ class OffersAPI {
|
|
|
180
180
|
* @param {string} options.tierId
|
|
181
181
|
* @param {'month'|'year'} options.cadence
|
|
182
182
|
* @param {'signup'|'retention'} [options.redemptionType]
|
|
183
|
-
* @returns {Promise<OfferMapper.
|
|
183
|
+
* @returns {Promise<OfferMapper.PublicOfferDTO[]>}
|
|
184
184
|
*/
|
|
185
185
|
async listOffersAvailableToSubscription({subscriptionId, tierId, cadence, redemptionType}) {
|
|
186
186
|
debug(`listOffersAvailableToSubscription: subscriptionId=${subscriptionId}, tierId=${tierId}, cadence=${cadence}, redemptionType=${redemptionType}`);
|
|
@@ -246,7 +246,7 @@ class OffersAPI {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
debug(`listOffersAvailableToSubscription: returning ${available.length} available offers`);
|
|
249
|
-
return available.map(OfferMapper.
|
|
249
|
+
return available.map(OfferMapper.toPublicDTO);
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
},
|
|
241
241
|
"portal": {
|
|
242
242
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
243
|
-
"version": "2.
|
|
243
|
+
"version": "2.66"
|
|
244
244
|
},
|
|
245
245
|
"sodoSearch": {
|
|
246
246
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
package/core/shared/labs.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghost",
|
|
3
|
-
"version": "6.21.
|
|
3
|
+
"version": "6.21.2",
|
|
4
4
|
"description": "The professional publishing platform",
|
|
5
5
|
"author": "Ghost Foundation",
|
|
6
6
|
"homepage": "https://ghost.org",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@tryghost/helpers": "1.1.97",
|
|
86
86
|
"@tryghost/html-to-plaintext": "1.0.4",
|
|
87
87
|
"@tryghost/http-cache-utils": "0.1.20",
|
|
88
|
-
"@tryghost/i18n": "file:components/tryghost-i18n-6.21.
|
|
88
|
+
"@tryghost/i18n": "file:components/tryghost-i18n-6.21.2.tgz",
|
|
89
89
|
"@tryghost/image-transform": "1.4.6",
|
|
90
90
|
"@tryghost/job-manager": "1.0.3",
|
|
91
91
|
"@tryghost/kg-card-factory": "5.1.10",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"@tryghost/mw-vhost": "1.0.1",
|
|
108
108
|
"@tryghost/nodemailer": "0.3.48",
|
|
109
109
|
"@tryghost/nql": "0.12.10",
|
|
110
|
-
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.21.
|
|
110
|
+
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.21.2.tgz",
|
|
111
111
|
"@tryghost/pretty-cli": "1.2.47",
|
|
112
112
|
"@tryghost/prometheus-metrics": "1.0.2",
|
|
113
113
|
"@tryghost/promise": "0.3.15",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"moment": "2.24.0",
|
|
196
196
|
"moment-timezone": "0.5.45",
|
|
197
197
|
"multer": "2.0.2",
|
|
198
|
-
"mysql2": "3.18.
|
|
198
|
+
"mysql2": "3.18.1",
|
|
199
199
|
"nconf": "0.13.0",
|
|
200
200
|
"node-fetch": "2.7.0",
|
|
201
201
|
"node-jose": "2.2.0",
|
|
@@ -272,8 +272,8 @@
|
|
|
272
272
|
"jackspeak": "2.3.6",
|
|
273
273
|
"moment": "2.24.0",
|
|
274
274
|
"moment-timezone": "0.5.45",
|
|
275
|
-
"@tryghost/i18n": "file:components/tryghost-i18n-6.21.
|
|
276
|
-
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.21.
|
|
275
|
+
"@tryghost/i18n": "file:components/tryghost-i18n-6.21.2.tgz",
|
|
276
|
+
"@tryghost/parse-email-address": "file:components/tryghost-parse-email-address-6.21.2.tgz"
|
|
277
277
|
},
|
|
278
278
|
"nx": {
|
|
279
279
|
"targets": {
|