ghost 5.115.0 → 5.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.116.0.tgz} +0 -0
- package/components/tryghost-constants-5.116.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.116.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.116.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.116.0.tgz +0 -0
- package/components/{tryghost-donations-5.115.0.tgz → tryghost-donations-5.116.0.tgz} +0 -0
- package/components/tryghost-email-addresses-5.116.0.tgz +0 -0
- package/components/tryghost-email-service-5.116.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.116.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.116.0.tgz +0 -0
- package/components/tryghost-i18n-5.116.0.tgz +0 -0
- package/components/tryghost-job-manager-5.116.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.116.0.tgz +0 -0
- package/components/tryghost-magic-link-5.116.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.116.0.tgz +0 -0
- package/components/tryghost-member-events-5.116.0.tgz +0 -0
- package/components/tryghost-members-api-5.116.0.tgz +0 -0
- package/components/tryghost-members-csv-5.116.0.tgz +0 -0
- package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.116.0.tgz} +0 -0
- package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.116.0.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.116.0.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.116.0.tgz +0 -0
- package/components/{tryghost-post-events-5.115.0.tgz → tryghost-post-events-5.116.0.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.116.0.tgz} +0 -0
- package/components/tryghost-posts-service-5.116.0.tgz +0 -0
- package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.116.0.tgz} +0 -0
- package/components/tryghost-security-5.116.0.tgz +0 -0
- package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.116.0.tgz} +0 -0
- package/components/tryghost-webmentions-5.116.0.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 +11 -6
- package/content/themes/source/partials/feature-image.hbs +2 -2
- package/core/boot.js +3 -43
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +30494 -29403
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-a9601514.mjs} +5 -4
- package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-c1789d04.mjs} +67 -65
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.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-376f847c.mjs → index-84580c3a.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f744cab7.mjs} +3147 -3123
- package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-d9ca60c5.mjs} +1198 -1192
- package/core/built/admin/assets/chunk.524.8371443ef8f60db429d0.js +35 -0
- package/core/built/admin/assets/chunk.582.f90151775f2e53dd21d9.js +37 -0
- package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
- package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +272 -251
- 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 +75373 -0
- 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 +8 -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/frontend/src/cards/css/cta.css +1 -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/slugs.js +6 -2
- 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 +3 -3
- package/core/server/data/importer/importers/importer-revue.js +128 -0
- package/core/server/data/importer/importers/json-to-html.js +107 -0
- package/core/server/data/migrations/utils/tables.js +2 -4
- 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/lib/bootstrap-socket.js +87 -0
- package/core/server/lib/package-json/index.js +1 -0
- package/core/server/lib/package-json/package-json.js +160 -0
- package/core/server/lib/package-json/parse.js +57 -0
- package/core/server/models/base/plugins/actions.js +44 -31
- package/core/server/models/base/plugins/generate-slug.js +6 -0
- package/core/server/notify.js +1 -1
- package/core/server/services/activitypub/ActivityPubService.ts +1 -1
- 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 +99 -0
- package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
- package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
- package/core/server/services/api-version-compatibility/index.js +2 -2
- package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
- package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
- package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
- package/core/server/services/audience-feedback/Feedback.js +35 -0
- package/core/server/services/audience-feedback/index.js +4 -2
- package/core/server/services/auth/session/emails/signin.js +168 -0
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-from-token.js +69 -0
- package/core/server/services/auth/session/session-service.js +374 -0
- package/core/server/services/custom-redirects/index.js +1 -1
- package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
- package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
- package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
- 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/explore-ping/ExplorePingService.js +106 -0
- package/core/server/services/explore-ping/index.js +31 -0
- package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
- package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
- package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
- package/core/server/services/invitations/accept.js +5 -2
- 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/BookshelfMailEventRepository.js +2 -2
- package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
- package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
- package/core/server/services/mail-events/MailEvent.js +20 -0
- package/core/server/services/mail-events/MailEvent.ts +10 -0
- package/core/server/services/mail-events/MailEventRepository.js +2 -0
- package/core/server/services/mail-events/MailEventRepository.ts +5 -0
- package/core/server/services/mail-events/MailEventService.js +124 -0
- package/core/server/services/mail-events/MailEventService.ts +169 -0
- package/core/server/services/mail-events/index.js +1 -1
- package/core/server/services/mail-events/libraries.d.ts +2 -0
- package/core/server/services/members/CaptchaService.js +80 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
- package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
- package/core/server/services/members/importer/email-template.js +182 -0
- package/core/server/services/members/importer/index.js +30 -0
- package/core/server/services/members/members-ssr.js +333 -0
- package/core/server/services/members/service.js +2 -2
- package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/posts/stats/PostStats.js +13 -0
- 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/route-settings/SettingsPathManager.js +47 -0
- package/core/server/services/route-settings/index.js +1 -1
- 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/services/stripe/README.md +63 -0
- package/core/server/services/stripe/StripeAPI.js +931 -0
- package/core/server/services/stripe/StripeMigrations.js +613 -0
- package/core/server/services/stripe/StripeService.js +175 -0
- package/core/server/services/stripe/WebhookController.js +100 -0
- package/core/server/services/stripe/WebhookManager.js +175 -0
- package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
- package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
- package/core/server/services/stripe/events/index.js +4 -0
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
- package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
- package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
- package/core/server/services/themes/loader.js +1 -1
- package/core/server/services/themes/to-json.js +1 -1
- package/core/server/web/api/endpoints/admin/app.js +1 -21
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/api/middleware/version-match.js +41 -0
- package/core/server/web/shared/middleware/cache-control.js +51 -0
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/server/web/well-known.js +1 -1
- package/core/shared/labs.js +5 -3
- package/core/shared/settings-cache/CacheManager.js +64 -6
- package/package.json +98 -146
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +1478 -1634
- package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.115.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
- package/components/tryghost-constants-5.115.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
- package/components/tryghost-data-generator-5.115.0.tgz +0 -0
- package/components/tryghost-domain-events-5.115.0.tgz +0 -0
- package/components/tryghost-email-addresses-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.115.0.tgz +0 -0
- package/components/tryghost-email-events-5.115.0.tgz +0 -0
- package/components/tryghost-email-service-5.115.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.115.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.115.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
- package/components/tryghost-ghost-5.115.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
- package/components/tryghost-i18n-5.115.0.tgz +0 -0
- package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.115.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
- package/components/tryghost-job-manager-5.115.0.tgz +0 -0
- package/components/tryghost-link-redirects-5.115.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
- package/components/tryghost-magic-link-5.115.0.tgz +0 -0
- package/components/tryghost-mail-events-5.115.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.115.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
- package/components/tryghost-member-events-5.115.0.tgz +0 -0
- package/components/tryghost-members-api-5.115.0.tgz +0 -0
- package/components/tryghost-members-csv-5.115.0.tgz +0 -0
- package/components/tryghost-members-importer-5.115.0.tgz +0 -0
- package/components/tryghost-members-payments-5.115.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
- package/components/tryghost-minifier-5.115.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.115.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
- package/components/tryghost-package-json-5.115.0.tgz +0 -0
- package/components/tryghost-posts-service-5.115.0.tgz +0 -0
- package/components/tryghost-recommendations-5.115.0.tgz +0 -0
- package/components/tryghost-referrers-5.115.0.tgz +0 -0
- package/components/tryghost-security-5.115.0.tgz +0 -0
- package/components/tryghost-session-service-5.115.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.115.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
- package/components/tryghost-webmentions-5.115.0.tgz +0 -0
- package/core/built/admin/assets/chunk.524.31419fdf6fb3859ecc1e.js +0 -35
- package/core/built/admin/assets/chunk.582.08c816d5e4ab766486a7.js +0 -37
- package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
- package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.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,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RecommendationService = void 0;
|
|
7
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
8
|
+
const logging_1 = __importDefault(require("@tryghost/logging"));
|
|
9
|
+
const tpl_1 = __importDefault(require("@tryghost/tpl"));
|
|
10
|
+
const ClickEvent_1 = require("./ClickEvent");
|
|
11
|
+
const Recommendation_1 = require("./Recommendation");
|
|
12
|
+
const SubscribeEvent_1 = require("./SubscribeEvent");
|
|
13
|
+
const messages = {
|
|
14
|
+
notFound: 'Recommendation with id {id} not found'
|
|
15
|
+
};
|
|
16
|
+
class RecommendationService {
|
|
17
|
+
repository;
|
|
18
|
+
clickEventRepository;
|
|
19
|
+
subscribeEventRepository;
|
|
20
|
+
wellknownService;
|
|
21
|
+
mentionSendingService;
|
|
22
|
+
recommendationEnablerService;
|
|
23
|
+
recommendationMetadataService;
|
|
24
|
+
constructor(deps) {
|
|
25
|
+
this.repository = deps.repository;
|
|
26
|
+
this.wellknownService = deps.wellknownService;
|
|
27
|
+
this.mentionSendingService = deps.mentionSendingService;
|
|
28
|
+
this.recommendationEnablerService = deps.recommendationEnablerService;
|
|
29
|
+
this.clickEventRepository = deps.clickEventRepository;
|
|
30
|
+
this.subscribeEventRepository = deps.subscribeEventRepository;
|
|
31
|
+
this.recommendationMetadataService = deps.recommendationMetadataService;
|
|
32
|
+
}
|
|
33
|
+
async init() {
|
|
34
|
+
const recommendations = await this.#listRecommendations();
|
|
35
|
+
await this.updateWellknown(recommendations);
|
|
36
|
+
// Do a slow update of all the recommendation metadata (keeping logo up to date, one-click-subscribe, etc.)
|
|
37
|
+
// We better move this to a job in the future
|
|
38
|
+
if (!process.env.NODE_ENV?.startsWith('test')) {
|
|
39
|
+
setTimeout(async () => {
|
|
40
|
+
try {
|
|
41
|
+
await this.updateAllRecommendationsMetadata();
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
logging_1.default.error('[Recommendations] Failed to update all recommendations metadata on boot', e);
|
|
45
|
+
}
|
|
46
|
+
}, 2 * 60 * 1000 + Math.random() * 5 * 60 * 1000);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async updateAllRecommendationsMetadata() {
|
|
50
|
+
const recommendations = await this.#listRecommendations();
|
|
51
|
+
logging_1.default.info('[Recommendations] Updating recommendations metadata');
|
|
52
|
+
for (const recommendation of recommendations) {
|
|
53
|
+
try {
|
|
54
|
+
await this._updateRecommendationMetadata(recommendation);
|
|
55
|
+
await this.repository.save(recommendation);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
logging_1.default.error('[Recommendations] Failed to save updated metadata for recommendation ' + recommendation.url.toString(), e);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async updateWellknown(recommendations) {
|
|
63
|
+
await this.wellknownService.set(recommendations);
|
|
64
|
+
}
|
|
65
|
+
async updateRecommendationsEnabledSetting(recommendations) {
|
|
66
|
+
const expectedSetting = (recommendations.length > 0).toString();
|
|
67
|
+
const currentSetting = this.recommendationEnablerService.getSetting();
|
|
68
|
+
if (currentSetting && currentSetting === expectedSetting) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await this.recommendationEnablerService.setSetting(expectedSetting);
|
|
72
|
+
}
|
|
73
|
+
sendMentionToRecommendation(recommendation) {
|
|
74
|
+
this.mentionSendingService.sendAll({
|
|
75
|
+
url: this.wellknownService.getURL(),
|
|
76
|
+
links: [
|
|
77
|
+
recommendation.url
|
|
78
|
+
]
|
|
79
|
+
}).catch((err) => {
|
|
80
|
+
logging_1.default.error('Failed to send mention to recommendation', err);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async readRecommendation(id) {
|
|
84
|
+
const recommendation = await this.repository.getById(id);
|
|
85
|
+
if (!recommendation) {
|
|
86
|
+
throw new errors_1.default.NotFoundError({
|
|
87
|
+
message: (0, tpl_1.default)(messages.notFound, { id })
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return recommendation.plain;
|
|
91
|
+
}
|
|
92
|
+
async addRecommendation(addRecommendation) {
|
|
93
|
+
const recommendation = Recommendation_1.Recommendation.create(addRecommendation);
|
|
94
|
+
// If a recommendation with this URL already exists, throw an error
|
|
95
|
+
const existing = await this.repository.getByUrl(recommendation.url);
|
|
96
|
+
if (existing) {
|
|
97
|
+
throw new errors_1.default.ValidationError({
|
|
98
|
+
message: 'A recommendation with this URL already exists.'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
await this.repository.save(recommendation);
|
|
102
|
+
const recommendations = await this.#listRecommendations();
|
|
103
|
+
await this.updateWellknown(recommendations);
|
|
104
|
+
await this.updateRecommendationsEnabledSetting(recommendations);
|
|
105
|
+
// Only send an update for the mentioned URL
|
|
106
|
+
this.sendMentionToRecommendation(recommendation);
|
|
107
|
+
return recommendation.plain;
|
|
108
|
+
}
|
|
109
|
+
async checkRecommendation(url) {
|
|
110
|
+
// If a recommendation with this URL already exists, return it, but with updated metadata
|
|
111
|
+
const existing = await this.repository.getByUrl(url);
|
|
112
|
+
if (existing) {
|
|
113
|
+
this._updateRecommendationMetadata(existing);
|
|
114
|
+
await this.repository.save(existing);
|
|
115
|
+
return existing.plain;
|
|
116
|
+
}
|
|
117
|
+
let metadata;
|
|
118
|
+
try {
|
|
119
|
+
metadata = await this.recommendationMetadataService.fetch(url);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
logging_1.default.error('[Recommendations] Failed to fetch metadata for url ' + url, e);
|
|
123
|
+
return {
|
|
124
|
+
url: url,
|
|
125
|
+
title: undefined,
|
|
126
|
+
excerpt: undefined,
|
|
127
|
+
featuredImage: undefined,
|
|
128
|
+
favicon: undefined,
|
|
129
|
+
oneClickSubscribe: false
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
url: url,
|
|
134
|
+
title: metadata.title ?? undefined,
|
|
135
|
+
excerpt: metadata.excerpt ?? undefined,
|
|
136
|
+
featuredImage: metadata.featuredImage ?? undefined,
|
|
137
|
+
favicon: metadata.favicon ?? undefined,
|
|
138
|
+
oneClickSubscribe: !!metadata.oneClickSubscribe
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async _updateRecommendationMetadata(recommendation) {
|
|
142
|
+
// Fetch data
|
|
143
|
+
try {
|
|
144
|
+
const metadata = await this.recommendationMetadataService.fetch(recommendation.url);
|
|
145
|
+
// Set null values to undefined so we don't trigger an update
|
|
146
|
+
recommendation.edit({
|
|
147
|
+
// Don't set title if it's already set on the recommendation
|
|
148
|
+
title: recommendation.title ? undefined : (metadata.title ?? undefined),
|
|
149
|
+
excerpt: metadata.excerpt ?? undefined,
|
|
150
|
+
featuredImage: metadata.featuredImage ?? undefined,
|
|
151
|
+
favicon: metadata.favicon ?? undefined,
|
|
152
|
+
oneClickSubscribe: !!metadata.oneClickSubscribe
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
logging_1.default.error('[Recommendations] Failed to update metadata for recommendation ' + recommendation.url.toString(), e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async editRecommendation(id, recommendationEdit) {
|
|
160
|
+
// Check if it exists
|
|
161
|
+
const existing = await this.repository.getById(id);
|
|
162
|
+
if (!existing) {
|
|
163
|
+
throw new errors_1.default.NotFoundError({
|
|
164
|
+
message: (0, tpl_1.default)(messages.notFound, { id })
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
existing.edit(recommendationEdit);
|
|
168
|
+
await this._updateRecommendationMetadata(existing);
|
|
169
|
+
await this.repository.save(existing);
|
|
170
|
+
const recommendations = await this.#listRecommendations();
|
|
171
|
+
await this.updateWellknown(recommendations);
|
|
172
|
+
this.sendMentionToRecommendation(existing);
|
|
173
|
+
return existing.plain;
|
|
174
|
+
}
|
|
175
|
+
async deleteRecommendation(id) {
|
|
176
|
+
const existing = await this.repository.getById(id);
|
|
177
|
+
if (!existing) {
|
|
178
|
+
throw new errors_1.default.NotFoundError({
|
|
179
|
+
message: (0, tpl_1.default)(messages.notFound, { id })
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
existing.delete();
|
|
183
|
+
await this.repository.save(existing);
|
|
184
|
+
const recommendations = await this.#listRecommendations();
|
|
185
|
+
await this.updateWellknown(recommendations);
|
|
186
|
+
await this.updateRecommendationsEnabledSetting(recommendations);
|
|
187
|
+
// Send a mention (because it was deleted, according to the webmentions spec)
|
|
188
|
+
this.sendMentionToRecommendation(existing);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Sames as listRecommendations, but returns Entities instead of plain objects (Entities are only used internally)
|
|
192
|
+
*/
|
|
193
|
+
async #listRecommendations(options = { page: 1, limit: 'all' }) {
|
|
194
|
+
if (options.limit === 'all') {
|
|
195
|
+
return await this.repository.getAll({
|
|
196
|
+
...options
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return await this.repository.getPage({
|
|
200
|
+
...options,
|
|
201
|
+
page: options.page || 1,
|
|
202
|
+
limit: options.limit || 15
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async listRecommendations(options = { page: 1, limit: 'all', include: [] }) {
|
|
206
|
+
const list = await this.#listRecommendations(options);
|
|
207
|
+
return list.map(e => e.plain);
|
|
208
|
+
}
|
|
209
|
+
async countRecommendations({ filter }) {
|
|
210
|
+
return await this.repository.getCount({ filter });
|
|
211
|
+
}
|
|
212
|
+
async trackClicked({ id, memberId }) {
|
|
213
|
+
const clickEvent = ClickEvent_1.ClickEvent.create({ recommendationId: id, memberId });
|
|
214
|
+
await this.clickEventRepository.save(clickEvent);
|
|
215
|
+
}
|
|
216
|
+
async trackSubscribed({ id, memberId }) {
|
|
217
|
+
const subscribeEvent = SubscribeEvent_1.SubscribeEvent.create({ recommendationId: id, memberId });
|
|
218
|
+
await this.subscribeEventRepository.save(subscribeEvent);
|
|
219
|
+
}
|
|
220
|
+
async readRecommendationByUrl(url) {
|
|
221
|
+
const recommendation = await this.repository.getByUrl(url);
|
|
222
|
+
if (!recommendation) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return recommendation.plain;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.RecommendationService = RecommendationService;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import errors from '@tryghost/errors';
|
|
2
|
+
import {InMemoryRepository} from '../../lib/InMemoryRepository';
|
|
3
|
+
import logging from '@tryghost/logging';
|
|
4
|
+
import tpl from '@tryghost/tpl';
|
|
5
|
+
|
|
6
|
+
import {IncludeOption, OrderOption} from './BookshelfRepository';
|
|
7
|
+
import {ClickEvent} from './ClickEvent';
|
|
8
|
+
import {AddRecommendation, Recommendation, RecommendationPlain} from './Recommendation';
|
|
9
|
+
import {RecommendationRepository} from './RecommendationRepository';
|
|
10
|
+
import {SubscribeEvent} from './SubscribeEvent';
|
|
11
|
+
import {WellknownService} from './WellknownService';
|
|
12
|
+
import {RecommendationMetadataService} from './RecommendationMetadataService';
|
|
13
|
+
|
|
14
|
+
type MentionSendingService = {
|
|
15
|
+
sendAll(options: {url: URL, links: URL[]}): Promise<void>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type RecommendationEnablerService = {
|
|
19
|
+
getSetting(): string,
|
|
20
|
+
setSetting(value: string): Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const messages = {
|
|
24
|
+
notFound: 'Recommendation with id {id} not found'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class RecommendationService {
|
|
28
|
+
repository: RecommendationRepository;
|
|
29
|
+
clickEventRepository: InMemoryRepository<string, ClickEvent>;
|
|
30
|
+
subscribeEventRepository: InMemoryRepository<string, SubscribeEvent>;
|
|
31
|
+
|
|
32
|
+
wellknownService: WellknownService;
|
|
33
|
+
mentionSendingService: MentionSendingService;
|
|
34
|
+
recommendationEnablerService: RecommendationEnablerService;
|
|
35
|
+
recommendationMetadataService: RecommendationMetadataService;
|
|
36
|
+
|
|
37
|
+
constructor(deps: {
|
|
38
|
+
repository: RecommendationRepository,
|
|
39
|
+
clickEventRepository: InMemoryRepository<string, ClickEvent>,
|
|
40
|
+
subscribeEventRepository: InMemoryRepository<string, SubscribeEvent>,
|
|
41
|
+
wellknownService: WellknownService,
|
|
42
|
+
mentionSendingService: MentionSendingService,
|
|
43
|
+
recommendationEnablerService: RecommendationEnablerService,
|
|
44
|
+
recommendationMetadataService: RecommendationMetadataService
|
|
45
|
+
}) {
|
|
46
|
+
this.repository = deps.repository;
|
|
47
|
+
this.wellknownService = deps.wellknownService;
|
|
48
|
+
this.mentionSendingService = deps.mentionSendingService;
|
|
49
|
+
this.recommendationEnablerService = deps.recommendationEnablerService;
|
|
50
|
+
this.clickEventRepository = deps.clickEventRepository;
|
|
51
|
+
this.subscribeEventRepository = deps.subscribeEventRepository;
|
|
52
|
+
this.recommendationMetadataService = deps.recommendationMetadataService;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async init() {
|
|
56
|
+
const recommendations = await this.#listRecommendations();
|
|
57
|
+
await this.updateWellknown(recommendations);
|
|
58
|
+
|
|
59
|
+
// Do a slow update of all the recommendation metadata (keeping logo up to date, one-click-subscribe, etc.)
|
|
60
|
+
// We better move this to a job in the future
|
|
61
|
+
if (!process.env.NODE_ENV?.startsWith('test')) {
|
|
62
|
+
setTimeout(async () => {
|
|
63
|
+
try {
|
|
64
|
+
await this.updateAllRecommendationsMetadata();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
logging.error('[Recommendations] Failed to update all recommendations metadata on boot', e);
|
|
67
|
+
}
|
|
68
|
+
}, 2 * 60 * 1000 + Math.random() * 5 * 60 * 1000);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async updateAllRecommendationsMetadata() {
|
|
73
|
+
const recommendations = await this.#listRecommendations();
|
|
74
|
+
logging.info('[Recommendations] Updating recommendations metadata');
|
|
75
|
+
for (const recommendation of recommendations) {
|
|
76
|
+
try {
|
|
77
|
+
await this._updateRecommendationMetadata(recommendation);
|
|
78
|
+
await this.repository.save(recommendation);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
logging.error('[Recommendations] Failed to save updated metadata for recommendation ' + recommendation.url.toString(), e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async updateWellknown(recommendations: Recommendation[]) {
|
|
86
|
+
await this.wellknownService.set(recommendations);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async updateRecommendationsEnabledSetting(recommendations: Recommendation[]) {
|
|
90
|
+
const expectedSetting = (recommendations.length > 0).toString();
|
|
91
|
+
const currentSetting = this.recommendationEnablerService.getSetting();
|
|
92
|
+
|
|
93
|
+
if (currentSetting && currentSetting === expectedSetting) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await this.recommendationEnablerService.setSetting(expectedSetting);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private sendMentionToRecommendation(recommendation: Recommendation) {
|
|
101
|
+
this.mentionSendingService.sendAll({
|
|
102
|
+
url: this.wellknownService.getURL(),
|
|
103
|
+
links: [
|
|
104
|
+
recommendation.url
|
|
105
|
+
]
|
|
106
|
+
}).catch((err) => {
|
|
107
|
+
logging.error('Failed to send mention to recommendation', err);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async readRecommendation(id: string): Promise<RecommendationPlain> {
|
|
112
|
+
const recommendation = await this.repository.getById(id);
|
|
113
|
+
|
|
114
|
+
if (!recommendation) {
|
|
115
|
+
throw new errors.NotFoundError({
|
|
116
|
+
message: tpl(messages.notFound, {id})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return recommendation.plain;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async addRecommendation(addRecommendation: AddRecommendation): Promise<RecommendationPlain> {
|
|
124
|
+
const recommendation = Recommendation.create(addRecommendation);
|
|
125
|
+
|
|
126
|
+
// If a recommendation with this URL already exists, throw an error
|
|
127
|
+
const existing = await this.repository.getByUrl(recommendation.url);
|
|
128
|
+
if (existing) {
|
|
129
|
+
throw new errors.ValidationError({
|
|
130
|
+
message: 'A recommendation with this URL already exists.'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await this.repository.save(recommendation);
|
|
135
|
+
|
|
136
|
+
const recommendations = await this.#listRecommendations();
|
|
137
|
+
await this.updateWellknown(recommendations);
|
|
138
|
+
await this.updateRecommendationsEnabledSetting(recommendations);
|
|
139
|
+
|
|
140
|
+
// Only send an update for the mentioned URL
|
|
141
|
+
this.sendMentionToRecommendation(recommendation);
|
|
142
|
+
return recommendation.plain;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async checkRecommendation(url: URL): Promise<Partial<RecommendationPlain>> {
|
|
146
|
+
// If a recommendation with this URL already exists, return it, but with updated metadata
|
|
147
|
+
const existing = await this.repository.getByUrl(url);
|
|
148
|
+
if (existing) {
|
|
149
|
+
this._updateRecommendationMetadata(existing);
|
|
150
|
+
await this.repository.save(existing);
|
|
151
|
+
return existing.plain;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let metadata;
|
|
155
|
+
try {
|
|
156
|
+
metadata = await this.recommendationMetadataService.fetch(url);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
logging.error('[Recommendations] Failed to fetch metadata for url ' + url, e);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
url: url,
|
|
162
|
+
title: undefined,
|
|
163
|
+
excerpt: undefined,
|
|
164
|
+
featuredImage: undefined,
|
|
165
|
+
favicon: undefined,
|
|
166
|
+
oneClickSubscribe: false
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
url: url,
|
|
172
|
+
title: metadata.title ?? undefined,
|
|
173
|
+
excerpt: metadata.excerpt ?? undefined,
|
|
174
|
+
featuredImage: metadata.featuredImage ?? undefined,
|
|
175
|
+
favicon: metadata.favicon ?? undefined,
|
|
176
|
+
oneClickSubscribe: !!metadata.oneClickSubscribe
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async _updateRecommendationMetadata(recommendation: Recommendation) {
|
|
181
|
+
// Fetch data
|
|
182
|
+
try {
|
|
183
|
+
const metadata = await this.recommendationMetadataService.fetch(recommendation.url);
|
|
184
|
+
|
|
185
|
+
// Set null values to undefined so we don't trigger an update
|
|
186
|
+
recommendation.edit({
|
|
187
|
+
// Don't set title if it's already set on the recommendation
|
|
188
|
+
title: recommendation.title ? undefined : (metadata.title ?? undefined),
|
|
189
|
+
excerpt: metadata.excerpt ?? undefined,
|
|
190
|
+
featuredImage: metadata.featuredImage ?? undefined,
|
|
191
|
+
favicon: metadata.favicon ?? undefined,
|
|
192
|
+
oneClickSubscribe: !!metadata.oneClickSubscribe
|
|
193
|
+
});
|
|
194
|
+
} catch (e) {
|
|
195
|
+
logging.error('[Recommendations] Failed to update metadata for recommendation ' + recommendation.url.toString(), e);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async editRecommendation(id: string, recommendationEdit: Partial<Recommendation>): Promise<RecommendationPlain> {
|
|
200
|
+
// Check if it exists
|
|
201
|
+
const existing = await this.repository.getById(id);
|
|
202
|
+
if (!existing) {
|
|
203
|
+
throw new errors.NotFoundError({
|
|
204
|
+
message: tpl(messages.notFound, {id})
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
existing.edit(recommendationEdit);
|
|
209
|
+
await this._updateRecommendationMetadata(existing);
|
|
210
|
+
await this.repository.save(existing);
|
|
211
|
+
|
|
212
|
+
const recommendations = await this.#listRecommendations();
|
|
213
|
+
await this.updateWellknown(recommendations);
|
|
214
|
+
|
|
215
|
+
this.sendMentionToRecommendation(existing);
|
|
216
|
+
return existing.plain;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async deleteRecommendation(id: string) {
|
|
220
|
+
const existing = await this.repository.getById(id);
|
|
221
|
+
if (!existing) {
|
|
222
|
+
throw new errors.NotFoundError({
|
|
223
|
+
message: tpl(messages.notFound, {id})
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
existing.delete();
|
|
228
|
+
await this.repository.save(existing);
|
|
229
|
+
|
|
230
|
+
const recommendations = await this.#listRecommendations();
|
|
231
|
+
await this.updateWellknown(recommendations);
|
|
232
|
+
await this.updateRecommendationsEnabledSetting(recommendations);
|
|
233
|
+
|
|
234
|
+
// Send a mention (because it was deleted, according to the webmentions spec)
|
|
235
|
+
this.sendMentionToRecommendation(existing);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Sames as listRecommendations, but returns Entities instead of plain objects (Entities are only used internally)
|
|
240
|
+
*/
|
|
241
|
+
async #listRecommendations(options: { filter?: string; order?: OrderOption<Recommendation>; page?: number; limit?: number|'all', include?: IncludeOption<Recommendation> } = {page: 1, limit: 'all'}): Promise<Recommendation[]> {
|
|
242
|
+
if (options.limit === 'all') {
|
|
243
|
+
return await this.repository.getAll({
|
|
244
|
+
...options
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return await this.repository.getPage({
|
|
248
|
+
...options,
|
|
249
|
+
page: options.page || 1,
|
|
250
|
+
limit: options.limit || 15
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async listRecommendations(options: { filter?: string; order?: OrderOption<Recommendation>; page?: number; limit?: number|'all', include?: IncludeOption<Recommendation> } = {page: 1, limit: 'all', include: []}): Promise<RecommendationPlain[]> {
|
|
255
|
+
const list = await this.#listRecommendations(options);
|
|
256
|
+
return list.map(e => e.plain);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async countRecommendations({filter}: { filter?: string }) {
|
|
260
|
+
return await this.repository.getCount({filter});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async trackClicked({id, memberId}: { id: string, memberId?: string }) {
|
|
264
|
+
const clickEvent = ClickEvent.create({recommendationId: id, memberId});
|
|
265
|
+
await this.clickEventRepository.save(clickEvent);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async trackSubscribed({id, memberId}: { id: string, memberId: string }) {
|
|
269
|
+
const subscribeEvent = SubscribeEvent.create({recommendationId: id, memberId});
|
|
270
|
+
await this.subscribeEventRepository.save(subscribeEvent);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async readRecommendationByUrl(url: URL): Promise<RecommendationPlain|null> {
|
|
274
|
+
const recommendation = await this.repository.getByUrl(url);
|
|
275
|
+
|
|
276
|
+
if (!recommendation) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return recommendation.plain;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SubscribeEvent = void 0;
|
|
7
|
+
const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
8
|
+
class SubscribeEvent {
|
|
9
|
+
id;
|
|
10
|
+
recommendationId;
|
|
11
|
+
memberId;
|
|
12
|
+
createdAt;
|
|
13
|
+
get deleted() {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
constructor(data) {
|
|
17
|
+
this.id = data.id;
|
|
18
|
+
this.recommendationId = data.recommendationId;
|
|
19
|
+
this.memberId = data.memberId;
|
|
20
|
+
this.createdAt = data.createdAt;
|
|
21
|
+
}
|
|
22
|
+
static create(data) {
|
|
23
|
+
const id = data.id ?? (0, bson_objectid_1.default)().toString();
|
|
24
|
+
const d = {
|
|
25
|
+
id,
|
|
26
|
+
recommendationId: data.recommendationId,
|
|
27
|
+
memberId: data.memberId,
|
|
28
|
+
createdAt: data.createdAt ?? new Date()
|
|
29
|
+
};
|
|
30
|
+
return new SubscribeEvent(d);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.SubscribeEvent = SubscribeEvent;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ObjectId from 'bson-objectid';
|
|
2
|
+
|
|
3
|
+
export class SubscribeEvent {
|
|
4
|
+
id: string;
|
|
5
|
+
recommendationId: string;
|
|
6
|
+
memberId: string|null;
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
|
|
9
|
+
get deleted() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private constructor(data: {id: string, recommendationId: string, memberId: string|null, createdAt: Date}) {
|
|
14
|
+
this.id = data.id;
|
|
15
|
+
this.recommendationId = data.recommendationId;
|
|
16
|
+
this.memberId = data.memberId;
|
|
17
|
+
this.createdAt = data.createdAt;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static create(data: {id?: string, recommendationId: string, memberId: string, createdAt?: Date}) {
|
|
21
|
+
const id = data.id ?? ObjectId().toString();
|
|
22
|
+
|
|
23
|
+
const d = {
|
|
24
|
+
id,
|
|
25
|
+
recommendationId: data.recommendationId,
|
|
26
|
+
memberId: data.memberId,
|
|
27
|
+
createdAt: data.createdAt ?? new Date()
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return new SubscribeEvent(d);
|
|
31
|
+
}
|
|
32
|
+
}
|