ghost 5.80.2 → 5.80.3
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-adapter-cache-memory-ttl-5.80.2.tgz → tryghost-adapter-cache-memory-ttl-5.80.3.tgz} +0 -0
- package/components/{tryghost-adapter-cache-redis-5.80.2.tgz → tryghost-adapter-cache-redis-5.80.3.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.80.2.tgz → tryghost-adapter-manager-5.80.3.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.80.3.tgz +0 -0
- package/components/{tryghost-api-framework-5.80.2.tgz → tryghost-api-framework-5.80.3.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.80.2.tgz → tryghost-api-version-compatibility-service-5.80.3.tgz} +0 -0
- package/components/tryghost-audience-feedback-5.80.3.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.80.3.tgz +0 -0
- package/components/{tryghost-bootstrap-socket-5.80.2.tgz → tryghost-bootstrap-socket-5.80.3.tgz} +0 -0
- package/components/tryghost-collections-5.80.3.tgz +0 -0
- package/components/{tryghost-constants-5.80.2.tgz → tryghost-constants-5.80.3.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.80.3.tgz +0 -0
- package/components/{tryghost-data-generator-5.80.2.tgz → tryghost-data-generator-5.80.3.tgz} +0 -0
- package/components/tryghost-domain-events-5.80.3.tgz +0 -0
- package/components/{tryghost-donations-5.80.2.tgz → tryghost-donations-5.80.3.tgz} +0 -0
- package/components/tryghost-dynamic-routing-events-5.80.3.tgz +0 -0
- package/components/tryghost-email-addresses-5.80.3.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.80.3.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.80.3.tgz +0 -0
- package/components/tryghost-email-content-generator-5.80.3.tgz +0 -0
- package/components/{tryghost-email-events-5.80.2.tgz → tryghost-email-events-5.80.3.tgz} +0 -0
- package/components/tryghost-email-service-5.80.3.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.80.3.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.80.2.tgz → tryghost-express-dynamic-redirects-5.80.3.tgz} +0 -0
- package/components/tryghost-external-media-inliner-5.80.3.tgz +0 -0
- package/components/tryghost-extract-api-key-5.80.3.tgz +0 -0
- package/components/tryghost-ghost-5.80.3.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.80.2.tgz → tryghost-html-to-plaintext-5.80.3.tgz} +0 -0
- package/components/tryghost-i18n-5.80.3.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.80.3.tgz +0 -0
- package/components/tryghost-importer-revue-5.80.3.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.80.3.tgz +0 -0
- package/components/tryghost-job-manager-5.80.3.tgz +0 -0
- package/components/tryghost-link-redirects-5.80.3.tgz +0 -0
- package/components/tryghost-link-replacer-5.80.3.tgz +0 -0
- package/components/tryghost-link-tracking-5.80.3.tgz +0 -0
- package/components/tryghost-magic-link-5.80.3.tgz +0 -0
- package/components/tryghost-mail-events-5.80.3.tgz +0 -0
- package/components/tryghost-mailgun-client-5.80.3.tgz +0 -0
- package/components/{tryghost-member-attribution-5.80.2.tgz → tryghost-member-attribution-5.80.3.tgz} +0 -0
- package/components/{tryghost-member-events-5.80.2.tgz → tryghost-member-events-5.80.3.tgz} +0 -0
- package/components/tryghost-members-api-5.80.3.tgz +0 -0
- package/components/{tryghost-members-csv-5.80.2.tgz → tryghost-members-csv-5.80.3.tgz} +0 -0
- package/components/{tryghost-members-events-service-5.80.2.tgz → tryghost-members-events-service-5.80.3.tgz} +0 -0
- package/components/tryghost-members-importer-5.80.3.tgz +0 -0
- package/components/{tryghost-members-offers-5.80.2.tgz → tryghost-members-offers-5.80.3.tgz} +0 -0
- package/components/tryghost-members-payments-5.80.3.tgz +0 -0
- package/components/tryghost-members-ssr-5.80.3.tgz +0 -0
- package/components/{tryghost-members-stripe-service-5.80.2.tgz → tryghost-members-stripe-service-5.80.3.tgz} +0 -0
- package/components/tryghost-mentions-email-report-5.80.3.tgz +0 -0
- package/components/tryghost-milestones-5.80.3.tgz +0 -0
- package/components/tryghost-minifier-5.80.3.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.80.3.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.80.3.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.80.3.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.80.2.tgz → tryghost-mw-error-handler-5.80.3.tgz} +0 -0
- package/components/{tryghost-mw-session-from-token-5.80.2.tgz → tryghost-mw-session-from-token-5.80.3.tgz} +0 -0
- package/components/{tryghost-mw-update-user-last-seen-5.80.2.tgz → tryghost-mw-update-user-last-seen-5.80.3.tgz} +0 -0
- package/components/tryghost-mw-version-match-5.80.3.tgz +0 -0
- package/components/tryghost-mw-vhost-5.80.3.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.80.3.tgz +0 -0
- package/components/tryghost-oembed-service-5.80.3.tgz +0 -0
- package/components/tryghost-package-json-5.80.3.tgz +0 -0
- package/components/{tryghost-post-events-5.80.2.tgz → tryghost-post-events-5.80.3.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.80.2.tgz → tryghost-post-revisions-5.80.3.tgz} +0 -0
- package/components/tryghost-posts-service-5.80.3.tgz +0 -0
- package/components/tryghost-recommendations-5.80.3.tgz +0 -0
- package/components/tryghost-referrers-5.80.3.tgz +0 -0
- package/components/{tryghost-security-5.80.2.tgz → tryghost-security-5.80.3.tgz} +0 -0
- package/components/tryghost-session-service-5.80.3.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.80.3.tgz +0 -0
- package/components/tryghost-slack-notifications-5.80.3.tgz +0 -0
- package/components/{tryghost-staff-service-5.80.2.tgz → tryghost-staff-service-5.80.3.tgz} +0 -0
- package/components/{tryghost-stats-service-5.80.2.tgz → tryghost-stats-service-5.80.3.tgz} +0 -0
- package/components/tryghost-tiers-5.80.3.tgz +0 -0
- package/components/{tryghost-update-check-service-5.80.2.tgz → tryghost-update-check-service-5.80.3.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.80.3.tgz +0 -0
- package/components/{tryghost-version-notifications-data-service-5.80.2.tgz → tryghost-version-notifications-data-service-5.80.3.tgz} +0 -0
- package/components/tryghost-webmentions-5.80.3.tgz +0 -0
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/screen.css +4 -0
- package/content/themes/casper/default.hbs +3 -3
- package/content/themes/casper/package.json +1 -1
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +3 -3
- 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 +8 -0
- package/content/themes/source/package.json +1 -1
- package/core/boot.js +27 -0
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0722d3aa.mjs → index-771f4964.mjs} +2 -2
- package/core/built/admin/assets/admin-x-demo/{modals-969dbbd3.mjs → modals-4c390700.mjs} +46 -46
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-78d4e20d.mjs → CodeEditorView-00610c2f.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-d4d158b7.mjs → index-2deb7a1f.mjs} +4273 -4908
- package/core/built/admin/assets/admin-x-settings/{index-8d67ef5d.mjs → index-e308aac8.mjs} +1845 -1814
- package/core/built/admin/assets/admin-x-settings/{modals-ae924d1e.mjs → modals-e2ca767c.mjs} +13378 -10736
- package/core/built/admin/assets/{chunk.524.c3472d95b4bd1a45a18d.js → chunk.524.c6ed93ff44a9cb49235c.js} +6 -6
- package/core/built/admin/assets/{chunk.582.c3f2b4d45f0b280c944c.js → chunk.582.4e31037f1eb4c88f017f.js} +4 -4
- package/core/built/admin/assets/{chunk.763.bd01d249eecdcce3856c.js → chunk.763.7849440ec6494d2449e9.js} +426 -624
- package/core/built/admin/assets/{ghost-ea7a51891dfca9b7a04aacfffe7dea4e.js → ghost-594a7dd26bdb4fec210b285cab5555cb.js} +14 -14
- package/core/built/admin/assets/{ghost-ab71ce6991cc9bdcd6b915a9c984fa27.css → ghost-71cfcdf9410af9b3aec11a28f9a4130f.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-fd04889e3b6ac569d2158c740dbcb9c5.css → ghost-dark-1e5bec737be037b1b5cb2a423603271d.css} +1 -1
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/get.js +89 -2
- package/core/frontend/public/robots.txt +0 -1
- package/core/frontend/services/rendering/renderer.js +19 -1
- package/core/frontend/services/theme-engine/i18n/I18n.js +7 -6
- package/core/frontend/services/theme-engine/i18n/ThemeI18n.js +4 -3
- package/core/server/adapters/storage/LocalStorageBase.js +13 -1
- package/core/server/api/endpoints/images.js +14 -3
- package/core/server/api/endpoints/utils/serializers/input/tiers.js +30 -2
- package/core/server/services/auth/api-key/admin.js +92 -85
- package/core/server/services/members/service.js +3 -1
- package/core/server/services/slack-notifications/service.js +6 -3
- package/core/server/services/themes/installer.js +1 -1
- package/core/server/services/tiers/TierRepository.js +2 -1
- package/core/server/web/admin/app.js +18 -1
- package/core/server/web/api/endpoints/admin/app.js +10 -0
- package/core/shared/labs.js +1 -0
- package/package.json +151 -149
- package/yarn.lock +517 -311
- package/components/tryghost-announcement-bar-settings-5.80.2.tgz +0 -0
- package/components/tryghost-audience-feedback-5.80.2.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.80.2.tgz +0 -0
- package/components/tryghost-collections-5.80.2.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.80.2.tgz +0 -0
- package/components/tryghost-domain-events-5.80.2.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.80.2.tgz +0 -0
- package/components/tryghost-email-addresses-5.80.2.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.80.2.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.80.2.tgz +0 -0
- package/components/tryghost-email-content-generator-5.80.2.tgz +0 -0
- package/components/tryghost-email-service-5.80.2.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.80.2.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.80.2.tgz +0 -0
- package/components/tryghost-extract-api-key-5.80.2.tgz +0 -0
- package/components/tryghost-i18n-5.80.2.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.80.2.tgz +0 -0
- package/components/tryghost-importer-revue-5.80.2.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.80.2.tgz +0 -0
- package/components/tryghost-job-manager-5.80.2.tgz +0 -0
- package/components/tryghost-link-redirects-5.80.2.tgz +0 -0
- package/components/tryghost-link-replacer-5.80.2.tgz +0 -0
- package/components/tryghost-link-tracking-5.80.2.tgz +0 -0
- package/components/tryghost-magic-link-5.80.2.tgz +0 -0
- package/components/tryghost-mail-events-5.80.2.tgz +0 -0
- package/components/tryghost-mailgun-client-5.80.2.tgz +0 -0
- package/components/tryghost-members-api-5.80.2.tgz +0 -0
- package/components/tryghost-members-importer-5.80.2.tgz +0 -0
- package/components/tryghost-members-payments-5.80.2.tgz +0 -0
- package/components/tryghost-members-ssr-5.80.2.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.80.2.tgz +0 -0
- package/components/tryghost-milestones-5.80.2.tgz +0 -0
- package/components/tryghost-minifier-5.80.2.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.80.2.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.80.2.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.80.2.tgz +0 -0
- package/components/tryghost-mw-version-match-5.80.2.tgz +0 -0
- package/components/tryghost-mw-vhost-5.80.2.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.80.2.tgz +0 -0
- package/components/tryghost-oembed-service-5.80.2.tgz +0 -0
- package/components/tryghost-package-json-5.80.2.tgz +0 -0
- package/components/tryghost-posts-service-5.80.2.tgz +0 -0
- package/components/tryghost-recommendations-5.80.2.tgz +0 -0
- package/components/tryghost-referrers-5.80.2.tgz +0 -0
- package/components/tryghost-session-service-5.80.2.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.80.2.tgz +0 -0
- package/components/tryghost-slack-notifications-5.80.2.tgz +0 -0
- package/components/tryghost-tiers-5.80.2.tgz +0 -0
- package/components/tryghost-verification-trigger-5.80.2.tgz +0 -0
- package/components/tryghost-webmentions-5.80.2.tgz +0 -0
- /package/core/built/admin/assets/{chunk.763.bd01d249eecdcce3856c.js.LICENSE.txt → chunk.763.7849440ec6494d2449e9.js.LICENSE.txt} +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.80%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%22c1a8c8a94f%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%
|
|
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.80%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%22c1a8c8a94f%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2224dcc6aee2%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%2233f4aa4a54%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-71cfcdf9410af9b3aec11a28f9a4130f.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-43a631b7c834235c4ff0b2186b5ca27d.js"></script>
|
|
60
|
-
<script src="assets/chunk.763.
|
|
61
|
-
<script src="assets/chunk.524.
|
|
62
|
-
<script src="assets/ghost-
|
|
60
|
+
<script src="assets/chunk.763.7849440ec6494d2449e9.js"></script>
|
|
61
|
+
<script src="assets/chunk.524.c6ed93ff44a9cb49235c.js"></script>
|
|
62
|
+
<script src="assets/ghost-594a7dd26bdb4fec210b285cab5555cb.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -11,6 +11,7 @@ const Sentry = require('@sentry/node');
|
|
|
11
11
|
|
|
12
12
|
const _ = require('lodash');
|
|
13
13
|
const jsonpath = require('jsonpath');
|
|
14
|
+
const nqlLang = require('@tryghost/nql-lang');
|
|
14
15
|
|
|
15
16
|
const messages = {
|
|
16
17
|
mustBeCalledAsBlock: 'The {\\{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}',
|
|
@@ -121,6 +122,84 @@ function parseOptions(globals, data, options) {
|
|
|
121
122
|
return options;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
function optimiseFilterCacheability(resource, options) {
|
|
126
|
+
const noOptimisation = {
|
|
127
|
+
options,
|
|
128
|
+
parseResult(result) {
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
if (resource !== 'posts') {
|
|
133
|
+
return noOptimisation;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!options.filter) {
|
|
137
|
+
return noOptimisation;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
if (options.filter.split('id:-').length !== 2) {
|
|
142
|
+
return noOptimisation;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const parsedFilter = nqlLang.parse(options.filter);
|
|
146
|
+
// Support either `id:blah` or `id:blah+other:stuff`
|
|
147
|
+
if (!parsedFilter.$and && !parsedFilter.id) {
|
|
148
|
+
return noOptimisation;
|
|
149
|
+
}
|
|
150
|
+
const queries = parsedFilter.$and || [parsedFilter];
|
|
151
|
+
const query = queries.find((q) => {
|
|
152
|
+
return q?.id?.$ne;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!query) {
|
|
156
|
+
return noOptimisation;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const idToFilter = query.id.$ne;
|
|
160
|
+
|
|
161
|
+
let limit = options.limit;
|
|
162
|
+
if (options.limit !== 'all') {
|
|
163
|
+
limit = options.limit ? 1 + parseInt(options.limit, 10) : 16;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// We replace with id:-null so we don't have to deal with leading/trailing AND operators
|
|
167
|
+
const filter = options.filter.replace(/id:-[a-f0-9A-F]{24}/, 'id:-null');
|
|
168
|
+
|
|
169
|
+
const parseResult = function parseResult(result) {
|
|
170
|
+
const filteredPosts = result?.posts?.filter((post) => {
|
|
171
|
+
return post.id !== idToFilter;
|
|
172
|
+
}) || [];
|
|
173
|
+
|
|
174
|
+
const modifiedResult = {
|
|
175
|
+
...result,
|
|
176
|
+
posts: limit === 'all' ? filteredPosts : filteredPosts.slice(0, limit - 1)
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
modifiedResult.meta = modifiedResult.meta || {};
|
|
180
|
+
modifiedResult.meta.cacheabilityOptimisation = true;
|
|
181
|
+
|
|
182
|
+
if (typeof modifiedResult?.meta?.pagination?.limit === 'number') {
|
|
183
|
+
modifiedResult.meta.pagination.limit = modifiedResult.meta.pagination.limit - 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return modifiedResult;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
options: {
|
|
191
|
+
...options,
|
|
192
|
+
limit,
|
|
193
|
+
filter
|
|
194
|
+
},
|
|
195
|
+
parseResult
|
|
196
|
+
};
|
|
197
|
+
} catch (err) {
|
|
198
|
+
logging.warn(err);
|
|
199
|
+
return noOptimisation;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
124
203
|
/**
|
|
125
204
|
*
|
|
126
205
|
* @param {String} resource
|
|
@@ -131,6 +210,12 @@ function parseOptions(globals, data, options) {
|
|
|
131
210
|
*/
|
|
132
211
|
async function makeAPICall(resource, controllerName, action, apiOptions) {
|
|
133
212
|
const controller = api[controllerName];
|
|
213
|
+
let makeRequest = options => controller[action](options);
|
|
214
|
+
|
|
215
|
+
const {
|
|
216
|
+
options,
|
|
217
|
+
parseResult
|
|
218
|
+
} = optimiseFilterCacheability(resource, apiOptions);
|
|
134
219
|
|
|
135
220
|
let timer;
|
|
136
221
|
|
|
@@ -141,7 +226,7 @@ async function makeAPICall(resource, controllerName, action, apiOptions) {
|
|
|
141
226
|
const logLevel = config.get('optimization:getHelper:timeout:level') || 'error';
|
|
142
227
|
const threshold = config.get('optimization:getHelper:timeout:threshold');
|
|
143
228
|
|
|
144
|
-
const apiResponse =
|
|
229
|
+
const apiResponse = makeRequest(options).then(parseResult);
|
|
145
230
|
|
|
146
231
|
const timeout = new Promise((resolve) => {
|
|
147
232
|
timer = setTimeout(() => {
|
|
@@ -161,7 +246,7 @@ async function makeAPICall(resource, controllerName, action, apiOptions) {
|
|
|
161
246
|
response = await Promise.race([apiResponse, timeout]);
|
|
162
247
|
clearTimeout(timer);
|
|
163
248
|
} else {
|
|
164
|
-
response = await
|
|
249
|
+
response = await makeRequest(options).then(parseResult);
|
|
165
250
|
}
|
|
166
251
|
|
|
167
252
|
return response;
|
|
@@ -279,3 +364,5 @@ module.exports = async function get(resource, options) {
|
|
|
279
364
|
};
|
|
280
365
|
|
|
281
366
|
module.exports.async = true;
|
|
367
|
+
|
|
368
|
+
module.exports.optimiseFilterCacheability = optimiseFilterCacheability;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const debug = require('@tryghost/debug')('services:routing:renderer:renderer');
|
|
3
|
+
const {IncorrectUsageError} = require('@tryghost/errors');
|
|
2
4
|
const setContext = require('./context');
|
|
3
5
|
const templates = require('./templates');
|
|
6
|
+
const tpl = require('@tryghost/tpl');
|
|
7
|
+
const messages = {
|
|
8
|
+
couldNotReadFile: 'Could not read file {file}'
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* @description Helper function to finally render the data.
|
|
@@ -26,5 +32,17 @@ module.exports = function renderer(req, res, data) {
|
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
// Render Call
|
|
29
|
-
res.render(res._template, data)
|
|
35
|
+
res.render(res._template, data, function (err, html) {
|
|
36
|
+
if (err) {
|
|
37
|
+
if (err.code === 'ENOENT') {
|
|
38
|
+
return req.next(
|
|
39
|
+
new IncorrectUsageError({
|
|
40
|
+
message: tpl(messages.couldNotReadFile, {file: path.basename(err.path)})
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return req.next(err);
|
|
45
|
+
}
|
|
46
|
+
res.send(html);
|
|
47
|
+
});
|
|
30
48
|
};
|
|
@@ -14,9 +14,9 @@ const get = require('lodash/get');
|
|
|
14
14
|
class I18n {
|
|
15
15
|
/**
|
|
16
16
|
* @param {object} [options]
|
|
17
|
-
* @param {string} basePath - the base path to the translations directory
|
|
18
|
-
* @param {string} [locale] - a locale string
|
|
19
|
-
* @param {
|
|
17
|
+
* @param {string} options.basePath - the base path to the translations directory
|
|
18
|
+
* @param {string} [options.locale] - a locale string
|
|
19
|
+
* @param {string} [options.stringMode] - which mode our translation keys use
|
|
20
20
|
*/
|
|
21
21
|
constructor(options = {}) {
|
|
22
22
|
this._basePath = options.basePath || __dirname;
|
|
@@ -100,7 +100,7 @@ class I18n {
|
|
|
100
100
|
/**
|
|
101
101
|
* Attempt to load strings from a file
|
|
102
102
|
*
|
|
103
|
-
* @param {
|
|
103
|
+
* @param {string} [locale]
|
|
104
104
|
* @returns {object} strings
|
|
105
105
|
*/
|
|
106
106
|
_loadStrings(locale) {
|
|
@@ -110,7 +110,7 @@ class I18n {
|
|
|
110
110
|
return this._readTranslationsFile(locale);
|
|
111
111
|
} catch (err) {
|
|
112
112
|
if (err.code === 'ENOENT') {
|
|
113
|
-
this._handleMissingFileError(locale
|
|
113
|
+
this._handleMissingFileError(locale);
|
|
114
114
|
|
|
115
115
|
if (locale !== this.defaultLocale()) {
|
|
116
116
|
this._handleFallbackToDefault();
|
|
@@ -207,7 +207,7 @@ class I18n {
|
|
|
207
207
|
*/
|
|
208
208
|
_readTranslationsFile(locale) {
|
|
209
209
|
const filePath = path.join(...this._translationFileDirs(), this._translationFileName(locale));
|
|
210
|
-
const content = fs.readFileSync(filePath);
|
|
210
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
211
211
|
return JSON.parse(content);
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -276,6 +276,7 @@ class I18n {
|
|
|
276
276
|
_handleMissingFileError(locale) {
|
|
277
277
|
logging.warn(`i18n was unable to find ${locale}.json.`);
|
|
278
278
|
}
|
|
279
|
+
|
|
279
280
|
_handleInvalidFileError(locale, err) {
|
|
280
281
|
logging.error(new errors.IncorrectUsageError({
|
|
281
282
|
err,
|
|
@@ -4,9 +4,9 @@ const I18n = require('./I18n');
|
|
|
4
4
|
|
|
5
5
|
class ThemeI18n extends I18n {
|
|
6
6
|
/**
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {string} basePath - the base path for the translation directory (e.g. where themes live)
|
|
9
|
-
* @param {string} [locale] - a locale string
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {string} options.basePath - the base path for the translation directory (e.g. where themes live)
|
|
9
|
+
* @param {string} [options.locale] - a locale string
|
|
10
10
|
*/
|
|
11
11
|
constructor(options = {}) {
|
|
12
12
|
super(options);
|
|
@@ -48,6 +48,7 @@ class ThemeI18n extends I18n {
|
|
|
48
48
|
logging.warn(`Theme translations file locales/${locale}.json not found.`);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
51
52
|
_handleInvalidFileError(locale, err) {
|
|
52
53
|
logging.error(new errors.IncorrectUsageError({
|
|
53
54
|
err,
|
|
@@ -58,7 +58,15 @@ class LocalStorageBase extends StorageBase {
|
|
|
58
58
|
targetFilename = filename;
|
|
59
59
|
await fs.mkdirs(targetDir);
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
try {
|
|
62
|
+
await fs.copy(file.path, targetFilename);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err.code === 'ENAMETOOLONG') {
|
|
65
|
+
throw new errors.BadRequestError({err});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
62
70
|
|
|
63
71
|
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
|
64
72
|
// For local file system storage can use relative path so add a slash
|
|
@@ -149,6 +157,10 @@ class LocalStorageBase extends StorageBase {
|
|
|
149
157
|
return next(new errors.NoPermissionError({err: err}));
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
if (err.name === 'RangeNotSatisfiableError') {
|
|
161
|
+
return next(new errors.RangeNotSatisfiableError({err}));
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
return next(new errors.InternalServerError({err: err}));
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/* eslint-disable ghost/ghost-custom/max-api-complexity */
|
|
2
|
-
const
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const errors = require('@tryghost/errors');
|
|
3
4
|
const imageTransform = require('@tryghost/image-transform');
|
|
5
|
+
|
|
6
|
+
const storage = require('../../adapters/storage');
|
|
4
7
|
const config = require('../../../shared/config');
|
|
5
|
-
const path = require('path');
|
|
6
8
|
|
|
7
9
|
module.exports = {
|
|
8
10
|
docName: 'images',
|
|
@@ -33,7 +35,16 @@ module.exports = {
|
|
|
33
35
|
width: config.get('imageOptimization:defaultMaxWidth')
|
|
34
36
|
}, imageOptimizationOptions);
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
try {
|
|
39
|
+
await imageTransform.resizeFromPath(options);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// If the image processing fails, we don't want to store the image because it's corrupted/invalid
|
|
42
|
+
throw new errors.BadRequestError({
|
|
43
|
+
message: 'Image processing failed',
|
|
44
|
+
context: err.message,
|
|
45
|
+
help: 'Please verify that the image is valid'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
37
48
|
|
|
38
49
|
// Store the processed/optimized image
|
|
39
50
|
const processedImageUrl = await store.save({
|
|
@@ -1,10 +1,26 @@
|
|
|
1
|
+
const {BadRequestError} = require('@tryghost/errors');
|
|
1
2
|
const localUtils = require('../../index');
|
|
3
|
+
const nql = require('@tryghost/nql-lang');
|
|
4
|
+
const tpl = require('@tryghost/tpl');
|
|
5
|
+
|
|
6
|
+
const messages = {
|
|
7
|
+
invalidNQLFilter: 'The NQL filter you passed was invalid.'
|
|
8
|
+
};
|
|
2
9
|
|
|
3
10
|
const forceActiveFilter = (frame) => {
|
|
4
11
|
if (frame.options.filter) {
|
|
5
|
-
frame.options.filter =
|
|
12
|
+
frame.options.filter = {
|
|
13
|
+
$and: [
|
|
14
|
+
{
|
|
15
|
+
active: true
|
|
16
|
+
},
|
|
17
|
+
frame.options.filter
|
|
18
|
+
]
|
|
19
|
+
};
|
|
6
20
|
} else {
|
|
7
|
-
frame.options.filter =
|
|
21
|
+
frame.options.filter = {
|
|
22
|
+
active: true
|
|
23
|
+
};
|
|
8
24
|
}
|
|
9
25
|
};
|
|
10
26
|
|
|
@@ -41,6 +57,18 @@ function convertTierInput(input) {
|
|
|
41
57
|
|
|
42
58
|
module.exports = {
|
|
43
59
|
all(_apiConfig, frame) {
|
|
60
|
+
if (frame.options.filter) {
|
|
61
|
+
try {
|
|
62
|
+
frame.options.filter = nql.parse(frame.options.filter);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
throw new BadRequestError({
|
|
65
|
+
message: tpl(messages.invalidNQLFilter)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
frame.options.filter = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
44
72
|
if (localUtils.isContentAPI(frame)) {
|
|
45
73
|
// CASE: content api can only have active tiers
|
|
46
74
|
forceActiveFilter(frame);
|
|
@@ -17,6 +17,7 @@ const messages = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
let JWT_OPTIONS_DEFAULTS = {
|
|
20
|
+
/** @type import('jsonwebtoken').Algorithm[] */
|
|
20
21
|
algorithms: ['HS256'],
|
|
21
22
|
maxAge: '5m'
|
|
22
23
|
};
|
|
@@ -60,7 +61,7 @@ const authenticate = function apiKeyAdminAuth(req, res, next) {
|
|
|
60
61
|
}));
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
return
|
|
64
|
+
return wrappedAuthenticateWithToken(req, res, next, {token});
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
@@ -72,9 +73,20 @@ const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
|
72
73
|
}));
|
|
73
74
|
}
|
|
74
75
|
// CASE: Scheduler publish URLs can have long maxAge but controllerd by expiry and neverBefore
|
|
75
|
-
return
|
|
76
|
+
return wrappedAuthenticateWithToken(req, res, next, {token, ignoreMaxAge: true});
|
|
76
77
|
};
|
|
77
78
|
|
|
79
|
+
async function wrappedAuthenticateWithToken(req, res, next, options) {
|
|
80
|
+
try {
|
|
81
|
+
const {apiKey, user} = await authenticateWithToken(req.originalUrl, options.token, options.ignoreMaxAge);
|
|
82
|
+
req.api_key = apiKey;
|
|
83
|
+
req.user = user;
|
|
84
|
+
next();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
next(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
/**
|
|
79
91
|
* Admin API key authentication flow:
|
|
80
92
|
* 1. extract the JWT token from the `Authorization: Ghost xxxx` header or from URL(for schedules)
|
|
@@ -89,109 +101,104 @@ const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
|
|
|
89
101
|
* - the "Audience" claim should match the requested API path
|
|
90
102
|
* https://tools.ietf.org/html/rfc7519#section-4.1.3
|
|
91
103
|
*/
|
|
92
|
-
const authenticateWithToken = async function apiKeyAuthenticateWithToken(
|
|
104
|
+
const authenticateWithToken = async function apiKeyAuthenticateWithToken(originalUrl, token, ignoreMaxAge) {
|
|
93
105
|
const decoded = jwt.decode(token, {complete: true});
|
|
106
|
+
const jwtValidationOptions = ignoreMaxAge ? _.omit(JWT_OPTIONS_DEFAULTS, 'maxAge') : JWT_OPTIONS_DEFAULTS;
|
|
94
107
|
|
|
95
108
|
if (!decoded || !decoded.header) {
|
|
96
|
-
|
|
109
|
+
throw new errors.BadRequestError({
|
|
97
110
|
message: tpl(messages.invalidToken),
|
|
98
111
|
code: 'INVALID_JWT'
|
|
99
|
-
})
|
|
112
|
+
});
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
const apiKeyId = decoded.header.kid;
|
|
103
116
|
|
|
104
117
|
if (!apiKeyId) {
|
|
105
|
-
|
|
118
|
+
throw new errors.BadRequestError({
|
|
106
119
|
message: tpl(messages.adminApiKidMissing),
|
|
107
120
|
code: 'MISSING_ADMIN_API_KID'
|
|
108
|
-
})
|
|
121
|
+
});
|
|
109
122
|
}
|
|
110
123
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (limitService.isLimited('customIntegrations')
|
|
124
|
+
const apiKey = await models.ApiKey.findOne({id: apiKeyId}, {withRelated: ['integration']});
|
|
125
|
+
|
|
126
|
+
if (!apiKey) {
|
|
127
|
+
throw new errors.UnauthorizedError({
|
|
128
|
+
message: tpl(messages.unknownAdminApiKey),
|
|
129
|
+
code: 'UNKNOWN_ADMIN_API_KEY'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (apiKey.get('type') !== 'admin') {
|
|
134
|
+
throw new errors.UnauthorizedError({
|
|
135
|
+
message: tpl(messages.invalidApiKeyType),
|
|
136
|
+
code: 'INVALID_API_KEY_TYPE'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
|
|
141
|
+
if (limitService.isLimited('customIntegrations')
|
|
130
142
|
&& (apiKey.relations.integration && !['internal', 'core'].includes(apiKey.relations.integration.get('type')))) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Decoding from hex and transforming into bytes is here to
|
|
137
|
-
// keep comparison of the bytes that are stored in the secret.
|
|
138
|
-
// Useful context:
|
|
139
|
-
// https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
|
|
140
|
-
const secret = Buffer.from(apiKey.get('secret'), 'hex');
|
|
141
|
-
|
|
142
|
-
// Using req.originalUrl means we get the right url even if version-rewrites have happened
|
|
143
|
-
const {version, api} = legacyApiPathMatch(req.originalUrl);
|
|
144
|
-
|
|
145
|
-
// ensure the token was meant for this api
|
|
146
|
-
let options;
|
|
147
|
-
|
|
148
|
-
if (version) {
|
|
149
|
-
// CASE: legacy versioned api request
|
|
150
|
-
options = Object.assign({
|
|
151
|
-
audience: new RegExp(`/?${version}/${api}/?$`)
|
|
152
|
-
}, JWT_OPTIONS);
|
|
153
|
-
} else {
|
|
154
|
-
options = Object.assign({
|
|
155
|
-
audience: new RegExp(`/?${api}/?$`)
|
|
156
|
-
}, JWT_OPTIONS);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
jwt.verify(token, secret, options);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
return next(new errors.UnauthorizedError({
|
|
163
|
-
message: tpl(messages.invalidTokenWithMessage, {message: err.message}),
|
|
164
|
-
code: 'INVALID_JWT',
|
|
165
|
-
err
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// authenticated OK
|
|
170
|
-
|
|
171
|
-
if (apiKey.get('user_id')) {
|
|
172
|
-
// fetch the user and store it on the request for later checks and logging
|
|
173
|
-
const user = await models.User.findOne(
|
|
174
|
-
{id: apiKey.get('user_id'), status: 'active'},
|
|
175
|
-
{require: true}
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
req.user = user;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// store the api key on the request for later checks and logging
|
|
182
|
-
req.api_key = apiKey;
|
|
143
|
+
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
|
|
144
|
+
// a concept of measuring if the limit has been surpassed
|
|
145
|
+
await limitService.errorIfWouldGoOverLimit('customIntegrations');
|
|
146
|
+
}
|
|
183
147
|
|
|
184
|
-
|
|
148
|
+
// Decoding from hex and transforming into bytes is here to
|
|
149
|
+
// keep comparison of the bytes that are stored in the secret.
|
|
150
|
+
// Useful context:
|
|
151
|
+
// https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
|
|
152
|
+
const secret = Buffer.from(apiKey.get('secret'), 'hex');
|
|
153
|
+
|
|
154
|
+
// Using req.originalUrl means we get the right url even if version-rewrites have happened
|
|
155
|
+
const {version, api} = legacyApiPathMatch(originalUrl);
|
|
156
|
+
|
|
157
|
+
// ensure the token was meant for this api
|
|
158
|
+
let options;
|
|
159
|
+
|
|
160
|
+
if (version) {
|
|
161
|
+
// CASE: legacy versioned api request
|
|
162
|
+
options = Object.assign({
|
|
163
|
+
audience: new RegExp(`/?${version}/${api}/?$`)
|
|
164
|
+
}, jwtValidationOptions);
|
|
165
|
+
} else {
|
|
166
|
+
options = Object.assign({
|
|
167
|
+
audience: new RegExp(`/?${api}/?$`)
|
|
168
|
+
}, jwtValidationOptions);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
jwt.verify(token, secret, options);
|
|
185
173
|
} catch (err) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
174
|
+
throw new errors.UnauthorizedError({
|
|
175
|
+
message: tpl(messages.invalidTokenWithMessage, {message: err.message}),
|
|
176
|
+
code: 'INVALID_JWT',
|
|
177
|
+
err
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// authenticated OK
|
|
182
|
+
let result = {
|
|
183
|
+
user: null,
|
|
184
|
+
apiKey: apiKey
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (apiKey.get('user_id')) {
|
|
188
|
+
// fetch the user and store it on the request for later checks and logging
|
|
189
|
+
const user = await models.User.findOne(
|
|
190
|
+
{id: apiKey.get('user_id'), status: 'active'},
|
|
191
|
+
{require: true}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
result.user = user;
|
|
191
195
|
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
192
198
|
};
|
|
193
199
|
|
|
194
200
|
module.exports = {
|
|
195
201
|
authenticate,
|
|
196
|
-
authenticateWithUrl
|
|
202
|
+
authenticateWithUrl,
|
|
203
|
+
authenticateWithToken
|
|
197
204
|
};
|
|
@@ -12,10 +12,11 @@ class SlackNotificationsServiceWrapper {
|
|
|
12
12
|
* @param {string} deps.siteUrl
|
|
13
13
|
* @param {boolean} deps.isEnabled
|
|
14
14
|
* @param {URL} deps.webhookUrl
|
|
15
|
+
* @param {number} deps.minThreshold
|
|
15
16
|
*
|
|
16
17
|
* @returns {import('@tryghost/slack-notifications/lib/SlackNotificationsService')}
|
|
17
18
|
*/
|
|
18
|
-
static create({siteUrl, isEnabled, webhookUrl}) {
|
|
19
|
+
static create({siteUrl, isEnabled, webhookUrl, minThreshold}) {
|
|
19
20
|
const {
|
|
20
21
|
SlackNotificationsService,
|
|
21
22
|
SlackNotifications
|
|
@@ -32,7 +33,8 @@ class SlackNotificationsServiceWrapper {
|
|
|
32
33
|
logging,
|
|
33
34
|
config: {
|
|
34
35
|
isEnabled,
|
|
35
|
-
webhookUrl
|
|
36
|
+
webhookUrl,
|
|
37
|
+
minThreshold
|
|
36
38
|
},
|
|
37
39
|
slackNotifications
|
|
38
40
|
});
|
|
@@ -49,8 +51,9 @@ class SlackNotificationsServiceWrapper {
|
|
|
49
51
|
const siteUrl = urlUtils.getSiteUrl();
|
|
50
52
|
const isEnabled = !!(hostSettings?.milestones?.enabled && hostSettings?.milestones?.url);
|
|
51
53
|
const webhookUrl = hostSettings?.milestones?.url;
|
|
54
|
+
const minThreshold = hostSettings?.milestones?.minThreshold ? parseInt(hostSettings.milestones.minThreshold) : 0;
|
|
52
55
|
|
|
53
|
-
this.#api = SlackNotificationsServiceWrapper.create({siteUrl, isEnabled, webhookUrl});
|
|
56
|
+
this.#api = SlackNotificationsServiceWrapper.create({siteUrl, isEnabled, webhookUrl, minThreshold});
|
|
54
57
|
|
|
55
58
|
this.#api.subscribeEvents();
|
|
56
59
|
}
|
|
@@ -3,7 +3,7 @@ const os = require('os');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const security = require('@tryghost/security');
|
|
5
5
|
const request = require('@tryghost/request');
|
|
6
|
-
const errors = require('@tryghost/errors
|
|
6
|
+
const errors = require('@tryghost/errors');
|
|
7
7
|
const limitService = require('../../services/limits');
|
|
8
8
|
const {setFromZip} = require('./storage');
|
|
9
9
|
|
|
@@ -86,7 +86,8 @@ module.exports = class TierRepository {
|
|
|
86
86
|
* @returns {Promise<import('@tryghost/tiers/lib/Tier')[]>}
|
|
87
87
|
*/
|
|
88
88
|
async getAll(options = {}) {
|
|
89
|
-
const filter = nql(
|
|
89
|
+
const filter = nql();
|
|
90
|
+
filter.filter = options.filter || {};
|
|
90
91
|
return Promise.all(this.#store.slice().filter((item) => {
|
|
91
92
|
return filter.queryJSON(this.toPrimitive(item));
|
|
92
93
|
}).map((tier) => {
|