ghost 5.115.1 → 5.116.1
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-api-framework-5.115.1.tgz → tryghost-api-framework-5.116.1.tgz} +0 -0
- package/components/tryghost-constants-5.116.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.116.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.115.1.tgz → tryghost-custom-theme-settings-service-5.116.1.tgz} +0 -0
- package/components/{tryghost-domain-events-5.115.1.tgz → tryghost-domain-events-5.116.1.tgz} +0 -0
- package/components/{tryghost-donations-5.115.1.tgz → tryghost-donations-5.116.1.tgz} +0 -0
- package/components/tryghost-email-addresses-5.116.1.tgz +0 -0
- package/components/tryghost-email-service-5.116.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.116.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.116.1.tgz +0 -0
- package/components/tryghost-i18n-5.116.1.tgz +0 -0
- package/components/tryghost-job-manager-5.116.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.116.1.tgz +0 -0
- package/components/tryghost-magic-link-5.116.1.tgz +0 -0
- package/components/{tryghost-member-attribution-5.115.1.tgz → tryghost-member-attribution-5.116.1.tgz} +0 -0
- package/components/tryghost-member-events-5.116.1.tgz +0 -0
- package/components/tryghost-members-api-5.116.1.tgz +0 -0
- package/components/tryghost-members-csv-5.116.1.tgz +0 -0
- package/components/{tryghost-members-offers-5.115.1.tgz → tryghost-members-offers-5.116.1.tgz} +0 -0
- package/components/{tryghost-milestones-5.115.1.tgz → tryghost-milestones-5.116.1.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.115.1.tgz → tryghost-mw-error-handler-5.116.1.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.116.1.tgz +0 -0
- package/components/{tryghost-post-events-5.115.1.tgz → tryghost-post-events-5.116.1.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.115.1.tgz → tryghost-post-revisions-5.116.1.tgz} +0 -0
- package/components/tryghost-posts-service-5.116.1.tgz +0 -0
- package/components/{tryghost-prometheus-metrics-5.115.1.tgz → tryghost-prometheus-metrics-5.116.1.tgz} +0 -0
- package/components/tryghost-security-5.116.1.tgz +0 -0
- package/components/{tryghost-tiers-5.115.1.tgz → tryghost-tiers-5.116.1.tgz} +0 -0
- package/components/tryghost-webmentions-5.116.1.tgz +0 -0
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/source/LICENSE +1 -1
- package/content/themes/source/README.md +1 -1
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/screen.css.map +1 -1
- package/content/themes/source/assets/css/screen.css +6 -11
- package/content/themes/source/partials/feature-image.hbs +2 -2
- package/core/boot.js +0 -42
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +24764 -24129
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-15df2af5.mjs → index-a9601514.mjs} +3 -3
- package/core/built/admin/assets/admin-x-demo/{modals-8ca61d78.mjs → modals-c1789d04.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-d2e6872f.mjs → CodeEditorView-e9c9deb8.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8e8821e5.mjs → index-84580c3a.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-f5cb3db3.mjs → index-f744cab7.mjs} +49 -35
- package/core/built/admin/assets/admin-x-settings/{modals-e8ae4d46.mjs → modals-d9ca60c5.mjs} +1198 -1192
- package/core/built/admin/assets/chunk.524.cb72a86e19c9ffd6172e.js +35 -0
- package/core/built/admin/assets/chunk.582.4f4d38ffe79fbdbd26f7.js +37 -0
- package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
- package/core/built/admin/assets/{ghost-df7b9558260aa27d18b195ee895b487d.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +144 -145
- package/core/built/admin/assets/ghost-ba58e9822f7384461e926c7e23f04a75.css +1 -0
- package/core/built/admin/assets/ghost-dark-f1f29683b14ffa11615b3bba8b6ab92c.css +1 -0
- package/core/built/admin/assets/koenig-lexical/index.css +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +20563 -20891
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +139 -139
- package/core/built/admin/assets/posts/posts.js +5732 -5667
- package/core/built/admin/assets/stats/stats.js +71082 -7533
- package/core/built/admin/assets/{vendor-68a4aa424a179a90f5bbc2b750def576.js → vendor-72026232b36d97babc6320917c16c321.js} +36 -34
- package/core/built/admin/index.html +6 -6
- package/core/cli/generate-data.js +1 -1
- package/core/frontend/helpers/ghost_head.js +6 -1
- package/core/frontend/public/ghost-stats.js +55 -2
- package/core/frontend/services/assets-minification/AdminAuthAssets.js +2 -1
- package/core/frontend/services/assets-minification/CardAssets.js +1 -1
- package/core/frontend/services/assets-minification/CommentCountsAssets.js +1 -1
- package/core/frontend/services/assets-minification/MemberAttributionAssets.js +1 -1
- package/core/frontend/services/assets-minification/Minifier.js +191 -0
- package/core/frontend/services/routing/controllers/previews.js +2 -1
- package/core/server/adapters/cache/Redis.js +1 -1
- package/core/server/adapters/lib/redis/AdapterCacheRedis.js +287 -0
- package/core/server/adapters/lib/redis/redis-store-factory.js +22 -0
- package/core/server/api/endpoints/posts.js +9 -3
- package/core/server/api/endpoints/previews.js +35 -1
- package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -9
- package/core/server/api/endpoints/utils/validators/input/settings.js +1 -1
- package/core/server/data/db/connection.js +2 -0
- package/core/server/data/db/index.js +1 -0
- package/core/server/data/importer/handlers/ImporterContentFileHandler.js +90 -0
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/seeders/DataGenerator.js +288 -0
- package/core/server/data/seeders/importers/BenefitsImporter.js +28 -0
- package/core/server/data/seeders/importers/CommentsImporter.js +73 -0
- package/core/server/data/seeders/importers/EmailBatchesImporter.js +38 -0
- package/core/server/data/seeders/importers/EmailRecipientFailuresImporter.js +67 -0
- package/core/server/data/seeders/importers/EmailRecipientsImporter.js +212 -0
- package/core/server/data/seeders/importers/EmailsImporter.js +99 -0
- package/core/server/data/seeders/importers/LabelsImporter.js +41 -0
- package/core/server/data/seeders/importers/MembersClickEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersCreatedEventsImporter.js +103 -0
- package/core/server/data/seeders/importers/MembersFeedbackImporter.js +45 -0
- package/core/server/data/seeders/importers/MembersImporter.js +111 -0
- package/core/server/data/seeders/importers/MembersLabelsImporter.js +39 -0
- package/core/server/data/seeders/importers/MembersLoginEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersNewslettersImporter.js +38 -0
- package/core/server/data/seeders/importers/MembersPaidSubscriptionEventsImporter.js +99 -0
- package/core/server/data/seeders/importers/MembersProductsImporter.js +42 -0
- package/core/server/data/seeders/importers/MembersStatusEventsImporter.js +58 -0
- package/core/server/data/seeders/importers/MembersStripeCustomersImporter.js +60 -0
- package/core/server/data/seeders/importers/MembersStripeCustomersSubscriptionsImporter.js +259 -0
- package/core/server/data/seeders/importers/MembersSubscribeEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersSubscriptionCreatedEventsImporter.js +95 -0
- package/core/server/data/seeders/importers/NewslettersImporter.js +40 -0
- package/core/server/data/seeders/importers/OffersImporter.js +70 -0
- package/core/server/data/seeders/importers/PostsAuthorsImporter.js +32 -0
- package/core/server/data/seeders/importers/PostsImporter.js +102 -0
- package/core/server/data/seeders/importers/PostsProductsImporter.js +35 -0
- package/core/server/data/seeders/importers/PostsTagsImporter.js +46 -0
- package/core/server/data/seeders/importers/ProductsBenefitsImporter.js +54 -0
- package/core/server/data/seeders/importers/ProductsImporter.js +90 -0
- package/core/server/data/seeders/importers/RecommendationClickEventsImporter.js +32 -0
- package/core/server/data/seeders/importers/RecommendationSubscribeEventsImporter.js +32 -0
- package/core/server/data/seeders/importers/RecommendationsImporter.js +34 -0
- package/core/server/data/seeders/importers/RedirectsImporter.js +49 -0
- package/core/server/data/seeders/importers/RolesUsersImporter.js +42 -0
- package/core/server/data/seeders/importers/StripePricesImporter.js +69 -0
- package/core/server/data/seeders/importers/StripeProductsImporter.js +34 -0
- package/core/server/data/seeders/importers/TableImporter.js +187 -0
- package/core/server/data/seeders/importers/TagsImporter.js +41 -0
- package/core/server/data/seeders/importers/UsersImporter.js +31 -0
- package/core/server/data/seeders/importers/WebMentionsImporter.js +42 -0
- package/core/server/data/seeders/importers/index.js +41 -0
- package/core/server/data/seeders/utils/JsonImporter.js +39 -0
- package/core/server/data/seeders/utils/blog-info.js +3 -0
- package/core/server/data/seeders/utils/database-date.js +7 -0
- package/core/server/data/seeders/utils/event-generator.js +48 -0
- package/core/server/data/seeders/utils/random.js +13 -0
- package/core/server/data/seeders/utils/topological-sort.js +33 -0
- package/core/server/services/adapter-manager/AdapterManager.js +161 -0
- package/core/server/services/adapter-manager/index.js +1 -1
- package/core/server/services/announcement-bar-service/AnnouncementBarSettings.js +54 -0
- package/core/server/services/announcement-bar-service/AnnouncementVisibilityValues.js +11 -0
- package/core/server/services/announcement-bar-service/index.js +1 -1
- package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +1 -1
- package/core/server/services/auth/session/session-service.js +15 -5
- package/core/server/services/custom-redirects/index.js +1 -1
- package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +1 -1
- package/core/server/services/email-service/EmailServiceWrapper.js +4 -4
- package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +1 -1
- package/core/server/services/email-suppression-list/service.js +1 -1
- package/core/server/services/lib/DynamicRedirectManager.js +156 -0
- package/core/server/services/lib/EmailContentGenerator.js +54 -0
- package/core/server/services/lib/InMemoryRepository.js +62 -0
- package/core/server/services/lib/InMemoryRepository.ts +80 -0
- package/core/server/services/lib/MailgunClient.js +364 -0
- package/core/server/services/link-redirection/LinkRedirect.js +26 -0
- package/core/server/services/link-redirection/LinkRedirectRepository.js +7 -7
- package/core/server/services/link-redirection/LinkRedirectsService.js +123 -0
- package/core/server/services/link-redirection/README.md +151 -0
- package/core/server/services/link-redirection/RedirectEvent.js +24 -0
- package/core/server/services/link-redirection/index.js +1 -1
- package/core/server/services/link-tracking/LinkClickTrackingService.js +1 -1
- package/core/server/services/mail/index.js +1 -1
- package/core/server/services/mail-events/InMemoryMailEventRepository.js +2 -2
- package/core/server/services/mail-events/InMemoryMailEventRepository.ts +1 -1
- package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +8 -8
- package/core/server/services/recommendations/service/BookshelfClickEventRepository.js +48 -0
- package/core/server/services/recommendations/service/BookshelfClickEventRepository.ts +49 -0
- package/core/server/services/recommendations/service/BookshelfRecommendationRepository.js +98 -0
- package/core/server/services/recommendations/service/BookshelfRecommendationRepository.ts +117 -0
- package/core/server/services/recommendations/service/BookshelfRepository.js +134 -0
- package/core/server/services/recommendations/service/BookshelfRepository.ts +196 -0
- package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.js +48 -0
- package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.ts +49 -0
- package/core/server/services/recommendations/service/ClickEvent.js +33 -0
- package/core/server/services/recommendations/service/ClickEvent.ts +32 -0
- package/core/server/services/recommendations/service/InMemoryRecommendationRepository.js +19 -0
- package/core/server/services/recommendations/service/InMemoryRecommendationRepository.ts +20 -0
- package/core/server/services/recommendations/service/IncomingRecommendationController.js +34 -0
- package/core/server/services/recommendations/service/IncomingRecommendationController.ts +51 -0
- package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.js +25 -0
- package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.ts +37 -0
- package/core/server/services/recommendations/service/IncomingRecommendationService.js +93 -0
- package/core/server/services/recommendations/service/IncomingRecommendationService.ts +160 -0
- package/core/server/services/recommendations/service/Recommendation.js +140 -0
- package/core/server/services/recommendations/service/Recommendation.ts +201 -0
- package/core/server/services/recommendations/service/RecommendationController.js +208 -0
- package/core/server/services/recommendations/service/RecommendationController.ts +258 -0
- package/core/server/services/recommendations/service/RecommendationMetadataService.js +86 -0
- package/core/server/services/recommendations/service/RecommendationMetadataService.ts +128 -0
- package/core/server/services/recommendations/service/RecommendationRepository.js +2 -0
- package/core/server/services/recommendations/service/RecommendationRepository.ts +13 -0
- package/core/server/services/recommendations/service/RecommendationService.js +228 -0
- package/core/server/services/recommendations/service/RecommendationService.ts +281 -0
- package/core/server/services/recommendations/service/SubscribeEvent.js +33 -0
- package/core/server/services/recommendations/service/SubscribeEvent.ts +32 -0
- package/core/server/services/recommendations/service/UnsafeData.js +183 -0
- package/core/server/services/recommendations/service/UnsafeData.ts +217 -0
- package/core/server/services/recommendations/service/WellknownService.js +36 -0
- package/core/server/services/recommendations/service/WellknownService.ts +47 -0
- package/core/server/services/recommendations/service/index.js +31 -0
- package/core/server/services/recommendations/service/index.ts +15 -0
- package/core/server/services/recommendations/service/libraries.d.ts +5 -0
- package/core/server/services/slack-notifications/SlackNotifications.js +211 -0
- package/core/server/services/slack-notifications/SlackNotificationsService.js +90 -0
- package/core/server/services/slack-notifications/service.js +4 -6
- package/core/server/web/api/endpoints/admin/app.js +1 -21
- package/core/server/web/api/middleware/version-match.js +41 -0
- package/core/shared/labs.js +2 -2
- package/package.json +87 -104
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +1470 -1540
- package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.115.1.tgz +0 -0
- package/components/tryghost-constants-5.115.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
- package/components/tryghost-data-generator-5.115.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.115.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.115.1.tgz +0 -0
- package/components/tryghost-email-events-5.115.1.tgz +0 -0
- package/components/tryghost-email-service-5.115.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.115.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.115.1.tgz +0 -0
- package/components/tryghost-ghost-5.115.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
- package/components/tryghost-i18n-5.115.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.115.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
- package/components/tryghost-job-manager-5.115.1.tgz +0 -0
- package/components/tryghost-link-redirects-5.115.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
- package/components/tryghost-magic-link-5.115.1.tgz +0 -0
- package/components/tryghost-mailgun-client-5.115.1.tgz +0 -0
- package/components/tryghost-member-events-5.115.1.tgz +0 -0
- package/components/tryghost-members-api-5.115.1.tgz +0 -0
- package/components/tryghost-members-csv-5.115.1.tgz +0 -0
- package/components/tryghost-members-payments-5.115.1.tgz +0 -0
- package/components/tryghost-minifier-5.115.1.tgz +0 -0
- package/components/tryghost-mw-version-match-5.115.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
- package/components/tryghost-posts-service-5.115.1.tgz +0 -0
- package/components/tryghost-recommendations-5.115.1.tgz +0 -0
- package/components/tryghost-security-5.115.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
- package/components/tryghost-webmentions-5.115.1.tgz +0 -0
- package/core/built/admin/assets/chunk.524.2439684964c164c598ab.js +0 -35
- package/core/built/admin/assets/chunk.582.bf5a2bbb2c4eb69ef1e7.js +0 -37
- package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +0 -1
- package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +0 -1
- /package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js.LICENSE.txt → chunk.713.e9027c0cc3c56110f5da.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const probabilityDistributions = require('probability-distributions');
|
|
2
|
+
|
|
3
|
+
const generateEvents = ({
|
|
4
|
+
shape = 'flat',
|
|
5
|
+
trend = 'positive',
|
|
6
|
+
total = 0,
|
|
7
|
+
startTime = new Date(),
|
|
8
|
+
endTime = new Date()
|
|
9
|
+
} = {}) => {
|
|
10
|
+
if (total <= 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let alpha = 0;
|
|
15
|
+
let beta = 0;
|
|
16
|
+
let positiveTrend = trend === 'positive';
|
|
17
|
+
switch (shape) {
|
|
18
|
+
case 'linear':
|
|
19
|
+
alpha = 2;
|
|
20
|
+
beta = 1;
|
|
21
|
+
break;
|
|
22
|
+
case 'ease-in':
|
|
23
|
+
alpha = 4;
|
|
24
|
+
beta = 1;
|
|
25
|
+
break;
|
|
26
|
+
case 'ease-out':
|
|
27
|
+
alpha = 1;
|
|
28
|
+
beta = 4;
|
|
29
|
+
positiveTrend = !positiveTrend;
|
|
30
|
+
break;
|
|
31
|
+
case 'flat':
|
|
32
|
+
alpha = 1;
|
|
33
|
+
beta = 1;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data = probabilityDistributions.rbeta(total, alpha, beta, 0);
|
|
38
|
+
const startTimeValue = startTime.valueOf();
|
|
39
|
+
const timeDifference = endTime.valueOf() - startTimeValue;
|
|
40
|
+
return data.map((x) => {
|
|
41
|
+
if (!positiveTrend) {
|
|
42
|
+
x = 1 - x;
|
|
43
|
+
}
|
|
44
|
+
return new Date(startTimeValue + timeDifference * x);
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports = generateEvents;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const {faker} = require('@faker-js/faker');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds another degree of randomness into some decisions
|
|
5
|
+
* @param {number} lowerThan Only this % of people will achieve this luck
|
|
6
|
+
* @returns {boolean} Whether this person is lucky enough for the condition
|
|
7
|
+
*/
|
|
8
|
+
const luck = lowerThan => faker.datatype.number({
|
|
9
|
+
min: 1,
|
|
10
|
+
max: 100
|
|
11
|
+
}) <= lowerThan;
|
|
12
|
+
|
|
13
|
+
module.exports = {luck};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This sorting algorithm is used to make sure that dependent tables are imported after their dependencies.
|
|
3
|
+
* @param {Array<Object>} objects Objects with a name and dependencies properties
|
|
4
|
+
* @returns Topologically sorted array of objects
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function topologicalSort(objects) {
|
|
7
|
+
// Create an empty result array to store the ordered objects
|
|
8
|
+
const result = [];
|
|
9
|
+
// Create a set to track visited objects during the DFS
|
|
10
|
+
const visited = new Set();
|
|
11
|
+
|
|
12
|
+
// Helper function to perform DFS
|
|
13
|
+
function dfs(name) {
|
|
14
|
+
if (visited.has(name)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
visited.add(name);
|
|
19
|
+
const dependencies = objects.find(item => item.name === name)?.dependencies || [];
|
|
20
|
+
for (const dependency of dependencies) {
|
|
21
|
+
dfs(dependency);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
result.push(objects.find(item => item.name === name));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Perform DFS on each object
|
|
28
|
+
for (const object of objects) {
|
|
29
|
+
dfs(object.name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const errors = require('@tryghost/errors');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef { function(new: Adapter, object) } AdapterConstructor
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} Adapter
|
|
10
|
+
* @prop {string[]} requiredFns
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
module.exports = class AdapterManager {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} config
|
|
16
|
+
* @param {string[]} config.pathsToAdapters The paths to check, e.g. ['content/adapters', 'core/server/adapters']
|
|
17
|
+
* @param {(path: string) => AdapterConstructor} config.loadAdapterFromPath A function to load adapters, e.g. global.require
|
|
18
|
+
*/
|
|
19
|
+
constructor({pathsToAdapters, loadAdapterFromPath}) {
|
|
20
|
+
/**
|
|
21
|
+
* @private
|
|
22
|
+
* @type {Object.<string, AdapterConstructor>}
|
|
23
|
+
*/
|
|
24
|
+
this.baseClasses = {};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @private
|
|
28
|
+
* @type {Object.<string, Object.<string, Adapter>>}
|
|
29
|
+
*/
|
|
30
|
+
this.instanceCache = {};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @private
|
|
34
|
+
* @type {string[]}
|
|
35
|
+
*/
|
|
36
|
+
this.pathsToAdapters = pathsToAdapters;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @private
|
|
40
|
+
* @type {(path: string) => AdapterConstructor}
|
|
41
|
+
*/
|
|
42
|
+
this.loadAdapterFromPath = loadAdapterFromPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Register an adapter type and the corresponding base class. Must be called before requesting adapters of that type
|
|
47
|
+
*
|
|
48
|
+
* @param {string} type The name for the type of adapter
|
|
49
|
+
* @param {AdapterConstructor} BaseClass The class from which all adapters of this type must extend
|
|
50
|
+
*/
|
|
51
|
+
registerAdapter(type, BaseClass) {
|
|
52
|
+
this.instanceCache[type] = {};
|
|
53
|
+
this.baseClasses[type] = BaseClass;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Force recreation of all instances instead of reusing cached instances. Use when editing config file during tests.
|
|
58
|
+
*/
|
|
59
|
+
clearInstanceCache() {
|
|
60
|
+
for (const key of Object.keys(this.instanceCache)) {
|
|
61
|
+
this.instanceCache[key] = {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* getAdapter
|
|
67
|
+
*
|
|
68
|
+
* @param {string} adapterName The name of the type of adapter, e.g. "storage" or "scheduling", optionally including the feature, e.g. "storage:files"
|
|
69
|
+
* @param {string} adapterClassName The active adapter instance class name e.g. "LocalFileStorage"
|
|
70
|
+
* @param {object} [config] The config the adapter could be instantiated with
|
|
71
|
+
*
|
|
72
|
+
* @returns {Adapter} The resolved and instantiated adapter
|
|
73
|
+
*/
|
|
74
|
+
getAdapter(adapterName, adapterClassName, config) {
|
|
75
|
+
if (!adapterName || !adapterClassName) {
|
|
76
|
+
throw new errors.IncorrectUsageError({
|
|
77
|
+
message: 'getAdapter must be called with a adapterName and a adapterClassName.'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let adapterType;
|
|
82
|
+
if (adapterName.includes(':')) {
|
|
83
|
+
[adapterType] = adapterName.split(':');
|
|
84
|
+
} else {
|
|
85
|
+
adapterType = adapterName;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const adapterCache = this.instanceCache[adapterType];
|
|
89
|
+
|
|
90
|
+
if (!adapterCache) {
|
|
91
|
+
throw new errors.NotFoundError({
|
|
92
|
+
message: `Unknown adapter type ${adapterType}. Please register adapter.`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// @NOTE: example cache key value 'email:newsletters:custom-newsletter-adapter'
|
|
97
|
+
const adapterCacheKey = `${adapterName}:${adapterClassName}`;
|
|
98
|
+
if (adapterCache[adapterCacheKey]) {
|
|
99
|
+
return adapterCache[adapterCacheKey];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @type AdapterConstructor */
|
|
103
|
+
let Adapter;
|
|
104
|
+
for (const pathToAdapters of this.pathsToAdapters) {
|
|
105
|
+
const pathToAdapter = path.join(pathToAdapters, adapterType, adapterClassName);
|
|
106
|
+
try {
|
|
107
|
+
Adapter = this.loadAdapterFromPath(pathToAdapter);
|
|
108
|
+
if (Adapter) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
// Catch runtime errors
|
|
113
|
+
if (err.code !== 'MODULE_NOT_FOUND') {
|
|
114
|
+
throw new errors.IncorrectUsageError({err});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Catch missing dependencies BUT NOT missing adapter
|
|
118
|
+
if (!err.message.includes(pathToAdapter)) {
|
|
119
|
+
throw new errors.IncorrectUsageError({
|
|
120
|
+
message: `You are missing dependencies in your adapter ${pathToAdapter}`,
|
|
121
|
+
err
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!Adapter) {
|
|
128
|
+
throw new errors.IncorrectUsageError({
|
|
129
|
+
message: `Unable to find ${adapterType} adapter ${adapterClassName} in ${this.pathsToAdapters}.`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const adapter = new Adapter(config);
|
|
134
|
+
|
|
135
|
+
if (!(adapter instanceof this.baseClasses[adapterType])) {
|
|
136
|
+
if (Object.getPrototypeOf(Adapter).name !== this.baseClasses[adapterType].name) {
|
|
137
|
+
throw new errors.IncorrectUsageError({
|
|
138
|
+
message: `${adapterType} adapter ${adapterClassName} does not inherit from the base class.`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!adapter.requiredFns) {
|
|
144
|
+
throw new errors.IncorrectUsageError({
|
|
145
|
+
message: `${adapterType} adapter ${adapterClassName} does not have the requiredFns.`
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const requiredFn of adapter.requiredFns) {
|
|
150
|
+
if (typeof adapter[requiredFn] !== 'function') {
|
|
151
|
+
throw new errors.IncorrectUsageError({
|
|
152
|
+
message: `${adapterType} adapter ${adapterClassName} is missing the ${requiredFn} method.`
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
adapterCache[adapterCacheKey] = adapter;
|
|
158
|
+
|
|
159
|
+
return adapter;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const AdapterManager = require('
|
|
1
|
+
const AdapterManager = require('./AdapterManager');
|
|
2
2
|
const getAdapterServiceConfig = require('./config');
|
|
3
3
|
const resolveAdapterOptions = require('./options-resolver');
|
|
4
4
|
const config = require('../../../shared/config');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const AnnouncementVisibilityValues = require('./AnnouncementVisibilityValues');
|
|
2
|
+
|
|
3
|
+
class AnnouncementBarSettings {
|
|
4
|
+
#getAnnouncementSettings;
|
|
5
|
+
|
|
6
|
+
static VisibilityValues = AnnouncementVisibilityValues;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} deps
|
|
11
|
+
* @param {() => {announcement: string, announcement_visibility: string[], announcement_background: string}} deps.getAnnouncementSettings
|
|
12
|
+
*/
|
|
13
|
+
constructor(deps) {
|
|
14
|
+
this.#getAnnouncementSettings = deps.getAnnouncementSettings;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} [member]
|
|
19
|
+
* @param {string} member.status
|
|
20
|
+
* @returns {{announcement: string, announcement_background: string}}
|
|
21
|
+
*/
|
|
22
|
+
getAnnouncementSettings(member) {
|
|
23
|
+
let announcement = undefined;
|
|
24
|
+
|
|
25
|
+
// NOTE: combination of 'free_members' & 'paid_members' makes just a 'members' filter
|
|
26
|
+
const announcementSettings = this.#getAnnouncementSettings();
|
|
27
|
+
|
|
28
|
+
if (announcementSettings.announcement) {
|
|
29
|
+
const visibilities = announcementSettings.announcement_visibility;
|
|
30
|
+
const announcementContent = announcementSettings.announcement;
|
|
31
|
+
|
|
32
|
+
if (visibilities.length === 0) {
|
|
33
|
+
announcement = undefined;
|
|
34
|
+
} else {
|
|
35
|
+
if (visibilities.includes(AnnouncementVisibilityValues.VISITORS) && !member) {
|
|
36
|
+
announcement = announcementContent;
|
|
37
|
+
} else if (visibilities.includes(AnnouncementVisibilityValues.FREE_MEMBERS) && (member?.status === 'free')) {
|
|
38
|
+
announcement = announcementContent;
|
|
39
|
+
} else if (visibilities.includes(AnnouncementVisibilityValues.PAID_MEMBERS) && (member && member.status !== 'free')) {
|
|
40
|
+
announcement = announcementContent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (announcement !== undefined) {
|
|
46
|
+
return {
|
|
47
|
+
announcement,
|
|
48
|
+
announcement_background: announcementSettings.announcement_background
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = AnnouncementBarSettings;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Available visibilities:
|
|
2
|
+
// 'visitors' - anonymous visitors
|
|
3
|
+
// 'free_members' - free members
|
|
4
|
+
// 'paid_members' - paid members (aka non-free members)
|
|
5
|
+
class AnnouncementVisibilityValues {
|
|
6
|
+
static VISITORS = 'visitors';
|
|
7
|
+
static FREE_MEMBERS = 'free_members';
|
|
8
|
+
static PAID_MEMBERS = 'paid_members';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = AnnouncementVisibilityValues;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const settingsCache = require('../../../shared/settings-cache');
|
|
2
|
-
const AnnouncementBarSettings = require('
|
|
2
|
+
const AnnouncementBarSettings = require('./AnnouncementBarSettings');
|
|
3
3
|
|
|
4
4
|
const announcementBarService = new AnnouncementBarSettings({
|
|
5
5
|
getAnnouncementSettings: () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const VersionNotificationsDataService = require('./VersionNotificationsDataService');
|
|
3
|
-
const EmailContentGenerator = require('
|
|
3
|
+
const EmailContentGenerator = require('../lib/EmailContentGenerator');
|
|
4
4
|
|
|
5
5
|
class APIVersionCompatibilityService {
|
|
6
6
|
/**
|
|
@@ -46,6 +46,7 @@ totp.options = {
|
|
|
46
46
|
* @prop {(req: Req, res: Res) => Promise<void>} sendAuthCodeToUser
|
|
47
47
|
* @prop {(req: Req, res: Res) => Promise<boolean>} verifyAuthCodeForUser
|
|
48
48
|
* @prop {(req: Req, res: Res) => Promise<boolean>} isVerifiedSession
|
|
49
|
+
* @prop {() => boolean} isVerificationRequired
|
|
49
50
|
*/
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -53,7 +54,7 @@ totp.options = {
|
|
|
53
54
|
* @param {(req: Req, res: Res) => Promise<Session>} deps.getSession
|
|
54
55
|
* @param {(data: {id: string}) => Promise<User>} deps.findUserById
|
|
55
56
|
* @param {(req: Req) => string} deps.getOriginOfRequest
|
|
56
|
-
* @param {(key:
|
|
57
|
+
* @param {(key: 'require_email_mfa' | 'admin_session_secret' | 'title') => boolean | string} deps.getSettingsCache
|
|
57
58
|
* @param {() => string} deps.getBlogLogo
|
|
58
59
|
* @param {import('../../core/core/server/services/mail').GhostMailer} deps.mailer
|
|
59
60
|
* @param {import('../../core/core/shared/labs')} deps.labs
|
|
@@ -96,6 +97,15 @@ module.exports = function createSessionService({
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
/**
|
|
101
|
+
* isVerificationRequired
|
|
102
|
+
* Determines if 2FA verification is required based on site settings
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
function isVerificationRequired() {
|
|
106
|
+
return getSettingsCache('require_email_mfa') === true;
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
/**
|
|
100
110
|
* createSessionForUser
|
|
101
111
|
*
|
|
@@ -261,7 +271,7 @@ module.exports = function createSessionService({
|
|
|
261
271
|
siteLogo: siteLogo,
|
|
262
272
|
token: token,
|
|
263
273
|
deviceDetails: await getDeviceDetails(session.user_agent, session.ip),
|
|
264
|
-
is2FARequired:
|
|
274
|
+
is2FARequired: this.isVerificationRequired()
|
|
265
275
|
});
|
|
266
276
|
|
|
267
277
|
try {
|
|
@@ -310,8 +320,7 @@ module.exports = function createSessionService({
|
|
|
310
320
|
async function removeUserForSession(req, res) {
|
|
311
321
|
const session = await getSession(req, res);
|
|
312
322
|
|
|
313
|
-
|
|
314
|
-
if (requireMfa) {
|
|
323
|
+
if (this.isVerificationRequired()) {
|
|
315
324
|
session.verified = undefined;
|
|
316
325
|
}
|
|
317
326
|
|
|
@@ -359,6 +368,7 @@ module.exports = function createSessionService({
|
|
|
359
368
|
isVerifiedSession,
|
|
360
369
|
sendAuthCodeToUser,
|
|
361
370
|
verifyAuthCodeForUser,
|
|
362
|
-
generateAuthCodeForUser
|
|
371
|
+
generateAuthCodeForUser,
|
|
372
|
+
isVerificationRequired
|
|
363
373
|
};
|
|
364
374
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const config = require('../../../shared/config');
|
|
2
2
|
const urlUtils = require('../../../shared/url-utils');
|
|
3
3
|
|
|
4
|
-
const DynamicRedirectManager = require('
|
|
4
|
+
const DynamicRedirectManager = require('../lib/DynamicRedirectManager');
|
|
5
5
|
const CustomRedirectsAPI = require('./CustomRedirectsAPI');
|
|
6
6
|
const validation = require('./validation');
|
|
7
7
|
const {getBackupRedirectsFilePath} = require('./utils');
|
|
@@ -17,7 +17,7 @@ class EmailServiceWrapper {
|
|
|
17
17
|
|
|
18
18
|
const {EmailService, EmailController, EmailRenderer, SendingService, BatchSendingService, EmailSegmenter, MailgunEmailProvider} = require('@tryghost/email-service');
|
|
19
19
|
const {Post, Newsletter, Email, EmailBatch, EmailRecipient, Member} = require('../../models');
|
|
20
|
-
const MailgunClient = require('
|
|
20
|
+
const MailgunClient = require('../lib/MailgunClient');
|
|
21
21
|
const configService = require('../../../shared/config');
|
|
22
22
|
const settingsCache = require('../../../shared/settings-cache');
|
|
23
23
|
const settingsHelpers = require('../settings-helpers');
|
|
@@ -53,7 +53,7 @@ class EmailServiceWrapper {
|
|
|
53
53
|
});
|
|
54
54
|
const i18nLanguage = labs.isSet('i18n') ? settingsCache.get('locale') || 'en' : 'en';
|
|
55
55
|
const i18n = i18nLib(i18nLanguage, 'newsletter');
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
events.on('settings.labs.edited', () => {
|
|
58
58
|
if (labs.isSet('i18n')) {
|
|
59
59
|
debug('labs i18n enabled, updating i18n to', settingsCache.get('locale'));
|
|
@@ -63,12 +63,12 @@ class EmailServiceWrapper {
|
|
|
63
63
|
i18n.changeLanguage('en');
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
events.on('settings.locale.edited', (model) => {
|
|
68
68
|
if (labs.isSet('i18n')) {
|
|
69
69
|
debug('locale changed, updating i18n to', model.get('value'));
|
|
70
70
|
i18n.changeLanguage(model.get('value'));
|
|
71
|
-
}
|
|
71
|
+
}
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
const mailgunEmailProvider = new MailgunEmailProvider({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const {AbstractEmailSuppressionList, EmailSuppressionData, EmailSuppressedEvent} = require('@tryghost/email-suppression-list');
|
|
2
|
-
const {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-
|
|
2
|
+
const {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-service');
|
|
3
3
|
const DomainEvents = require('@tryghost/domain-events');
|
|
4
4
|
const logging = require('@tryghost/logging');
|
|
5
5
|
const models = require('../../models');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const MailgunClient = require('@tryghost/mailgun-client');
|
|
2
1
|
const models = require('../../models');
|
|
3
2
|
const configService = require('../../../shared/config');
|
|
4
3
|
const settingsCache = require('../../../shared/settings-cache');
|
|
4
|
+
const MailgunClient = require('../lib/MailgunClient');
|
|
5
5
|
const MailgunEmailSuppressionList = require('./MailgunEmailSuppressionList');
|
|
6
6
|
|
|
7
7
|
const mailgunClient = new MailgunClient({
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const {parse: parseURL, format: formatURL} = require('url');
|
|
3
|
+
const {parse: parseQuerystring, stringify: formatQuerystring} = require('querystring');
|
|
4
|
+
|
|
5
|
+
class DynamicRedirectManager {
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} config
|
|
8
|
+
* @param {number} config.permanentMaxAge
|
|
9
|
+
* @param {function} config.getSubdirectoryURL
|
|
10
|
+
*/
|
|
11
|
+
constructor({permanentMaxAge, getSubdirectoryURL}) {
|
|
12
|
+
/** @private */
|
|
13
|
+
this.permanentMaxAge = permanentMaxAge;
|
|
14
|
+
|
|
15
|
+
this.getSubdirectoryURL = getSubdirectoryURL;
|
|
16
|
+
|
|
17
|
+
/** @private */
|
|
18
|
+
this.router = express.Router();
|
|
19
|
+
/** @private @type {Object.<string, {fromRegex: RegExp, to: string, options: {permanent: boolean}}>} */
|
|
20
|
+
this.redirects = {};
|
|
21
|
+
|
|
22
|
+
this.handleRequest = this.handleRequest.bind(this);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @private
|
|
27
|
+
* @param {string} string
|
|
28
|
+
* @returns {RegExp}
|
|
29
|
+
*/
|
|
30
|
+
buildRegex(string) {
|
|
31
|
+
let flags = '';
|
|
32
|
+
|
|
33
|
+
if (string.startsWith('/') && string.endsWith('/i')) {
|
|
34
|
+
string = string.slice(1, -2);
|
|
35
|
+
flags += 'i';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (string.endsWith('/')) {
|
|
39
|
+
string = string.slice(0, -1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!string.endsWith('$')) {
|
|
43
|
+
string += '/?$';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new RegExp(string, flags);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @private
|
|
51
|
+
* @param {string} redirectId
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
setupRedirect(redirectId) {
|
|
55
|
+
const {fromRegex, to, options: {permanent}} = this.redirects[redirectId];
|
|
56
|
+
|
|
57
|
+
this.router.get(fromRegex, (req, res) => {
|
|
58
|
+
const maxAge = permanent ? this.permanentMaxAge : 0;
|
|
59
|
+
const toURL = parseURL(to);
|
|
60
|
+
const toURLParams = parseQuerystring(toURL.query);
|
|
61
|
+
const currentURL = parseURL(req.url);
|
|
62
|
+
const currentURLParams = parseQuerystring(currentURL.query);
|
|
63
|
+
const params = Object.assign({}, currentURLParams, toURLParams);
|
|
64
|
+
const search = formatQuerystring(params);
|
|
65
|
+
|
|
66
|
+
toURL.pathname = currentURL.pathname.replace(fromRegex, toURL.pathname);
|
|
67
|
+
toURL.search = search !== '' ? `?${search}` : null;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Only if the url is internal should we prepend the Ghost subdirectory
|
|
71
|
+
* @see https://github.com/TryGhost/Ghost/issues/10776
|
|
72
|
+
*/
|
|
73
|
+
if (!toURL.hostname) {
|
|
74
|
+
toURL.pathname = this.getSubdirectoryURL(toURL.pathname);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
res.set({
|
|
78
|
+
'Cache-Control': `public, max-age=${maxAge}`
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
res.redirect(permanent ? 301 : 302, formatURL(toURL));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} from
|
|
87
|
+
* @param {string} to
|
|
88
|
+
* @param {object} [options]
|
|
89
|
+
* @param {boolean} [options.permanent]
|
|
90
|
+
*
|
|
91
|
+
* @returns {string} The redirect ID
|
|
92
|
+
*/
|
|
93
|
+
addRedirect(from, to, options = {}) {
|
|
94
|
+
try {
|
|
95
|
+
// encode "from" only if it's not a regex
|
|
96
|
+
try {
|
|
97
|
+
new RegExp(from);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
from = encodeURI(from);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const fromRegex = this.buildRegex(from);
|
|
103
|
+
const redirectId = from;
|
|
104
|
+
|
|
105
|
+
this.redirects[redirectId] = {
|
|
106
|
+
fromRegex,
|
|
107
|
+
to,
|
|
108
|
+
options
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
this.setupRedirect(redirectId);
|
|
112
|
+
|
|
113
|
+
return redirectId;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.message.match(/Invalid regular expression/gi)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} redirectId
|
|
125
|
+
* @returns {void}
|
|
126
|
+
*/
|
|
127
|
+
removeRedirect(redirectId) {
|
|
128
|
+
delete this.redirects[redirectId];
|
|
129
|
+
|
|
130
|
+
this.router = express.Router();
|
|
131
|
+
Object.keys(this.redirects).forEach(id => this.setupRedirect(id));
|
|
132
|
+
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*/
|
|
139
|
+
removeAllRedirects() {
|
|
140
|
+
this.redirects = {};
|
|
141
|
+
this.router = express.Router();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {express.Request} req
|
|
146
|
+
* @param {express.Response} res
|
|
147
|
+
* @param {express.NextFunction} next
|
|
148
|
+
*
|
|
149
|
+
* @returns {void}
|
|
150
|
+
*/
|
|
151
|
+
handleRequest(req, res, next) {
|
|
152
|
+
this.router(req, res, next);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = DynamicRedirectManager;
|