ghost 5.110.3 → 5.111.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-activitypub-5.111.0.tgz +0 -0
- package/components/tryghost-adapter-cache-memory-ttl-5.111.0.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.111.0.tgz +0 -0
- package/components/{tryghost-adapter-manager-5.110.3.tgz → tryghost-adapter-manager-5.111.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.111.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.110.3.tgz → tryghost-api-framework-5.111.0.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.111.0.tgz +0 -0
- package/components/{tryghost-audience-feedback-5.110.3.tgz → tryghost-audience-feedback-5.111.0.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.111.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.111.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.111.0.tgz +0 -0
- package/components/tryghost-constants-5.111.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.111.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.110.3.tgz → tryghost-custom-theme-settings-service-5.111.0.tgz} +0 -0
- package/components/{tryghost-data-generator-5.110.3.tgz → tryghost-data-generator-5.111.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.111.0.tgz +0 -0
- package/components/tryghost-donations-5.111.0.tgz +0 -0
- package/components/{tryghost-email-addresses-5.110.3.tgz → tryghost-email-addresses-5.111.0.tgz} +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.111.0.tgz +0 -0
- package/components/{tryghost-email-analytics-service-5.110.3.tgz → tryghost-email-analytics-service-5.111.0.tgz} +0 -0
- package/components/tryghost-email-content-generator-5.111.0.tgz +0 -0
- package/components/tryghost-email-events-5.111.0.tgz +0 -0
- package/components/tryghost-email-service-5.111.0.tgz +0 -0
- package/components/{tryghost-email-suppression-list-5.110.3.tgz → tryghost-email-suppression-list-5.111.0.tgz} +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.110.3.tgz → tryghost-express-dynamic-redirects-5.111.0.tgz} +0 -0
- package/components/tryghost-external-media-inliner-5.111.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.111.0.tgz +0 -0
- package/components/tryghost-ghost-5.111.0.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.110.3.tgz → tryghost-html-to-plaintext-5.111.0.tgz} +0 -0
- package/components/tryghost-i18n-5.111.0.tgz +0 -0
- package/components/tryghost-identity-token-service-5.111.0.tgz +0 -0
- package/components/{tryghost-importer-handler-content-files-5.110.3.tgz → tryghost-importer-handler-content-files-5.111.0.tgz} +0 -0
- package/components/{tryghost-importer-revue-5.110.3.tgz → tryghost-importer-revue-5.111.0.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.111.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.110.3.tgz → tryghost-job-manager-5.111.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.111.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.111.0.tgz +0 -0
- package/components/{tryghost-magic-link-5.110.3.tgz → tryghost-magic-link-5.111.0.tgz} +0 -0
- package/components/tryghost-mail-events-5.111.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.111.0.tgz +0 -0
- package/components/{tryghost-member-attribution-5.110.3.tgz → tryghost-member-attribution-5.111.0.tgz} +0 -0
- package/components/tryghost-member-events-5.111.0.tgz +0 -0
- package/components/{tryghost-members-api-5.110.3.tgz → tryghost-members-api-5.111.0.tgz} +0 -0
- package/components/{tryghost-members-csv-5.110.3.tgz → tryghost-members-csv-5.111.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.110.3.tgz → tryghost-members-importer-5.111.0.tgz} +0 -0
- package/components/{tryghost-members-offers-5.110.3.tgz → tryghost-members-offers-5.111.0.tgz} +0 -0
- package/components/tryghost-members-payments-5.111.0.tgz +0 -0
- package/components/{tryghost-members-ssr-5.110.3.tgz → tryghost-members-ssr-5.111.0.tgz} +0 -0
- package/components/{tryghost-members-stripe-service-5.110.3.tgz → tryghost-members-stripe-service-5.111.0.tgz} +0 -0
- package/components/tryghost-milestones-5.111.0.tgz +0 -0
- package/components/{tryghost-minifier-5.110.3.tgz → tryghost-minifier-5.111.0.tgz} +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.110.3.tgz → tryghost-mw-api-version-mismatch-5.111.0.tgz} +0 -0
- package/components/tryghost-mw-cache-control-5.111.0.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.110.3.tgz → tryghost-mw-error-handler-5.111.0.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.111.0.tgz +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.110.3.tgz → tryghost-mw-update-user-last-seen-5.111.0.tgz} +0 -0
- package/components/tryghost-mw-version-match-5.111.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.111.0.tgz +0 -0
- package/components/tryghost-package-json-5.111.0.tgz +0 -0
- package/components/tryghost-post-events-5.111.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.111.0.tgz +0 -0
- package/components/{tryghost-posts-service-5.110.3.tgz → tryghost-posts-service-5.111.0.tgz} +0 -0
- package/components/tryghost-prometheus-metrics-5.111.0.tgz +0 -0
- package/components/tryghost-recommendations-5.111.0.tgz +0 -0
- package/components/tryghost-referrers-5.111.0.tgz +0 -0
- package/components/{tryghost-security-5.110.3.tgz → tryghost-security-5.111.0.tgz} +0 -0
- package/components/{tryghost-session-service-5.110.3.tgz → tryghost-session-service-5.111.0.tgz} +0 -0
- package/components/tryghost-settings-path-manager-5.111.0.tgz +0 -0
- package/components/{tryghost-slack-notifications-5.110.3.tgz → tryghost-slack-notifications-5.111.0.tgz} +0 -0
- package/components/{tryghost-stats-service-5.110.3.tgz → tryghost-stats-service-5.111.0.tgz} +0 -0
- package/components/{tryghost-tiers-5.110.3.tgz → tryghost-tiers-5.111.0.tgz} +0 -0
- package/components/tryghost-version-notifications-data-service-5.111.0.tgz +0 -0
- package/components/tryghost-webmentions-5.111.0.tgz +0 -0
- package/core/boot.js +3 -0
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +11401 -9442
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +2 -2
- package/core/built/admin/assets/admin-x-demo/{index-82e381fb.mjs → index-0040480a.mjs} +3252 -2891
- package/core/built/admin/assets/admin-x-demo/{modals-b20a9ede.mjs → modals-fb35c86c.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-ea62c29b.mjs → CodeEditorView-1298238e.mjs} +624 -618
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-4b25c788.mjs → index-0f51ccb5.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-af8cf9cf.mjs → index-2707471f.mjs} +6759 -6372
- package/core/built/admin/assets/admin-x-settings/{modals-cb2dc7b7.mjs → modals-f5983704.mjs} +7887 -7668
- package/core/built/admin/assets/{chunk.524.01cdbeb306d32d4ff118.js → chunk.524.405c43b2cb20553b51d9.js} +8 -8
- package/core/built/admin/assets/{chunk.582.3351d053b74b1a1748c0.js → chunk.582.eb4b096f29c97c9d6a64.js} +9 -9
- package/core/built/admin/assets/{ghost-98d002d50a5e01d2100b2c387a849249.js → ghost-87cffc153ec73d217c1ae9f9207ea5e1.js} +36 -37
- package/core/built/admin/assets/koenig-lexical/index.css +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +16898 -16284
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +224 -197
- package/core/built/admin/assets/posts/posts.js +24137 -24156
- package/core/built/admin/index.html +3 -3
- package/core/demo.js +6 -0
- package/core/demo.ts +3 -0
- package/core/frontend/services/sitemap/SiteMapManager.js +1 -1
- package/core/frontend/src/cards/css/cta.css +13 -5
- package/core/frontend/src/cards/css/video.css +1 -0
- package/core/server/api/endpoints/settings-public.js +3 -2
- package/core/server/api/endpoints/utils/serializers/input/settings.js +2 -1
- package/core/server/data/migrations/versions/5.111/2025-03-05-16-36-39-add-captcha-setting.js +8 -0
- package/core/server/data/schema/default-settings/default-settings.json +8 -0
- package/core/server/services/link-tracking/ClickEvent.js +25 -0
- package/core/server/services/link-tracking/FullPostLink.js +36 -0
- package/core/server/services/link-tracking/LinkClickRepository.js +1 -1
- package/core/server/services/link-tracking/LinkClickTrackingService.js +237 -0
- package/core/server/services/link-tracking/PostLink.js +29 -0
- package/core/server/services/link-tracking/PostLinkRepository.js +2 -2
- package/core/server/services/link-tracking/index.js +1 -1
- package/core/server/services/members-events/EventStorage.js +61 -0
- package/core/server/services/members-events/LastSeenAtCache.js +96 -0
- package/core/server/services/members-events/LastSeenAtUpdater.js +192 -0
- package/core/server/services/members-events/index.js +3 -1
- package/core/server/services/mentions-email-report/MentionEmailReportJob.js +117 -0
- package/core/server/services/mentions-email-report/service.js +3 -3
- package/core/server/services/staff/StaffService.js +179 -0
- package/core/server/services/staff/StaffServiceEmails.js +527 -0
- package/core/server/services/staff/email-templates/donation.hbs +119 -0
- package/core/server/services/staff/email-templates/donation.txt.js +15 -0
- package/core/server/services/staff/email-templates/mention-report.hbs +136 -0
- package/core/server/services/staff/email-templates/mention-report.txt.js +19 -0
- package/core/server/services/staff/email-templates/new-free-signup.hbs +118 -0
- package/core/server/services/staff/email-templates/new-free-signup.txt.js +13 -0
- package/core/server/services/staff/email-templates/new-milestone-received.hbs +142 -0
- package/core/server/services/staff/email-templates/new-milestone-received.txt.js +13 -0
- package/core/server/services/staff/email-templates/new-paid-cancellation.hbs +125 -0
- package/core/server/services/staff/email-templates/new-paid-cancellation.txt.js +13 -0
- package/core/server/services/staff/email-templates/new-paid-started.hbs +124 -0
- package/core/server/services/staff/email-templates/new-paid-started.txt.js +13 -0
- package/core/server/services/staff/email-templates/partials/preview.hbs +6 -0
- package/core/server/services/staff/email-templates/partials/styles.hbs +114 -0
- package/core/server/services/staff/email-templates/recommendation-received.hbs +154 -0
- package/core/server/services/staff/email-templates/recommendation-received.txt.js +13 -0
- package/core/server/services/staff/index.js +1 -1
- package/core/server/services/staff/milestone-email-config.js +207 -0
- package/core/server/services/url/Resources.js +1 -1
- package/core/shared/config/defaults.json +2 -1
- package/core/shared/events/URLResourceUpdatedEvent.js +33 -0
- package/core/shared/settings-cache/public.js +1 -0
- package/package.json +157 -157
- package/tsconfig.json +105 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/yarn.lock +342 -89
- package/components/tryghost-activitypub-5.110.3.tgz +0 -0
- package/components/tryghost-adapter-cache-memory-ttl-5.110.3.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.110.3.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.110.3.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.110.3.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.110.3.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.110.3.tgz +0 -0
- package/components/tryghost-captcha-service-5.110.3.tgz +0 -0
- package/components/tryghost-constants-5.110.3.tgz +0 -0
- package/components/tryghost-custom-fonts-5.110.3.tgz +0 -0
- package/components/tryghost-domain-events-5.110.3.tgz +0 -0
- package/components/tryghost-donations-5.110.3.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.110.3.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.110.3.tgz +0 -0
- package/components/tryghost-email-content-generator-5.110.3.tgz +0 -0
- package/components/tryghost-email-events-5.110.3.tgz +0 -0
- package/components/tryghost-email-service-5.110.3.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.110.3.tgz +0 -0
- package/components/tryghost-extract-api-key-5.110.3.tgz +0 -0
- package/components/tryghost-ghost-5.110.3.tgz +0 -0
- package/components/tryghost-i18n-5.110.3.tgz +0 -0
- package/components/tryghost-identity-token-service-5.110.3.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.110.3.tgz +0 -0
- package/components/tryghost-link-redirects-5.110.3.tgz +0 -0
- package/components/tryghost-link-replacer-5.110.3.tgz +0 -0
- package/components/tryghost-link-tracking-5.110.3.tgz +0 -0
- package/components/tryghost-mail-events-5.110.3.tgz +0 -0
- package/components/tryghost-mailgun-client-5.110.3.tgz +0 -0
- package/components/tryghost-member-events-5.110.3.tgz +0 -0
- package/components/tryghost-members-events-service-5.110.3.tgz +0 -0
- package/components/tryghost-members-payments-5.110.3.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.110.3.tgz +0 -0
- package/components/tryghost-milestones-5.110.3.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.110.3.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.110.3.tgz +0 -0
- package/components/tryghost-mw-version-match-5.110.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.110.3.tgz +0 -0
- package/components/tryghost-package-json-5.110.3.tgz +0 -0
- package/components/tryghost-post-events-5.110.3.tgz +0 -0
- package/components/tryghost-post-revisions-5.110.3.tgz +0 -0
- package/components/tryghost-prometheus-metrics-5.110.3.tgz +0 -0
- package/components/tryghost-recommendations-5.110.3.tgz +0 -0
- package/components/tryghost-referrers-5.110.3.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.110.3.tgz +0 -0
- package/components/tryghost-staff-service-5.110.3.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.110.3.tgz +0 -0
- package/components/tryghost-webmentions-5.110.3.tgz +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.111%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%2200504b8fb4%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2215e68d0314%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22d2bf2d8e82%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22c055bcc64c%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22d66064f4d7%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-fca15534b8426c0567400113c63a3e21.js"></script>
|
|
60
60
|
<script src="assets/chunk.874.461cb3cf5b6b36915f8c.js"></script>
|
|
61
|
-
<script src="assets/chunk.524.
|
|
62
|
-
<script src="assets/ghost-
|
|
61
|
+
<script src="assets/chunk.524.405c43b2cb20553b51d9.js"></script>
|
|
62
|
+
<script src="assets/ghost-87cffc153ec73d217c1ae9f9207ea5e1.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
package/core/demo.js
ADDED
package/core/demo.ts
ADDED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const DomainEvents = require('@tryghost/domain-events');
|
|
2
|
-
const
|
|
2
|
+
const URLResourceUpdatedEvent = require('../../../shared/events/URLResourceUpdatedEvent');
|
|
3
3
|
const IndexMapGenerator = require('./SiteMapIndexGenerator');
|
|
4
4
|
const PagesMapGenerator = require('./PageMapGenerator');
|
|
5
5
|
const PostsMapGenerator = require('./PostMapGenerator');
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
.kg-cta-sponsor-label p span:not(a span) {
|
|
69
|
-
color: color-mix(in srgb, currentColor
|
|
69
|
+
color: color-mix(in srgb, currentColor 45%, transparent);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.kg-cta-sponsor-label a {
|
|
@@ -170,17 +170,25 @@ a.kg-cta-button {
|
|
|
170
170
|
display: flex;
|
|
171
171
|
position: static;
|
|
172
172
|
align-items: center;
|
|
173
|
-
padding: 0 2rem;
|
|
174
|
-
height: 2.4em;
|
|
175
173
|
justify-content: center;
|
|
176
174
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
177
|
-
font-
|
|
178
|
-
|
|
175
|
+
font-weight: 500;
|
|
176
|
+
line-height: 1.65;
|
|
179
177
|
text-decoration: none;
|
|
180
178
|
border-radius: 6px;
|
|
181
179
|
transition: opacity 0.2s ease-in-out;
|
|
182
180
|
}
|
|
183
181
|
|
|
182
|
+
.kg-cta-minimal a.kg-cta-button {
|
|
183
|
+
padding: 0 2rem;
|
|
184
|
+
height: 2.4em;
|
|
185
|
+
font-size: 0.95em;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.kg-cta-immersive a.kg-cta-button {
|
|
189
|
+
padding: .8rem 2rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
184
192
|
a.kg-cta-button:hover {
|
|
185
193
|
opacity: 0.85;
|
|
186
194
|
}
|
|
@@ -7,11 +7,12 @@ const labs = require('../../../shared/labs');
|
|
|
7
7
|
const getCaptchaSettings = () => {
|
|
8
8
|
if (labs.isSet('captcha')) {
|
|
9
9
|
return {
|
|
10
|
-
captcha_enabled: config.get('captcha:enabled'),
|
|
11
10
|
captcha_sitekey: config.get('captcha:siteKey')
|
|
12
11
|
};
|
|
13
12
|
} else {
|
|
14
|
-
return {
|
|
13
|
+
return {
|
|
14
|
+
captcha_enabled: false
|
|
15
|
+
};
|
|
15
16
|
}
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -319,6 +319,14 @@
|
|
|
319
319
|
"blocked_email_domains": {
|
|
320
320
|
"defaultValue": "[]",
|
|
321
321
|
"type": "array"
|
|
322
|
+
},
|
|
323
|
+
"captcha_enabled": {
|
|
324
|
+
"defaultValue": "false",
|
|
325
|
+
"validations": {
|
|
326
|
+
"isEmpty": false,
|
|
327
|
+
"isIn": [["true", "false"]]
|
|
328
|
+
},
|
|
329
|
+
"type": "boolean"
|
|
322
330
|
}
|
|
323
331
|
},
|
|
324
332
|
"portal": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const ObjectID = require('bson-objectid').default;
|
|
2
|
+
|
|
3
|
+
module.exports = class ClickEvent {
|
|
4
|
+
/** @type {ObjectID} */
|
|
5
|
+
event_id;
|
|
6
|
+
/** @type {string} */
|
|
7
|
+
member_uuid;
|
|
8
|
+
/** @type {ObjectID} */
|
|
9
|
+
link_id;
|
|
10
|
+
|
|
11
|
+
constructor(data) {
|
|
12
|
+
if (!data.id) {
|
|
13
|
+
this.event_id = new ObjectID();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof data.id === 'string') {
|
|
17
|
+
this.event_id = ObjectID.createFromHexString(data.id);
|
|
18
|
+
} else {
|
|
19
|
+
this.event_id = data.id;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.member_uuid = data.member_uuid;
|
|
23
|
+
this.link_id = data.link_id;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const ObjectID = require('bson-objectid').default;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} FullPostLinkCount
|
|
5
|
+
* @property {number} clicks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stores the connection between a LinkRedirect and a Post
|
|
10
|
+
*/
|
|
11
|
+
module.exports = class FullPostLink {
|
|
12
|
+
/** @type {ObjectID} */
|
|
13
|
+
post_id;
|
|
14
|
+
|
|
15
|
+
/** @type {import('@tryghost/link-redirects/lib/LinkRedirect')} */
|
|
16
|
+
link;
|
|
17
|
+
|
|
18
|
+
/** @type {FullPostLinkCount} */
|
|
19
|
+
count;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} data
|
|
23
|
+
* @param {string|ObjectID} data.post_id
|
|
24
|
+
* @param {import('@tryghost/link-redirects/lib/LinkRedirect')} data.link
|
|
25
|
+
* @param {FullPostLinkCount} data.count
|
|
26
|
+
*/
|
|
27
|
+
constructor(data) {
|
|
28
|
+
if (typeof data.post_id === 'string') {
|
|
29
|
+
this.post_id = ObjectID.createFromHexString(data.post_id);
|
|
30
|
+
} else {
|
|
31
|
+
this.post_id = data.post_id;
|
|
32
|
+
}
|
|
33
|
+
this.link = data.link;
|
|
34
|
+
this.count = data.count;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const {RedirectEvent} = require('@tryghost/link-redirects');
|
|
2
|
+
const LinkClick = require('./ClickEvent');
|
|
3
|
+
const PostLink = require('./PostLink');
|
|
4
|
+
const ObjectID = require('bson-objectid').default;
|
|
5
|
+
const errors = require('@tryghost/errors');
|
|
6
|
+
const nql = require('@tryghost/nql');
|
|
7
|
+
const _ = require('lodash');
|
|
8
|
+
const tpl = require('@tryghost/tpl');
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} ILinkClickRepository
|
|
13
|
+
* @prop {(event: LinkClick) => Promise<void>} save
|
|
14
|
+
* @prop {({filter: string}) => Promise<LinkClick[]>} getAll
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} ILinkRedirect
|
|
19
|
+
* @prop {ObjectID} link_id
|
|
20
|
+
* @prop {URL} to
|
|
21
|
+
* @prop {URL} from
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {import('./FullPostLink')} FullPostLink
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} ILinkRedirectService
|
|
30
|
+
* @prop {(to: URL, slug: string) => Promise<ILinkRedirect>} addRedirect
|
|
31
|
+
* @prop {() => Promise<string>} getSlug
|
|
32
|
+
* @prop {({filter: string}) => Promise<ILinkRedirect[]>} getAll
|
|
33
|
+
* @prop {({filter: string}) => Promise<string[]>} getFilteredIds
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} IPostLinkRepository
|
|
38
|
+
* @prop {(postLink: PostLink) => Promise<void>} save
|
|
39
|
+
* @prop {({filter: string}) => Promise<FullPostLink[]>} getAll
|
|
40
|
+
* @prop {(linkIds: array, data, options) => Promise<FullPostLink[]>} updateLinks
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const messages = {
|
|
44
|
+
invalidFilter: 'Invalid filter value received',
|
|
45
|
+
unsupportedBulkAction: 'Unsupported bulk action',
|
|
46
|
+
invalidRedirectUrl: 'Invalid redirect URL value'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
class LinkClickTrackingService {
|
|
50
|
+
#initialised = false;
|
|
51
|
+
|
|
52
|
+
/** @type ILinkClickRepository */
|
|
53
|
+
#linkClickRepository;
|
|
54
|
+
/** @type ILinkRedirectService */
|
|
55
|
+
#linkRedirectService;
|
|
56
|
+
/** @type IPostLinkRepository */
|
|
57
|
+
#postLinkRepository;
|
|
58
|
+
/** @type DomainEvents */
|
|
59
|
+
#DomainEvents;
|
|
60
|
+
/** @type {Object} */
|
|
61
|
+
#LinkRedirect;
|
|
62
|
+
/** @type {Object} */
|
|
63
|
+
#urlUtils;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {object} deps
|
|
67
|
+
* @param {ILinkClickRepository} deps.linkClickRepository
|
|
68
|
+
* @param {ILinkRedirectService} deps.linkRedirectService
|
|
69
|
+
* @param {IPostLinkRepository} deps.postLinkRepository
|
|
70
|
+
* @param {DomainEvents} deps.DomainEvents
|
|
71
|
+
* @param {urlUtils} deps.urlUtils
|
|
72
|
+
*/
|
|
73
|
+
constructor(deps) {
|
|
74
|
+
this.#linkClickRepository = deps.linkClickRepository;
|
|
75
|
+
this.#linkRedirectService = deps.linkRedirectService;
|
|
76
|
+
this.#postLinkRepository = deps.postLinkRepository;
|
|
77
|
+
this.#DomainEvents = deps.DomainEvents;
|
|
78
|
+
this.#urlUtils = deps.urlUtils;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async init() {
|
|
82
|
+
if (this.#initialised) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.subscribe();
|
|
86
|
+
this.#initialised = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {object} options
|
|
91
|
+
* @param {string} options.filter
|
|
92
|
+
* @return {Promise<FullPostLink[]>}
|
|
93
|
+
*/
|
|
94
|
+
async getLinks(options) {
|
|
95
|
+
return await this.#postLinkRepository.getAll({
|
|
96
|
+
filter: options.filter
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* validate and manage the new redirect url in filter
|
|
102
|
+
* `to` url needs decoding and transformation to relative url for comparision
|
|
103
|
+
* @param {string} filter
|
|
104
|
+
* @returns {Object} parsed filter
|
|
105
|
+
* @throws {errors.BadRequestError}
|
|
106
|
+
*/
|
|
107
|
+
#parseLinkFilter(filter) {
|
|
108
|
+
try {
|
|
109
|
+
const filterJson = nql(filter).parse();
|
|
110
|
+
const postId = filterJson?.$and?.[0]?.post_id;
|
|
111
|
+
const redirectUrl = new URL(filterJson?.$and?.[1]?.to);
|
|
112
|
+
if (!postId || !redirectUrl) {
|
|
113
|
+
throw new errors.BadRequestError({
|
|
114
|
+
message: tpl(messages.invalidFilter)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
postId,
|
|
119
|
+
redirectUrl
|
|
120
|
+
};
|
|
121
|
+
} catch (e) {
|
|
122
|
+
throw new errors.BadRequestError({
|
|
123
|
+
message: tpl(messages.invalidFilter),
|
|
124
|
+
context: e.message
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#getRedirectLinkWithAttribution({newLink, oldLink, postId}) {
|
|
130
|
+
const newUrl = new URL(newLink);
|
|
131
|
+
const oldUrl = new URL(oldLink);
|
|
132
|
+
// append newsletter ref query param from oldUrl to newUrl
|
|
133
|
+
if (oldUrl.searchParams.has('ref')) {
|
|
134
|
+
newUrl.searchParams.set('ref', oldUrl.searchParams.get('ref'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// append post attribution to site urls
|
|
138
|
+
const isSite = this.#urlUtils.isSiteUrl(newUrl);
|
|
139
|
+
if (isSite) {
|
|
140
|
+
newUrl.searchParams.set('attribution_type', 'post');
|
|
141
|
+
newUrl.searchParams.set('attribution_id', postId);
|
|
142
|
+
}
|
|
143
|
+
return newUrl;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async #updateLinks(data, options) {
|
|
147
|
+
const filterOptions = _.pick(options, ['transacting', 'context', 'filter']);
|
|
148
|
+
|
|
149
|
+
// decode and parse filter to manage new redirect url
|
|
150
|
+
const {postId, redirectUrl} = this.#parseLinkFilter(filterOptions.filter);
|
|
151
|
+
|
|
152
|
+
// manages transformation of current url to relative for comparision
|
|
153
|
+
const transformedOldUrl = this.#urlUtils.absoluteToTransformReady(redirectUrl.href);
|
|
154
|
+
const filterQuery = `post_id:'${postId}'+to:'${transformedOldUrl}'`;
|
|
155
|
+
|
|
156
|
+
const updatedFilterOptions = {
|
|
157
|
+
...filterOptions,
|
|
158
|
+
filter: filterQuery
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// get new redirect link with proper attribution
|
|
162
|
+
const newRedirectUrl = this.#getRedirectLinkWithAttribution({
|
|
163
|
+
newLink: data.meta?.link?.to,
|
|
164
|
+
oldLink: redirectUrl.href,
|
|
165
|
+
postId
|
|
166
|
+
});
|
|
167
|
+
const linkIds = await this.#linkRedirectService.getFilteredIds(updatedFilterOptions);
|
|
168
|
+
|
|
169
|
+
const bulkUpdateOptions = _.pick(options, ['transacting']);
|
|
170
|
+
const updateData = {
|
|
171
|
+
to: this.#urlUtils.absoluteToTransformReady(newRedirectUrl.href),
|
|
172
|
+
updated_at: moment().format('YYYY-MM-DD HH:mm:ss')
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return await this.#postLinkRepository.updateLinks(linkIds, updateData, bulkUpdateOptions);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async bulkEdit(data, options) {
|
|
179
|
+
if (data.action === 'updateLink') {
|
|
180
|
+
return await this.#updateLinks(data, options);
|
|
181
|
+
}
|
|
182
|
+
throw new errors.IncorrectUsageError({
|
|
183
|
+
message: tpl(messages.unsupportedBulkAction)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @private (not using # to allow tests)
|
|
189
|
+
* Replace URL with a redirect that redirects to the original URL, and link that redirect with the given post
|
|
190
|
+
*/
|
|
191
|
+
async addRedirectToUrl(url, post) {
|
|
192
|
+
// Generate a unique redirect slug
|
|
193
|
+
const slugUrl = await this.#linkRedirectService.getSlugUrl();
|
|
194
|
+
|
|
195
|
+
// Add redirect for link click tracking
|
|
196
|
+
const redirect = await this.#linkRedirectService.addRedirect(slugUrl, url);
|
|
197
|
+
|
|
198
|
+
// Store a reference of the link against the post
|
|
199
|
+
const postLink = new PostLink({
|
|
200
|
+
link_id: redirect.link_id,
|
|
201
|
+
post_id: ObjectID.createFromHexString(post.id)
|
|
202
|
+
});
|
|
203
|
+
await this.#postLinkRepository.save(postLink);
|
|
204
|
+
|
|
205
|
+
return redirect.from;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Add tracking to a URL and returns a new URL (if link click tracking is enabled)
|
|
210
|
+
* @param {URL} url
|
|
211
|
+
* @param {Post} post
|
|
212
|
+
* @param {string} memberUuid
|
|
213
|
+
* @return {Promise<URL>}
|
|
214
|
+
*/
|
|
215
|
+
async addTrackingToUrl(url, post, memberUuid) {
|
|
216
|
+
url = await this.addRedirectToUrl(url, post);
|
|
217
|
+
url.searchParams.set('m', memberUuid);
|
|
218
|
+
return url;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
subscribe() {
|
|
222
|
+
this.#DomainEvents.subscribe(RedirectEvent, async (event) => {
|
|
223
|
+
const uuid = event.data.url.searchParams.get('m');
|
|
224
|
+
if (!uuid) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const click = new LinkClick({
|
|
229
|
+
member_uuid: uuid,
|
|
230
|
+
link_id: event.data.link.link_id
|
|
231
|
+
});
|
|
232
|
+
await this.#linkClickRepository.save(click);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = LinkClickTrackingService;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ObjectID = require('bson-objectid').default;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stores the connection between a LinkRedirect and a Post
|
|
5
|
+
*/
|
|
6
|
+
module.exports = class PostLink {
|
|
7
|
+
/** @type {ObjectID} */
|
|
8
|
+
post_id;
|
|
9
|
+
/** @type {ObjectID} */
|
|
10
|
+
link_id;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} data
|
|
14
|
+
* @param {string|ObjectID} data.post_id
|
|
15
|
+
* @param {string|ObjectID} data.link_id
|
|
16
|
+
*/
|
|
17
|
+
constructor(data) {
|
|
18
|
+
if (typeof data.post_id === 'string') {
|
|
19
|
+
this.post_id = ObjectID.createFromHexString(data.post_id);
|
|
20
|
+
} else {
|
|
21
|
+
this.post_id = data.post_id;
|
|
22
|
+
}
|
|
23
|
+
if (typeof data.link_id === 'string') {
|
|
24
|
+
this.link_id = ObjectID.createFromHexString(data.link_id);
|
|
25
|
+
} else {
|
|
26
|
+
this.link_id = data.link_id;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const FullPostLink = require('./FullPostLink');
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('bson-objectid').default} ObjectID
|
|
6
|
-
* @typedef {import('
|
|
6
|
+
* @typedef {import('./PostLink')} PostLink
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
module.exports = class PostLinkRepository {
|
|
@@ -20,7 +20,7 @@ class LinkTrackingServiceWrapper {
|
|
|
20
20
|
const {MemberLinkClickEvent} = require('@tryghost/member-events');
|
|
21
21
|
const DomainEvents = require('@tryghost/domain-events');
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const LinkClickTrackingService = require('./LinkClickTrackingService');
|
|
24
24
|
|
|
25
25
|
const postLinkRepository = new PostLinkRepository({
|
|
26
26
|
LinkRedirect: models.Redirect,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const {MemberCreatedEvent, SubscriptionCreatedEvent} = require('@tryghost/member-events');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Store events in the database
|
|
5
|
+
*/
|
|
6
|
+
class EventStorage {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} deps
|
|
10
|
+
* @param {Object} deps.labsService
|
|
11
|
+
* @param {Object} deps.models
|
|
12
|
+
* @param {Object} deps.models.MemberCreatedEvent
|
|
13
|
+
* @param {Object} deps.models.SubscriptionCreatedEvent
|
|
14
|
+
*/
|
|
15
|
+
constructor({labsService, models}) {
|
|
16
|
+
this.models = models;
|
|
17
|
+
this.labsService = labsService;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to events of this domainEvents service
|
|
22
|
+
* @param {Object} domainEvents The DomainEvents service
|
|
23
|
+
*/
|
|
24
|
+
subscribe(domainEvents) {
|
|
25
|
+
domainEvents.subscribe(MemberCreatedEvent, async (event) => {
|
|
26
|
+
let attribution = event.data.attribution;
|
|
27
|
+
|
|
28
|
+
await this.models.MemberCreatedEvent.add({
|
|
29
|
+
member_id: event.data.memberId,
|
|
30
|
+
created_at: event.timestamp,
|
|
31
|
+
attribution_id: attribution?.id ?? null,
|
|
32
|
+
attribution_url: attribution?.url ?? null,
|
|
33
|
+
attribution_type: attribution?.type ?? null,
|
|
34
|
+
source: event.data.source,
|
|
35
|
+
referrer_source: attribution?.referrerSource ?? null,
|
|
36
|
+
referrer_medium: attribution?.referrerMedium ?? null,
|
|
37
|
+
referrer_url: attribution?.referrerUrl ?? null,
|
|
38
|
+
batch_id: event.data.batchId ?? null
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
domainEvents.subscribe(SubscriptionCreatedEvent, async (event) => {
|
|
43
|
+
let attribution = event.data.attribution;
|
|
44
|
+
|
|
45
|
+
await this.models.SubscriptionCreatedEvent.add({
|
|
46
|
+
member_id: event.data.memberId,
|
|
47
|
+
subscription_id: event.data.subscriptionId,
|
|
48
|
+
created_at: event.timestamp,
|
|
49
|
+
attribution_id: attribution?.id ?? null,
|
|
50
|
+
attribution_url: attribution?.url ?? null,
|
|
51
|
+
attribution_type: attribution?.type ?? null,
|
|
52
|
+
referrer_source: attribution?.referrerSource ?? null,
|
|
53
|
+
referrer_medium: attribution?.referrerMedium ?? null,
|
|
54
|
+
referrer_url: attribution?.referrerUrl ?? null,
|
|
55
|
+
batch_id: event.data.batchId ?? null
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = EventStorage;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const moment = require('moment-timezone');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A cache that stores the member ids that have been seen today. This cache is used to avoid having to query the database for the last_seen_at timestamp of a member multiple times in the same day.
|
|
5
|
+
*
|
|
6
|
+
* @constructor
|
|
7
|
+
* @param {Object} settingsCache - An instance of the settings cache
|
|
8
|
+
* @property {Set} _cache - A set that stores all the member ids that have been seen today
|
|
9
|
+
* @property {Object} _settingsCache - An instance of the settings cache
|
|
10
|
+
* @property {string} _startOfDay - The start of the current day in the site timezone, formatted in ISO 8601
|
|
11
|
+
*/
|
|
12
|
+
class LastSeenAtCache {
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} deps - Dependencies
|
|
16
|
+
* @param {Object} deps.services - The list of service dependencies
|
|
17
|
+
* @param {Object} deps.services.settingsCache - The settings service
|
|
18
|
+
*/
|
|
19
|
+
constructor({services: {settingsCache}}) {
|
|
20
|
+
this._cache = new Set();
|
|
21
|
+
this._settingsCache = settingsCache;
|
|
22
|
+
this._startOfDay = this._getStartOfCurrentDay();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @method add - Adds a member id to the cache
|
|
27
|
+
* @param {string} memberId
|
|
28
|
+
*/
|
|
29
|
+
add(memberId) {
|
|
30
|
+
this._cache.add(memberId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @method remove - Removes a member id from the cache
|
|
35
|
+
* @param {string} memberId
|
|
36
|
+
*/
|
|
37
|
+
remove(memberId) {
|
|
38
|
+
this._cache.delete(memberId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @method shouldUpdateMember - Checks if a member should be updated
|
|
43
|
+
* @param {string} memberId
|
|
44
|
+
* @returns {boolean} - Returns true if the member should be updated
|
|
45
|
+
*/
|
|
46
|
+
shouldUpdateMember(memberId) {
|
|
47
|
+
return !this._has(memberId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @method clear - Clears the cache
|
|
52
|
+
*/
|
|
53
|
+
clear() {
|
|
54
|
+
this._cache.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @method _has - Refreshes the cache and checks if a member id is in the cache
|
|
59
|
+
* @param {string} memberId
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
_has(memberId) {
|
|
63
|
+
this._refresh();
|
|
64
|
+
return this._cache.has(memberId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @method _shouldClear - Checks if the cache should be cleared, based on the current day
|
|
69
|
+
* @returns {boolean} - Returns true if the cache should be cleared
|
|
70
|
+
*/
|
|
71
|
+
_shouldClear() {
|
|
72
|
+
return this._startOfDay !== this._getStartOfCurrentDay();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @method _refresh - Clears the cache if the day has changed
|
|
77
|
+
*/
|
|
78
|
+
_refresh() {
|
|
79
|
+
if (this._shouldClear()) {
|
|
80
|
+
this.clear();
|
|
81
|
+
this._startOfDay = this._getStartOfCurrentDay();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns the start of the current day in the site timezone
|
|
87
|
+
* @returns {string} The start of the current day in the site timezone, formatted as a ISO 8601 string
|
|
88
|
+
*/
|
|
89
|
+
_getStartOfCurrentDay() {
|
|
90
|
+
const timezone = this._settingsCache.get('timezone') || 'Etc/UTC';
|
|
91
|
+
const startOfDay = moment().tz(timezone).startOf('day').utc().toISOString();
|
|
92
|
+
return startOfDay;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = LastSeenAtCache;
|