ghost 4.23.0 → 4.26.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/.eslintrc.js +39 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/casper.js.map +1 -1
- package/content/themes/casper/assets/built/global.css +1 -1
- package/content/themes/casper/assets/built/global.css.map +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/global.css +6 -1
- package/content/themes/casper/assets/css/screen.css +50 -215
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/package.json +3 -2
- package/content/themes/casper/post.hbs +1 -1
- package/content/themes/casper/yarn.lock +173 -123
- package/core/app.js +20 -5
- package/core/boot.js +77 -36
- package/core/bridge.js +10 -10
- package/core/built/assets/ghost-dark-ef86e3bc7f0fb83d39d3d6a49bff8dd5.css +1 -0
- package/core/built/assets/ghost.min-57c1e677f42d596942d317ce93e8a62c.css +1 -0
- package/core/built/assets/{ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js → ghost.min-f3c6886e191d34450e9ffca0c8fa056e.js} +543 -618
- package/core/built/assets/icons/audio-upload.svg +8 -0
- package/core/built/assets/{vendor.min-c9002845b6c30ac978abdadde9f33d7c.js → vendor.min-b6b8d2a31d61830c2d8f65c5ba54236a.js} +2656 -2118
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
- package/core/frontend/apps/amp/lib/views/amp.hbs +75 -0
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/helpers/url.js +18 -1
- package/core/frontend/services/apps/index.js +1 -1
- package/core/frontend/services/apps/loader.js +3 -3
- package/core/frontend/services/card-assets/index.js +0 -12
- package/core/frontend/services/card-assets/service.js +22 -21
- package/core/frontend/services/helpers/handlebars.js +1 -1
- package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
- package/core/frontend/services/theme-engine/middleware/index.js +6 -0
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
- package/core/frontend/src/cards/css/audio.css +186 -0
- package/core/frontend/src/cards/css/blockquote.css +27 -0
- package/core/frontend/src/cards/css/bookmark.css +7 -0
- package/core/frontend/src/cards/css/button.css +4 -0
- package/core/frontend/src/cards/css/callout.css +23 -15
- package/core/frontend/src/cards/css/gallery.css +13 -3
- package/core/frontend/src/cards/css/toggle.css +48 -15
- package/core/frontend/src/cards/js/audio.js +137 -0
- package/core/frontend/web/middleware/error-handler.js +93 -0
- package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
- package/core/frontend/web/middleware/index.js +1 -0
- package/core/frontend/web/middleware/serve-public-file.js +25 -8
- package/core/frontend/web/site.js +2 -5
- package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
- package/core/server/adapters/storage/LocalStorageBase.js +2 -2
- package/core/server/api/canary/db.js +2 -2
- package/core/server/api/canary/media.js +3 -2
- package/core/server/api/canary/oembed.js +16 -1
- package/core/server/api/canary/session.js +1 -1
- package/core/server/api/canary/slugs.js +1 -1
- package/core/server/api/canary/utils/permissions.js +2 -2
- package/core/server/api/canary/utils/serializers/output/config.js +2 -6
- package/core/server/api/v2/db.js +2 -2
- package/core/server/api/v2/session.js +1 -1
- package/core/server/api/v2/slugs.js +1 -1
- package/core/server/api/v2/utils/permissions.js +2 -2
- package/core/server/api/v3/db.js +2 -2
- package/core/server/api/v3/session.js +1 -1
- package/core/server/api/v3/slugs.js +1 -1
- package/core/server/api/v3/utils/permissions.js +2 -2
- package/core/server/data/db/state-manager.js +4 -4
- package/core/server/data/exporter/export-filename.js +1 -1
- package/core/server/data/importer/handlers/json.js +1 -1
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/importer/importers/data/base.js +1 -1
- package/core/server/data/migrations/utils.js +2 -2
- package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
- package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
- package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
- package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
- package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
- package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
- package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
- package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
- package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
- package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
- package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
- package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
- package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
- package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
- package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
- package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +1 -0
- package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
- package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
- package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
- package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
- package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
- package/core/server/data/schema/commands.js +2 -2
- package/core/server/ghost-server.js +2 -2
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/models/base/listeners.js +2 -2
- package/core/server/models/member-email-change-event.js +2 -2
- package/core/server/models/member-login-event.js +2 -2
- package/core/server/models/member-paid-subscription-event.js +3 -3
- package/core/server/models/member-payment-event.js +3 -3
- package/core/server/models/member-product-event.js +6 -6
- package/core/server/models/member-status-event.js +5 -3
- package/core/server/models/member-subscribe-event.js +9 -3
- package/core/server/models/relations/authors.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/notify.js +1 -2
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/setup.js +1 -1
- package/core/server/services/email-analytics/jobs/index.js +1 -1
- package/core/server/services/mega/mega.js +6 -4
- package/core/server/services/mega/template.js +47 -17
- package/core/server/services/members/api.js +22 -0
- package/core/server/services/members/config.js +1 -1
- package/core/server/services/members/emails/signup-paid.js +168 -0
- package/core/server/services/members/service.js +6 -2
- package/core/server/services/members/stripe-connect.js +4 -2
- package/core/server/services/nft-oembed.js +7 -2
- package/core/server/services/oembed.js +15 -3
- package/core/server/services/permissions/can-this.js +1 -1
- package/core/server/services/redirects/api.js +20 -25
- package/core/server/services/redirects/index.js +18 -10
- package/core/server/services/redirects/utils.js +14 -0
- package/core/server/services/redirects/validation.js +10 -0
- package/core/server/services/route-settings/default-settings-manager.js +1 -1
- package/core/server/services/route-settings/index.js +40 -17
- package/core/server/services/route-settings/route-settings.js +120 -115
- package/core/server/services/route-settings/settings-loader.js +18 -36
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activation-bridge.js +3 -3
- package/core/server/services/themes/storage.js +2 -2
- package/core/server/services/twitter-embed.js +81 -0
- package/core/server/services/url/LocalFileCache.js +75 -0
- package/core/server/services/url/UrlService.js +15 -47
- package/core/server/services/url/index.js +17 -4
- package/core/server/services/xmlrpc.js +2 -2
- package/core/server/web/admin/app.js +2 -5
- package/core/server/web/admin/controller.js +35 -12
- package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/canary/admin/app.js +0 -3
- package/core/server/web/api/canary/admin/middleware.js +1 -1
- package/core/server/web/api/canary/content/app.js +0 -3
- package/core/server/web/api/v2/admin/app.js +0 -3
- package/core/server/web/api/v2/admin/middleware.js +1 -1
- package/core/server/web/api/v2/content/app.js +0 -3
- package/core/server/web/api/v3/admin/app.js +0 -3
- package/core/server/web/api/v3/admin/middleware.js +1 -1
- package/core/server/web/api/v3/content/app.js +0 -3
- package/core/server/web/members/app.js +0 -3
- package/core/server/web/oauth/app.js +0 -4
- package/core/server/web/parent/app.js +2 -13
- package/core/server/web/parent/backend.js +2 -0
- package/core/server/web/shared/middleware/error-handler.js +57 -162
- package/core/server/web/shared/middleware/index.js +0 -4
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/express.js +1 -1
- package/core/shared/labs.js +12 -7
- package/core/shared/sentry.js +1 -1
- package/package.json +41 -40
- package/yarn.lock +799 -948
- package/content/themes/casper/assets/js/gallery-card.js +0 -24
- package/core/built/assets/ghost-dark-42cf6e0c730578940ec069bda45aea41.css +0 -1
- package/core/built/assets/ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css +0 -1
- package/core/frontend/services/theme-engine/middleware.js +0 -209
- package/core/server/web/shared/middleware/maintenance.js +0 -25
|
@@ -148,7 +148,7 @@ dd {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
blockquote {
|
|
151
|
-
margin: 2em 0;
|
|
151
|
+
margin: 2em 0 2em 0;
|
|
152
152
|
padding: 0 25px 0 25px;
|
|
153
153
|
border-left: ${templateSettings.accentColor || '#15212A'} 2px solid;
|
|
154
154
|
font-size: 17px;
|
|
@@ -157,6 +157,15 @@ blockquote {
|
|
|
157
157
|
letter-spacing: -0.2px;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
blockquote.kg-blockquote-alt {
|
|
161
|
+
border-left: 0 none;
|
|
162
|
+
padding: 0 50px 0 50px;
|
|
163
|
+
text-align: center;
|
|
164
|
+
font-size: 1.2em;
|
|
165
|
+
font-style: italic;
|
|
166
|
+
color: #999999;
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
blockquote p {
|
|
161
170
|
margin: 0.8em 0;
|
|
162
171
|
font-size: 1em;
|
|
@@ -557,52 +566,66 @@ figure blockquote p {
|
|
|
557
566
|
padding-bottom: 4px;
|
|
558
567
|
}
|
|
559
568
|
|
|
560
|
-
.kg-
|
|
569
|
+
.kg-twitter-link {
|
|
570
|
+
display: block;
|
|
571
|
+
text-decoration: none !important;
|
|
572
|
+
color: #15212A !important;
|
|
573
|
+
font-family: inherit !important;
|
|
574
|
+
font-size: 15px;
|
|
575
|
+
padding: 8px;
|
|
576
|
+
line-height: 1.3em;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.kg-callout-card {
|
|
561
580
|
display: flex;
|
|
562
581
|
margin: 0 0 1.5em 0;
|
|
563
582
|
padding: 20px 28px;
|
|
564
583
|
border-radius: 3px;
|
|
565
584
|
}
|
|
566
585
|
|
|
567
|
-
.kg-card
|
|
586
|
+
.kg-callout-card p {
|
|
568
587
|
margin: 0
|
|
569
588
|
}
|
|
570
589
|
|
|
571
|
-
.kg-card-
|
|
590
|
+
.kg-callout-card-grey {
|
|
572
591
|
background: #eef0f2;
|
|
573
592
|
}
|
|
574
593
|
|
|
575
|
-
.kg-card-
|
|
594
|
+
.kg-callout-card-white {
|
|
576
595
|
background: #fff;
|
|
577
596
|
box-shadow: inset 0 0 0 1px #dddedf;
|
|
578
597
|
}
|
|
579
598
|
|
|
580
|
-
.kg-card-
|
|
599
|
+
.kg-callout-card-blue {
|
|
581
600
|
background: #E9F6FB;
|
|
582
601
|
}
|
|
583
602
|
|
|
584
|
-
.kg-card-
|
|
603
|
+
.kg-callout-card-green {
|
|
585
604
|
background: #E8F8EA;
|
|
586
605
|
}
|
|
587
606
|
|
|
588
|
-
.kg-card-
|
|
607
|
+
.kg-callout-card-yellow {
|
|
589
608
|
background: #FCF4E3;
|
|
590
609
|
}
|
|
591
610
|
|
|
592
|
-
.kg-card-
|
|
611
|
+
.kg-callout-card-red {
|
|
593
612
|
background: #FBE9E9;
|
|
594
613
|
}
|
|
595
614
|
|
|
596
|
-
.kg-card-
|
|
615
|
+
.kg-callout-card-pink {
|
|
597
616
|
background: #FCEEF8;
|
|
598
617
|
}
|
|
599
618
|
|
|
600
|
-
.kg-card-
|
|
619
|
+
.kg-callout-card-purple {
|
|
601
620
|
background: #F2EDFC;
|
|
602
621
|
}
|
|
603
622
|
|
|
604
|
-
.kg-card-
|
|
605
|
-
background:
|
|
623
|
+
.kg-callout-card-accent {
|
|
624
|
+
background: ${templateSettings.accentColor || '#15212A'};
|
|
625
|
+
color: #fff;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.kg-callout-card-accent a {
|
|
606
629
|
color: #fff;
|
|
607
630
|
}
|
|
608
631
|
|
|
@@ -884,10 +907,17 @@ figure blockquote p {
|
|
|
884
907
|
}
|
|
885
908
|
|
|
886
909
|
table.body blockquote {
|
|
887
|
-
font-size: 17px
|
|
888
|
-
line-height: 1.6em
|
|
889
|
-
margin-bottom: 0
|
|
890
|
-
padding-left: 15px
|
|
910
|
+
font-size: 17px;
|
|
911
|
+
line-height: 1.6em;
|
|
912
|
+
margin-bottom: 0;
|
|
913
|
+
padding-left: 15px;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
table.body blockquote.kg-blockquote-alt {
|
|
917
|
+
border-left: 0 none !important;
|
|
918
|
+
margin: 0 0 2.5em 0;
|
|
919
|
+
padding: 0 50px 0 50px;
|
|
920
|
+
font-size: 1.2em;
|
|
891
921
|
}
|
|
892
922
|
|
|
893
923
|
table.body blockquote + * {
|
|
@@ -6,6 +6,7 @@ const mail = require('../mail');
|
|
|
6
6
|
const models = require('../../models');
|
|
7
7
|
const signinEmail = require('./emails/signin');
|
|
8
8
|
const signupEmail = require('./emails/signup');
|
|
9
|
+
const signupPaidEmail = require('./emails/signup-paid');
|
|
9
10
|
const subscribeEmail = require('./emails/subscribe');
|
|
10
11
|
const updateEmail = require('./emails/updateEmail');
|
|
11
12
|
const SingleUseTokenProvider = require('./SingleUseTokenProvider');
|
|
@@ -49,6 +50,8 @@ function createApiInstance(config) {
|
|
|
49
50
|
return `📫 Confirm your subscription to ${siteTitle}`;
|
|
50
51
|
case 'signup':
|
|
51
52
|
return `🙌 Complete your sign up to ${siteTitle}!`;
|
|
53
|
+
case 'signup-paid':
|
|
54
|
+
return `🙌 Thank you for signing up to ${siteTitle}!`;
|
|
52
55
|
case 'updateEmail':
|
|
53
56
|
return `📫 Confirm your email update for ${siteTitle}!`;
|
|
54
57
|
case 'signin':
|
|
@@ -93,6 +96,23 @@ function createApiInstance(config) {
|
|
|
93
96
|
Sent to ${email}
|
|
94
97
|
If you did not make this request, you can simply delete this message. You will not be signed up, and no account will be created for you.
|
|
95
98
|
`;
|
|
99
|
+
case 'signup-paid':
|
|
100
|
+
return `
|
|
101
|
+
Hey there!
|
|
102
|
+
|
|
103
|
+
Thank you for subscribing to ${siteTitle}. Tap the link below to be automatically signed in:
|
|
104
|
+
|
|
105
|
+
${url}
|
|
106
|
+
|
|
107
|
+
For your security, the link will expire in 24 hours time.
|
|
108
|
+
|
|
109
|
+
See you soon!
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
Sent to ${email}
|
|
114
|
+
Thank you for subscribing to ${siteTitle}!
|
|
115
|
+
`;
|
|
96
116
|
case 'updateEmail':
|
|
97
117
|
return `
|
|
98
118
|
Hey there,
|
|
@@ -139,6 +159,8 @@ function createApiInstance(config) {
|
|
|
139
159
|
return subscribeEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
|
|
140
160
|
case 'signup':
|
|
141
161
|
return signupEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
|
|
162
|
+
case 'signup-paid':
|
|
163
|
+
return signupPaidEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
|
|
142
164
|
case 'updateEmail':
|
|
143
165
|
return updateEmail({url, email, siteTitle, accentColor, siteDomain, siteUrl});
|
|
144
166
|
case 'signin':
|
|
@@ -98,7 +98,7 @@ class MembersConfigProvider {
|
|
|
98
98
|
*/
|
|
99
99
|
getStripeKeys(type) {
|
|
100
100
|
if (type !== 'direct' && type !== 'connect') {
|
|
101
|
-
throw new errors.IncorrectUsageError(tpl(messages.incorrectKeyType));
|
|
101
|
+
throw new errors.IncorrectUsageError({message: tpl(messages.incorrectKeyType)});
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const secretKey = this._settingsCache.get(`stripe_${type === 'connect' ? 'connect_' : ''}secret_key`);
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
module.exports = ({siteTitle, email, url, accentColor = '#15212A', siteDomain, siteUrl}) => `
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta name="viewport" content="width=device-width">
|
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
7
|
+
<title>🙌 Thank you for signing up to ${siteTitle}!</title>
|
|
8
|
+
<style>
|
|
9
|
+
/* -------------------------------------
|
|
10
|
+
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
|
11
|
+
------------------------------------- */
|
|
12
|
+
@media only screen and (max-width: 620px) {
|
|
13
|
+
table[class=body] h1 {
|
|
14
|
+
font-size: 28px !important;
|
|
15
|
+
margin-bottom: 10px !important;
|
|
16
|
+
}
|
|
17
|
+
table[class=body] p,
|
|
18
|
+
table[class=body] ul,
|
|
19
|
+
table[class=body] ol,
|
|
20
|
+
table[class=body] td,
|
|
21
|
+
table[class=body] span,
|
|
22
|
+
table[class=body] a {
|
|
23
|
+
font-size: 16px !important;
|
|
24
|
+
}
|
|
25
|
+
table[class=body] .wrapper,
|
|
26
|
+
table[class=body] .article {
|
|
27
|
+
padding: 10px !important;
|
|
28
|
+
}
|
|
29
|
+
table[class=body] .content {
|
|
30
|
+
padding: 0 !important;
|
|
31
|
+
}
|
|
32
|
+
table[class=body] .container {
|
|
33
|
+
padding: 0 !important;
|
|
34
|
+
width: 100% !important;
|
|
35
|
+
}
|
|
36
|
+
table[class=body] .main {
|
|
37
|
+
border-left-width: 0 !important;
|
|
38
|
+
border-radius: 0 !important;
|
|
39
|
+
border-right-width: 0 !important;
|
|
40
|
+
}
|
|
41
|
+
table[class=body] .btn table {
|
|
42
|
+
width: 100% !important;
|
|
43
|
+
}
|
|
44
|
+
table[class=body] .btn a {
|
|
45
|
+
width: 100% !important;
|
|
46
|
+
}
|
|
47
|
+
table[class=body] .img-responsive {
|
|
48
|
+
height: auto !important;
|
|
49
|
+
max-width: 100% !important;
|
|
50
|
+
width: auto !important;
|
|
51
|
+
}
|
|
52
|
+
table[class=body] p[class=small],
|
|
53
|
+
table[class=body] a[class=small] {
|
|
54
|
+
font-size: 11px !important;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/* -------------------------------------
|
|
58
|
+
PRESERVE THESE STYLES IN THE HEAD
|
|
59
|
+
------------------------------------- */
|
|
60
|
+
@media all {
|
|
61
|
+
.ExternalClass {
|
|
62
|
+
width: 100%;
|
|
63
|
+
}
|
|
64
|
+
.ExternalClass,
|
|
65
|
+
.ExternalClass p,
|
|
66
|
+
.ExternalClass span,
|
|
67
|
+
.ExternalClass font,
|
|
68
|
+
.ExternalClass td,
|
|
69
|
+
.ExternalClass div {
|
|
70
|
+
line-height: 100%;
|
|
71
|
+
}
|
|
72
|
+
.recipient-link a {
|
|
73
|
+
color: inherit !important;
|
|
74
|
+
font-family: inherit !important;
|
|
75
|
+
font-size: inherit !important;
|
|
76
|
+
font-weight: inherit !important;
|
|
77
|
+
line-height: inherit !important;
|
|
78
|
+
text-decoration: none !important;
|
|
79
|
+
}
|
|
80
|
+
#MessageViewBody a {
|
|
81
|
+
color: inherit;
|
|
82
|
+
text-decoration: none;
|
|
83
|
+
font-size: inherit;
|
|
84
|
+
font-family: inherit;
|
|
85
|
+
font-weight: inherit;
|
|
86
|
+
line-height: inherit;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
hr {
|
|
90
|
+
border-width: 0;
|
|
91
|
+
height: 0;
|
|
92
|
+
margin-top: 34px;
|
|
93
|
+
margin-bottom: 34px;
|
|
94
|
+
border-bottom-width: 1px;
|
|
95
|
+
border-bottom-color: #EEF5F8;
|
|
96
|
+
}
|
|
97
|
+
a {
|
|
98
|
+
color: #3A464C;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
101
|
+
</head>
|
|
102
|
+
<body class="" style="background-color: #FFFFFF; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.5em; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
|
103
|
+
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #FFFFFF;">
|
|
104
|
+
<tr>
|
|
105
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
|
106
|
+
<td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; margin: 0 auto; max-width: 540px; padding: 10px; width: 540px;">
|
|
107
|
+
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 30px 20px;">
|
|
108
|
+
|
|
109
|
+
<!-- START CENTERED WHITE CONTAINER -->
|
|
110
|
+
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Thank you for subscribing to ${siteTitle}.</span>
|
|
111
|
+
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
|
|
112
|
+
|
|
113
|
+
<!-- START MAIN CONTENT AREA -->
|
|
114
|
+
<tr>
|
|
115
|
+
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
|
|
116
|
+
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
|
117
|
+
<tr>
|
|
118
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
|
|
119
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 20px; color: #15212A; font-weight: bold; line-height: 25px; margin: 0; margin-bottom: 15px;">Hey there!</p>
|
|
120
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 32px;">Thank you for subscribing to ${siteTitle}. Tap the link below to be automatically signed in:</p>
|
|
121
|
+
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
|
122
|
+
<tbody>
|
|
123
|
+
<tr>
|
|
124
|
+
<td align="left" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; padding-bottom: 35px;">
|
|
125
|
+
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
|
126
|
+
<tbody>
|
|
127
|
+
<tr>
|
|
128
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top; background-color: ${accentColor}; border-radius: 5px; text-align: center;"> <a href="${url}" target="_blank" style="display: inline-block; color: #ffffff; background-color: ${accentColor}; border: solid 1px ${accentColor}; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: ${accentColor};">Sign in</a> </td>
|
|
129
|
+
</tr>
|
|
130
|
+
</tbody>
|
|
131
|
+
</table>
|
|
132
|
+
</td>
|
|
133
|
+
</tr>
|
|
134
|
+
</tbody>
|
|
135
|
+
</table>
|
|
136
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 25px;">For your security, the link will expire in 24 hours time.</p>
|
|
137
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 30px;">See you soon!</p>
|
|
138
|
+
<hr/>
|
|
139
|
+
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; color: #3A464C; font-weight: normal; line-height: 25px; margin: 0; margin-bottom: 5px;">You can also copy & paste this URL into your browser:</p>
|
|
140
|
+
<p style="word-break: break-all; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 15px; line-height: 25px; margin-top: 0; color: #3A464C;">${url}</p>
|
|
141
|
+
</td>
|
|
142
|
+
</tr>
|
|
143
|
+
</table>
|
|
144
|
+
</td>
|
|
145
|
+
</tr>
|
|
146
|
+
|
|
147
|
+
<!-- START FOOTER -->
|
|
148
|
+
<tr>
|
|
149
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px;">
|
|
150
|
+
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;">This message was sent from <a class="small" href="${siteUrl}" style="text-decoration: underline; color: #738A94; font-size: 11px;">${siteDomain}</a> to <a class="small" href="mailto:${email}" style="text-decoration: underline; color: #738A94; font-size: 11px;">${email}</a></p>
|
|
151
|
+
</td>
|
|
152
|
+
</tr>
|
|
153
|
+
|
|
154
|
+
<!-- END FOOTER -->
|
|
155
|
+
|
|
156
|
+
<!-- END MAIN CONTENT AREA -->
|
|
157
|
+
</table>
|
|
158
|
+
|
|
159
|
+
<!-- END CENTERED WHITE CONTAINER -->
|
|
160
|
+
</div>
|
|
161
|
+
</td>
|
|
162
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
|
163
|
+
</tr>
|
|
164
|
+
</table>
|
|
165
|
+
</body>
|
|
166
|
+
</html>
|
|
167
|
+
`;
|
|
168
|
+
|
|
@@ -159,12 +159,16 @@ const membersService = {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
if (stripeService.api.configured && stripeService.api.mode === 'live') {
|
|
162
|
-
throw new errors.IncorrectUsageError(
|
|
162
|
+
throw new errors.IncorrectUsageError({
|
|
163
|
+
message: tpl(messages.noLiveKeysInDevelopment)
|
|
164
|
+
});
|
|
163
165
|
}
|
|
164
166
|
} else {
|
|
165
167
|
const siteUrl = urlUtils.getSiteUrl();
|
|
166
168
|
if (!/^https/.test(siteUrl) && stripeService.api.configured) {
|
|
167
|
-
throw new errors.IncorrectUsageError(
|
|
169
|
+
throw new errors.IncorrectUsageError({
|
|
170
|
+
message: tpl(messages.sslRequiredForStripe)
|
|
171
|
+
});
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
if (!membersApi) {
|
|
@@ -63,7 +63,7 @@ async function getStripeConnectTokenData(encodedData, getSessionProp) {
|
|
|
63
63
|
const state = await getSessionProp(STATE_PROP);
|
|
64
64
|
|
|
65
65
|
if (state !== data.s) {
|
|
66
|
-
throw new errors.NoPermissionError(tpl(messages.incorrectState));
|
|
66
|
+
throw new errors.NoPermissionError({message: tpl(messages.incorrectState)});
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return {
|
|
@@ -81,7 +81,9 @@ function checkCanConnect() {
|
|
|
81
81
|
const siteUrlUsingSSL = /^https/.test(siteUrl);
|
|
82
82
|
const cannotConnectToStripe = productionMode && !siteUrlUsingSSL;
|
|
83
83
|
if (cannotConnectToStripe) {
|
|
84
|
-
throw new errors.BadRequestError(
|
|
84
|
+
throw new errors.BadRequestError({
|
|
85
|
+
message: 'Cannot connect to stripe unless site is using https://'
|
|
86
|
+
});
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -35,13 +35,18 @@ class NFTOEmbedProvider {
|
|
|
35
35
|
if (!match) {
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
|
+
const headers = {};
|
|
39
|
+
if (this.dependencies.config.apiKey) {
|
|
40
|
+
headers['X-API-KEY'] = this.dependencies.config.apiKey;
|
|
41
|
+
}
|
|
38
42
|
const result = await externalRequest(`https://api.opensea.io/api/v1/asset/${transaction}/${asset}/?format=json`, {
|
|
39
|
-
json: true
|
|
43
|
+
json: true,
|
|
44
|
+
headers
|
|
40
45
|
});
|
|
41
46
|
return {
|
|
42
47
|
version: '1.0',
|
|
43
48
|
type: 'nft',
|
|
44
|
-
title: result.body.name
|
|
49
|
+
title: result.body.name ? result.body.name : `#${result.body.token_id}`,
|
|
45
50
|
author_name: result.body.creator.user.username,
|
|
46
51
|
author_url: `https://opensea.io/${result.body.creator.user.username}`,
|
|
47
52
|
provider_name: 'OpenSea',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const errors = require('@tryghost/errors');
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
3
|
const logging = require('@tryghost/logging');
|
|
4
|
+
const sentry = require('../../shared/sentry');
|
|
4
5
|
const {extract, hasProvider} = require('oembed-parser');
|
|
5
6
|
const cheerio = require('cheerio');
|
|
6
7
|
const _ = require('lodash');
|
|
@@ -128,7 +129,14 @@ class OEmbed {
|
|
|
128
129
|
const response = await this.externalRequest(url, {cookieJar});
|
|
129
130
|
|
|
130
131
|
const html = response.body;
|
|
131
|
-
|
|
132
|
+
try {
|
|
133
|
+
scraperResponse = await metascraper({html, url});
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// Log to avoid being blind to errors happenning in metascraper
|
|
136
|
+
sentry.captureException(err);
|
|
137
|
+
logging.error(err);
|
|
138
|
+
return this.unknownProvider(url);
|
|
139
|
+
}
|
|
132
140
|
|
|
133
141
|
const metadata = Object.assign({}, scraperResponse, {
|
|
134
142
|
thumbnail: scraperResponse.image,
|
|
@@ -284,6 +292,10 @@ class OEmbed {
|
|
|
284
292
|
try {
|
|
285
293
|
const urlObject = new URL(url);
|
|
286
294
|
|
|
295
|
+
// Trimming solves the difference of url validation between `new URL(url)`
|
|
296
|
+
// and metascraper.
|
|
297
|
+
url = url.trim();
|
|
298
|
+
|
|
287
299
|
for (const provider of this.customProviders) {
|
|
288
300
|
if (await provider.canSupportRequest(urlObject)) {
|
|
289
301
|
const result = await provider.getOEmbedData(urlObject, this.externalRequest);
|
|
@@ -314,12 +326,12 @@ class OEmbed {
|
|
|
314
326
|
return data;
|
|
315
327
|
} catch (err) {
|
|
316
328
|
// allow specific validation errors through for better error messages
|
|
317
|
-
if (errors.utils.
|
|
329
|
+
if (errors.utils.isGhostError(err) && err.errorType === 'ValidationError') {
|
|
318
330
|
throw err;
|
|
319
331
|
}
|
|
320
332
|
|
|
321
333
|
// log the real error because we're going to throw a generic "Unknown provider" error
|
|
322
|
-
logging.error(new errors.
|
|
334
|
+
logging.error(new errors.InternalServerError({
|
|
323
335
|
message: 'Encountered error when fetching oembed',
|
|
324
336
|
err
|
|
325
337
|
}));
|
|
@@ -128,7 +128,7 @@ CanThisResult.prototype.beginCheck = function (context) {
|
|
|
128
128
|
context = parseContext(context);
|
|
129
129
|
|
|
130
130
|
if (actionsMap.empty()) {
|
|
131
|
-
throw new errors.
|
|
131
|
+
throw new errors.InternalServerError({message: tpl(messages.noActionsMapFoundError)});
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Kick off loading of user permissions if necessary
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const moment = require('moment-timezone');
|
|
4
3
|
const yaml = require('js-yaml');
|
|
5
4
|
|
|
6
5
|
const logging = require('@tryghost/logging');
|
|
7
6
|
const tpl = require('@tryghost/tpl');
|
|
8
7
|
const errors = require('@tryghost/errors');
|
|
9
8
|
|
|
10
|
-
const validation = require('./validation');
|
|
11
|
-
|
|
12
9
|
const messages = {
|
|
13
10
|
jsonParse: 'Could not parse JSON: {context}.',
|
|
14
11
|
yamlParse: 'Could not parse YAML: {context}.',
|
|
@@ -22,7 +19,7 @@ const messages = {
|
|
|
22
19
|
* @typedef {Object} RedirectConfig
|
|
23
20
|
* @property {String} from - Defines the relative incoming URL or pattern (regex)
|
|
24
21
|
* @property {String} to - Defines where the incoming traffic should be redirected to, which can be a static URL, or a dynamic value using regex (example: "to": "/$1/")
|
|
25
|
-
* @property {boolean} permanent - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
22
|
+
* @property {boolean} [permanent] - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
26
23
|
*/
|
|
27
24
|
|
|
28
25
|
/**
|
|
@@ -37,7 +34,7 @@ const readRedirectsFile = async (redirectsPath) => {
|
|
|
37
34
|
return '';
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
if (errors.utils.
|
|
37
|
+
if (errors.utils.isGhostError(err)) {
|
|
41
38
|
throw err;
|
|
42
39
|
}
|
|
43
40
|
|
|
@@ -120,16 +117,6 @@ const parseRedirectsFile = (content, ext) => {
|
|
|
120
117
|
throw new errors.IncorrectUsageError();
|
|
121
118
|
};
|
|
122
119
|
|
|
123
|
-
/**
|
|
124
|
-
* @param {string} filePath
|
|
125
|
-
* @returns {string}
|
|
126
|
-
*/
|
|
127
|
-
const getBackupRedirectsFilePath = (filePath) => {
|
|
128
|
-
const {dir, name, ext} = path.parse(filePath);
|
|
129
|
-
|
|
130
|
-
return path.join(dir, `${name}-${moment().format('YYYY-MM-DD-HH-mm-ss')}${ext}`);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
120
|
/**
|
|
134
121
|
* @typedef {object} IRedirectManager
|
|
135
122
|
*/
|
|
@@ -138,14 +125,22 @@ class CustomRedirectsAPI {
|
|
|
138
125
|
/**
|
|
139
126
|
* @param {object} config
|
|
140
127
|
* @param {string} config.basePath
|
|
141
|
-
*
|
|
142
|
-
* @param {
|
|
128
|
+
* @param {Function} config.validate - validates redirects configuration
|
|
129
|
+
* @param {Function} config.getBackupFilePath
|
|
130
|
+
* @param {IRedirectManager} config.redirectManager
|
|
143
131
|
*/
|
|
144
|
-
constructor(
|
|
132
|
+
constructor({basePath, validate, redirectManager, getBackupFilePath}) {
|
|
145
133
|
/** @private */
|
|
146
|
-
this.
|
|
134
|
+
this.basePath = basePath;
|
|
135
|
+
|
|
147
136
|
/** @private */
|
|
148
137
|
this.redirectManager = redirectManager;
|
|
138
|
+
|
|
139
|
+
/** @private */
|
|
140
|
+
this.validate = validate;
|
|
141
|
+
|
|
142
|
+
/** @private */
|
|
143
|
+
this.getBackupFilePath = getBackupFilePath;
|
|
149
144
|
}
|
|
150
145
|
|
|
151
146
|
async init() {
|
|
@@ -159,7 +154,7 @@ class CustomRedirectsAPI {
|
|
|
159
154
|
const content = await readRedirectsFile(filePath);
|
|
160
155
|
const ext = path.extname(filePath);
|
|
161
156
|
const redirects = parseRedirectsFile(content, ext);
|
|
162
|
-
|
|
157
|
+
this.validate(redirects);
|
|
163
158
|
|
|
164
159
|
this.redirectManager.removeAllRedirects();
|
|
165
160
|
for (const redirect of redirects) {
|
|
@@ -167,7 +162,7 @@ class CustomRedirectsAPI {
|
|
|
167
162
|
}
|
|
168
163
|
}
|
|
169
164
|
} catch (err) {
|
|
170
|
-
if (errors.utils.
|
|
165
|
+
if (errors.utils.isGhostError(err)) {
|
|
171
166
|
logging.error(err);
|
|
172
167
|
} else {
|
|
173
168
|
logging.error(new errors.IncorrectUsageError({
|
|
@@ -187,7 +182,7 @@ class CustomRedirectsAPI {
|
|
|
187
182
|
* @returns {string}
|
|
188
183
|
*/
|
|
189
184
|
createRedirectsFilePath(ext) {
|
|
190
|
-
return path.join(this.
|
|
185
|
+
return path.join(this.basePath, `redirects${ext}`);
|
|
191
186
|
}
|
|
192
187
|
|
|
193
188
|
/**
|
|
@@ -222,7 +217,7 @@ class CustomRedirectsAPI {
|
|
|
222
217
|
const redirectsFilePath = await this.getRedirectsFilePath();
|
|
223
218
|
|
|
224
219
|
if (redirectsFilePath) {
|
|
225
|
-
const backupRedirectsPath =
|
|
220
|
+
const backupRedirectsPath = this.getBackupFilePath(redirectsFilePath);
|
|
226
221
|
|
|
227
222
|
const backupExists = await fs.pathExists(backupRedirectsPath);
|
|
228
223
|
if (backupExists) {
|
|
@@ -234,10 +229,10 @@ class CustomRedirectsAPI {
|
|
|
234
229
|
|
|
235
230
|
const content = await readRedirectsFile(filePath);
|
|
236
231
|
const parsed = parseRedirectsFile(content, ext);
|
|
237
|
-
|
|
232
|
+
this.validate(parsed);
|
|
238
233
|
|
|
239
234
|
if (ext === '.json') {
|
|
240
|
-
await fs.writeFile(this.createRedirectsFilePath('.json'), JSON.stringify(
|
|
235
|
+
await fs.writeFile(this.createRedirectsFilePath('.json'), JSON.stringify(parsed), 'utf-8');
|
|
241
236
|
} else if (ext === '.yaml') {
|
|
242
237
|
await fs.copy(filePath, this.createRedirectsFilePath('.yaml'));
|
|
243
238
|
}
|
|
@@ -3,21 +3,27 @@ const urlUtils = require('../../../shared/url-utils');
|
|
|
3
3
|
|
|
4
4
|
const DynamicRedirectManager = require('@tryghost/express-dynamic-redirects');
|
|
5
5
|
const CustomRedirectsAPI = require('./api');
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
permanentMaxAge: config.get('caching:customRedirects:maxAge'),
|
|
9
|
-
getSubdirectoryURL: (pathname) => {
|
|
10
|
-
return urlUtils.urlJoin(urlUtils.getSubdir(), pathname);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
6
|
+
const validation = require('./validation');
|
|
7
|
+
const {getBackupRedirectsFilePath} = require('./utils');
|
|
13
8
|
|
|
14
9
|
let customRedirectsAPI;
|
|
10
|
+
let redirectManager;
|
|
15
11
|
|
|
16
12
|
module.exports = {
|
|
17
13
|
init() {
|
|
14
|
+
redirectManager = new DynamicRedirectManager({
|
|
15
|
+
permanentMaxAge: config.get('caching:customRedirects:maxAge'),
|
|
16
|
+
getSubdirectoryURL: (pathname) => {
|
|
17
|
+
return urlUtils.urlJoin(urlUtils.getSubdir(), pathname);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
18
21
|
customRedirectsAPI = new CustomRedirectsAPI({
|
|
19
|
-
basePath: config.getContentPath('data')
|
|
20
|
-
|
|
22
|
+
basePath: config.getContentPath('data'),
|
|
23
|
+
redirectManager,
|
|
24
|
+
getBackupFilePath: getBackupRedirectsFilePath,
|
|
25
|
+
validate: validation.validate.bind(validation)
|
|
26
|
+
});
|
|
21
27
|
|
|
22
28
|
return customRedirectsAPI.init();
|
|
23
29
|
},
|
|
@@ -26,5 +32,7 @@ module.exports = {
|
|
|
26
32
|
return customRedirectsAPI;
|
|
27
33
|
},
|
|
28
34
|
|
|
29
|
-
middleware
|
|
35
|
+
get middleware() {
|
|
36
|
+
return redirectManager.handleRequest;
|
|
37
|
+
}
|
|
30
38
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const moment = require('moment-timezone');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} filePath
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
const getBackupRedirectsFilePath = (filePath) => {
|
|
9
|
+
const {dir, name, ext} = path.parse(filePath);
|
|
10
|
+
|
|
11
|
+
return path.join(dir, `${name}-${moment().format('YYYY-MM-DD-HH-mm-ss')}${ext}`);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports.getBackupRedirectsFilePath = getBackupRedirectsFilePath;
|