ghost 5.33.8 → 5.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/tryghost-adapter-cache-redis-5.34.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.34.1.tgz +0 -0
- package/components/{tryghost-api-framework-5.33.8.tgz → tryghost-api-framework-5.34.1.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.34.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.34.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.34.1.tgz +0 -0
- package/components/tryghost-constants-5.34.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.33.8.tgz → tryghost-custom-theme-settings-service-5.34.1.tgz} +0 -0
- package/components/tryghost-data-generator-5.34.1.tgz +0 -0
- package/components/tryghost-domain-events-5.34.1.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.34.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.34.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.34.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.34.1.tgz +0 -0
- package/components/tryghost-email-events-5.34.1.tgz +0 -0
- package/components/tryghost-email-service-5.34.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.34.1.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.33.8.tgz → tryghost-express-dynamic-redirects-5.34.1.tgz} +0 -0
- package/components/tryghost-extract-api-key-5.34.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.34.1.tgz +0 -0
- package/components/tryghost-i18n-5.34.1.tgz +0 -0
- package/components/{tryghost-importer-revue-5.33.8.tgz → tryghost-importer-revue-5.34.1.tgz} +0 -0
- package/components/{tryghost-job-manager-5.33.8.tgz → tryghost-job-manager-5.34.1.tgz} +0 -0
- package/components/tryghost-link-redirects-5.34.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.34.1.tgz +0 -0
- package/components/tryghost-link-tracking-5.34.1.tgz +0 -0
- package/components/{tryghost-magic-link-5.33.8.tgz → tryghost-magic-link-5.34.1.tgz} +0 -0
- package/components/tryghost-mailgun-client-5.34.1.tgz +0 -0
- package/components/{tryghost-member-attribution-5.33.8.tgz → tryghost-member-attribution-5.34.1.tgz} +0 -0
- package/components/tryghost-member-events-5.34.1.tgz +0 -0
- package/components/tryghost-members-api-5.34.1.tgz +0 -0
- package/components/tryghost-members-csv-5.34.1.tgz +0 -0
- package/components/{tryghost-members-events-service-5.33.8.tgz → tryghost-members-events-service-5.34.1.tgz} +0 -0
- package/components/{tryghost-members-importer-5.33.8.tgz → tryghost-members-importer-5.34.1.tgz} +0 -0
- package/components/tryghost-members-offers-5.34.1.tgz +0 -0
- package/components/tryghost-members-payments-5.34.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.34.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.34.1.tgz +0 -0
- package/components/tryghost-milestone-emails-5.34.1.tgz +0 -0
- package/components/tryghost-minifier-5.34.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.34.1.tgz +0 -0
- package/components/{tryghost-mw-cache-control-5.33.8.tgz → tryghost-mw-cache-control-5.34.1.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.33.8.tgz → tryghost-mw-error-handler-5.34.1.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.34.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.34.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.34.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.34.1.tgz +0 -0
- package/components/{tryghost-package-json-5.33.8.tgz → tryghost-package-json-5.34.1.tgz} +0 -0
- package/components/tryghost-referrers-5.34.1.tgz +0 -0
- package/components/tryghost-security-5.34.1.tgz +0 -0
- package/components/tryghost-session-service-5.34.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.34.1.tgz +0 -0
- package/components/tryghost-staff-service-5.34.1.tgz +0 -0
- package/components/tryghost-stats-service-5.34.1.tgz +0 -0
- package/components/tryghost-tags-public-5.34.1.tgz +0 -0
- package/components/tryghost-tiers-5.34.1.tgz +0 -0
- package/components/{tryghost-update-check-service-5.33.8.tgz → tryghost-update-check-service-5.34.1.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.34.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.34.1.tgz +0 -0
- package/components/tryghost-webmentions-5.34.1.tgz +0 -0
- package/core/boot.js +6 -1
- package/core/built/admin/assets/{chunk.143.caa7dbd727b76021c31b.js → chunk.143.07f5af56ff872bb0e9e4.js} +14 -14
- package/core/built/admin/assets/{chunk.178.9803e6194593a0ed6595.js → chunk.178.2e831ef9072743e38dd1.js} +4 -4
- package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js → chunk.616.181e1ad6c33f0bec7a65.js} +3519 -3512
- package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js.LICENSE.txt → chunk.616.181e1ad6c33f0bec7a65.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/{chunk.79.c3c2c05ea7ff7707fcad.js → chunk.79.ec143a398298020c87e6.js} +112 -112
- package/core/built/admin/assets/codemirror/{codemirror-a81c0653d8e57286b75c5a1792f80779.js → codemirror-6c43f4894cbd8db73d7f35cde836c58e.js} +810 -809
- package/core/built/admin/assets/{ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css → ghost-558c1e319d6e025bfab2054bc0f7fe83.css} +1 -1
- package/core/built/admin/assets/{ghost-872d240189f9b5ff85f4f7cb2ac3ab4c.js → ghost-ad40d109653288e74a7cd922341fb33d.js} +114 -89
- package/core/built/admin/assets/{ghost-dark-ac0cb221eddc8652a0e7c263ed6513dc.css → ghost-dark-a15754df1f9070dc2525482ce22e2251.css} +1 -1
- package/core/built/admin/assets/simplemde/{simplemde-2885e4a40ed66fcae974595584efe50b.js → simplemde-28049a9bd7f432b0648747eb26958a33.js} +836 -836
- package/core/built/admin/assets/{vendor-0441964c34d58f2aacd5a04bbe240243.js → vendor-253d6527ca6353855164ef65f896f762.js} +1208 -1233
- package/core/built/admin/index.html +6 -6
- package/core/cli/generate-data.js +21 -3
- package/core/frontend/services/routing/router-manager.js +1 -1
- package/core/frontend/web/middleware/handle-image-sizes.js +6 -21
- package/core/server/adapters/cache/Redis.js +3 -0
- package/core/server/adapters/storage/LocalStorageBase.js +11 -1
- package/core/server/api/endpoints/images.js +47 -6
- package/core/server/api/endpoints/mentions.js +1 -1
- package/core/server/api/endpoints/tags-public.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/mappers/mentions.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +1 -0
- package/core/server/data/migrations/versions/5.34/2023-01-30-07-27-add-mentions-permission.js +10 -0
- package/core/server/data/migrations/versions/5.34/2023-02-08-03-08-add-mentions-notifications-column.js +7 -0
- package/core/server/data/migrations/versions/5.34/2023-02-08-22-32-add-mentions-delete-column.js +7 -0
- package/core/server/data/schema/fixtures/fixtures.json +9 -2
- package/core/server/data/schema/schema.js +3 -1
- package/core/server/lib/image/image-size.js +23 -5
- package/core/server/lib/lexical.js +36 -3
- package/core/server/models/member.js +3 -0
- package/core/server/models/mention.js +7 -1
- package/core/server/models/post.js +1 -1
- package/core/server/models/user.js +4 -1
- package/core/server/services/adapter-manager/index.js +7 -0
- package/core/server/services/email-analytics/wrapper.js +22 -13
- package/core/server/services/email-service/wrapper.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +1 -1
- package/core/server/services/members/jobs/index.js +4 -2
- package/core/server/services/mentions/BookshelfMentionRepository.js +3 -2
- package/core/server/services/mentions/MentionController.js +78 -12
- package/core/server/services/mentions/service.js +43 -3
- package/core/server/services/milestone-emails/MilestoneQueries.js +58 -0
- package/core/server/services/milestone-emails/index.js +1 -0
- package/core/server/services/milestone-emails/service.js +58 -0
- package/core/server/services/tags-public/index.js +1 -0
- package/core/server/services/tags-public/service.js +31 -0
- package/core/server/web/api/endpoints/admin/routes.js +0 -1
- package/core/server/web/api/middleware/index.js +0 -1
- package/core/server/web/shared/middleware/api/spam-prevention.js +31 -1
- package/core/server/web/shared/middleware/brute.js +13 -0
- package/core/server/web/webmentions/routes.js +3 -0
- package/core/shared/config/defaults.json +17 -1
- package/core/shared/config/env/config.development.json +0 -3
- package/core/shared/config/env/config.testing-browser.json +6 -0
- package/core/shared/config/env/config.testing-mysql.json +7 -0
- package/core/shared/config/env/config.testing.json +6 -0
- package/core/shared/labs.js +3 -3
- package/package.json +121 -114
- package/yarn.lock +327 -242
- package/components/tryghost-adapter-manager-5.33.8.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.33.8.tgz +0 -0
- package/components/tryghost-audience-feedback-5.33.8.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.33.8.tgz +0 -0
- package/components/tryghost-constants-5.33.8.tgz +0 -0
- package/components/tryghost-data-generator-5.33.8.tgz +0 -0
- package/components/tryghost-domain-events-5.33.8.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.33.8.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.33.8.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.33.8.tgz +0 -0
- package/components/tryghost-email-content-generator-5.33.8.tgz +0 -0
- package/components/tryghost-email-events-5.33.8.tgz +0 -0
- package/components/tryghost-email-service-5.33.8.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.33.8.tgz +0 -0
- package/components/tryghost-extract-api-key-5.33.8.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.33.8.tgz +0 -0
- package/components/tryghost-i18n-5.33.8.tgz +0 -0
- package/components/tryghost-link-redirects-5.33.8.tgz +0 -0
- package/components/tryghost-link-replacer-5.33.8.tgz +0 -0
- package/components/tryghost-link-tracking-5.33.8.tgz +0 -0
- package/components/tryghost-mailgun-client-5.33.8.tgz +0 -0
- package/components/tryghost-member-events-5.33.8.tgz +0 -0
- package/components/tryghost-members-api-5.33.8.tgz +0 -0
- package/components/tryghost-members-csv-5.33.8.tgz +0 -0
- package/components/tryghost-members-offers-5.33.8.tgz +0 -0
- package/components/tryghost-members-payments-5.33.8.tgz +0 -0
- package/components/tryghost-members-ssr-5.33.8.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.33.8.tgz +0 -0
- package/components/tryghost-minifier-5.33.8.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.33.8.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.33.8.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.33.8.tgz +0 -0
- package/components/tryghost-mw-vhost-5.33.8.tgz +0 -0
- package/components/tryghost-oembed-service-5.33.8.tgz +0 -0
- package/components/tryghost-referrers-5.33.8.tgz +0 -0
- package/components/tryghost-security-5.33.8.tgz +0 -0
- package/components/tryghost-session-service-5.33.8.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.33.8.tgz +0 -0
- package/components/tryghost-staff-service-5.33.8.tgz +0 -0
- package/components/tryghost-stats-service-5.33.8.tgz +0 -0
- package/components/tryghost-tiers-5.33.8.tgz +0 -0
- package/components/tryghost-verification-trigger-5.33.8.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.33.8.tgz +0 -0
- package/components/tryghost-webmentions-5.33.8.tgz +0 -0
- package/core/server/web/api/middleware/normalize-image.js +0 -42
|
@@ -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%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%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.34%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%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-3e6947aa681f0fb82b193090e520dc73.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-558c1e319d6e025bfab2054bc0f7fe83.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
|
|
57
57
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
58
58
|
|
|
59
|
-
<script src="assets/vendor-
|
|
60
|
-
<script src="assets/chunk.
|
|
61
|
-
<script src="assets/chunk.143.
|
|
62
|
-
<script src="assets/ghost-
|
|
59
|
+
<script src="assets/vendor-253d6527ca6353855164ef65f896f762.js"></script>
|
|
60
|
+
<script src="assets/chunk.616.181e1ad6c33f0bec7a65.js"></script>
|
|
61
|
+
<script src="assets/chunk.143.07f5af56ff872bb0e9e4.js"></script>
|
|
62
|
+
<script src="assets/ghost-ad40d109653288e74a7cd922341fb33d.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -2,10 +2,13 @@ const Command = require('./command');
|
|
|
2
2
|
const DataGenerator = require('@tryghost/data-generator');
|
|
3
3
|
const config = require('../shared/config');
|
|
4
4
|
|
|
5
|
-
module.exports = class
|
|
5
|
+
module.exports = class DataGeneratorCommand extends Command {
|
|
6
6
|
setup() {
|
|
7
7
|
this.help('Generates random data to populate the database for development & testing');
|
|
8
8
|
this.argument('--base-data-pack', {type: 'string', defaultValue: '', desc: 'Base data pack file location, imported instead of random content'});
|
|
9
|
+
this.argument('--scale', {type: 'string', defaultValue: 'small', desc: 'Scale of the data to generate. `small` for a quick run, `large` for more content'});
|
|
10
|
+
this.argument('--single-table', {type: 'string', desc: 'Import a single table'});
|
|
11
|
+
this.argument('--quantity', {type: 'number', desc: 'When importing a single table, the quantity to import'});
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
initializeContext(context) {
|
|
@@ -27,6 +30,17 @@ module.exports = class REPL extends Command {
|
|
|
27
30
|
async handle(argv = {}) {
|
|
28
31
|
const knex = require('../server/data/db/connection');
|
|
29
32
|
const {tables: schema} = require('../server/data/schema/index');
|
|
33
|
+
|
|
34
|
+
const modelQuantities = {};
|
|
35
|
+
if (argv.scale) {
|
|
36
|
+
if (argv.scale === 'small') {
|
|
37
|
+
modelQuantities.members = 200;
|
|
38
|
+
modelQuantities.membersLoginEvents = 1;
|
|
39
|
+
modelQuantities.posts = 10;
|
|
40
|
+
}
|
|
41
|
+
// Defaults in data-generator package make a large set
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
const dataGenerator = new DataGenerator({
|
|
31
45
|
baseDataPack: argv['base-data-pack'],
|
|
32
46
|
knex,
|
|
@@ -40,11 +54,15 @@ module.exports = class REPL extends Command {
|
|
|
40
54
|
fatal: this.fatal,
|
|
41
55
|
debug: this.debug
|
|
42
56
|
},
|
|
43
|
-
modelQuantities
|
|
57
|
+
modelQuantities,
|
|
44
58
|
baseUrl: config.getSiteUrl()
|
|
45
59
|
});
|
|
46
60
|
try {
|
|
47
|
-
|
|
61
|
+
if (argv['single-table']) {
|
|
62
|
+
await dataGenerator.importSingleTable(argv['single-table'], argv.quantity ?? undefined);
|
|
63
|
+
} else {
|
|
64
|
+
await dataGenerator.importData();
|
|
65
|
+
}
|
|
48
66
|
} catch (error) {
|
|
49
67
|
this.fatal('Failed while generating data: ', error);
|
|
50
68
|
}
|
|
@@ -177,7 +177,7 @@ class RouterManager {
|
|
|
177
177
|
|
|
178
178
|
// NOTE: timezone change only affects the collection router with dated permalinks
|
|
179
179
|
const collectionRouter = this.registry.getRouterByName('CollectionRouter');
|
|
180
|
-
if (collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
|
|
180
|
+
if (collectionRouter && collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
|
|
181
181
|
debug('handleTimezoneEdit: trigger regeneration');
|
|
182
182
|
|
|
183
183
|
this.urlService.onRouterUpdated(collectionRouter.identifier);
|
|
@@ -5,6 +5,7 @@ const imageTransform = require('@tryghost/image-transform');
|
|
|
5
5
|
const storage = require('../../../server/adapters/storage');
|
|
6
6
|
const activeTheme = require('../../services/theme-engine/active');
|
|
7
7
|
const config = require('../../../shared/config');
|
|
8
|
+
const {imageSize} = require('../../../server/lib/image');
|
|
8
9
|
|
|
9
10
|
const SIZE_PATH_REGEX = /^\/size\/([^/]+)\//;
|
|
10
11
|
const FORMAT_PATH_REGEX = /^\/format\/([^./]+)\//;
|
|
@@ -21,7 +22,7 @@ module.exports = function (req, res, next) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const requestedDimension = req.url.match(SIZE_PATH_REGEX)[1];
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
// Note that we don't use sizeImageDir because we need to keep the trailing slash
|
|
26
27
|
let imagePath = req.url.replace(`/size/${requestedDimension}`, '');
|
|
27
28
|
|
|
@@ -39,7 +40,7 @@ module.exports = function (req, res, next) {
|
|
|
39
40
|
// We need to keep the first slash here
|
|
40
41
|
let url = req.originalUrl
|
|
41
42
|
.replace(`/size/${requestedDimension}`, '');
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
if (format) {
|
|
44
45
|
url = url.replace(`/format/${format}`, '');
|
|
45
46
|
}
|
|
@@ -81,7 +82,7 @@ module.exports = function (req, res, next) {
|
|
|
81
82
|
return redirectToOriginal();
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
if (format) {
|
|
85
|
+
if (format) {
|
|
85
86
|
// CASE: When formatting, we need to check if the imageTransform package supports this specific format
|
|
86
87
|
if (!imageTransform.canTransformToFormat(format)) {
|
|
87
88
|
// transform not supported
|
|
@@ -89,7 +90,7 @@ module.exports = function (req, res, next) {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
// CASE: when transforming is supported, we need to check if it is desired
|
|
93
|
+
// CASE: when transforming is supported, we need to check if it is desired
|
|
93
94
|
// (e.g. it is not desired to resize SVGs when not formatting them to a different type)
|
|
94
95
|
if (!format && !imageTransform.shouldResizeFileExtension(requestUrlFileExtension)) {
|
|
95
96
|
return redirectToOriginal();
|
|
@@ -111,23 +112,7 @@ module.exports = function (req, res, next) {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
|
|
116
|
-
|
|
117
|
-
if (!imageNameMatched) {
|
|
118
|
-
// CASE: Image name does not contain any characters?
|
|
119
|
-
// RESULT: Hand off to `next()` which will 404
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const unoptimizedImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
|
|
123
|
-
|
|
124
|
-
return storageInstance.exists(unoptimizedImagePath)
|
|
125
|
-
.then((unoptimizedImageExists) => {
|
|
126
|
-
if (unoptimizedImageExists) {
|
|
127
|
-
return unoptimizedImagePath;
|
|
128
|
-
}
|
|
129
|
-
return imagePath;
|
|
130
|
-
})
|
|
115
|
+
return imageSize.getOriginalImagePath(imagePath)
|
|
131
116
|
.then((storagePath) => {
|
|
132
117
|
return storageInstance.read({path: storagePath});
|
|
133
118
|
})
|
|
@@ -83,9 +83,19 @@ class LocalStorageBase extends StorageBase {
|
|
|
83
83
|
urlToPath(url) {
|
|
84
84
|
let filePath;
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
const prefix = urlUtils.urlJoin('/',
|
|
87
|
+
urlUtils.getSubdir(),
|
|
88
|
+
this.staticFileURLPrefix
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (url.startsWith(this.staticFileUrl)) {
|
|
92
|
+
// CASE: full path that includes the site url
|
|
87
93
|
filePath = url.replace(this.staticFileUrl, '');
|
|
88
94
|
filePath = path.join(this.storagePath, filePath);
|
|
95
|
+
} else if (url.startsWith(prefix)) {
|
|
96
|
+
// CASE: The result of the save method doesn't include the site url. So we need to handle this case.
|
|
97
|
+
filePath = url.replace(prefix, '');
|
|
98
|
+
filePath = path.join(this.storagePath, filePath);
|
|
89
99
|
} else {
|
|
90
100
|
throw new errors.IncorrectUsageError({
|
|
91
101
|
message: tpl(messages.invalidUrlParameter, {url})
|
|
@@ -1,19 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable ghost/ghost-custom/max-api-complexity */
|
|
2
2
|
const storage = require('../../adapters/storage');
|
|
3
|
+
const imageTransform = require('@tryghost/image-transform');
|
|
4
|
+
const config = require('../../../shared/config');
|
|
5
|
+
const path = require('path');
|
|
3
6
|
|
|
4
7
|
module.exports = {
|
|
5
8
|
docName: 'images',
|
|
6
9
|
upload: {
|
|
7
10
|
statusCode: 201,
|
|
8
11
|
permissions: false,
|
|
9
|
-
query(frame) {
|
|
12
|
+
async query(frame) {
|
|
10
13
|
const store = storage.getStorage('images');
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
// Normalize
|
|
16
|
+
const imageOptimizationOptions = config.get('imageOptimization');
|
|
17
|
+
|
|
18
|
+
// Trim _o from file name (not allowed suffix)
|
|
19
|
+
frame.file.name = frame.file.name.replace(/_o(\.\w+?)$/, '$1');
|
|
20
|
+
|
|
21
|
+
// CASE: image transform is not capable of transforming file (e.g. .gif)
|
|
22
|
+
if (imageTransform.shouldResizeFileExtension(frame.file.ext) && imageOptimizationOptions.resize) {
|
|
23
|
+
const out = `${frame.file.path}_processed`;
|
|
24
|
+
const originalPath = frame.file.path;
|
|
25
|
+
|
|
26
|
+
const options = Object.assign({
|
|
27
|
+
in: originalPath,
|
|
28
|
+
out,
|
|
29
|
+
ext: frame.file.ext,
|
|
30
|
+
width: config.get('imageOptimization:defaultMaxWidth')
|
|
31
|
+
}, imageOptimizationOptions);
|
|
32
|
+
|
|
33
|
+
await imageTransform.resizeFromPath(options);
|
|
34
|
+
|
|
35
|
+
// Store the processed/optimized image
|
|
36
|
+
const processedImageUrl = await store.save({
|
|
37
|
+
...frame.file,
|
|
38
|
+
path: out
|
|
39
|
+
});
|
|
40
|
+
const processedImagePath = store.urlToPath(processedImageUrl);
|
|
41
|
+
|
|
42
|
+
// Get the path and name of the processed image
|
|
43
|
+
// We want to store the original image on the same name + _o
|
|
44
|
+
// So we need to wait for the first store to finish before generating the name of the original image
|
|
45
|
+
const processedImageName = path.basename(processedImagePath);
|
|
46
|
+
const processedImageDir = path.dirname(processedImagePath);
|
|
47
|
+
|
|
48
|
+
// Store the original image
|
|
49
|
+
await store.save({
|
|
50
|
+
...frame.file,
|
|
51
|
+
path: originalPath,
|
|
52
|
+
name: imageTransform.generateOriginalImageName(processedImageName)
|
|
53
|
+
}, processedImageDir);
|
|
54
|
+
|
|
55
|
+
return processedImageUrl;
|
|
16
56
|
}
|
|
57
|
+
|
|
17
58
|
return store.save(frame.file);
|
|
18
59
|
}
|
|
19
60
|
}
|
|
@@ -2,6 +2,7 @@ const Promise = require('bluebird');
|
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
3
|
const errors = require('@tryghost/errors');
|
|
4
4
|
const models = require('../../models');
|
|
5
|
+
const tagsPublicService = require('../../services/tags-public');
|
|
5
6
|
|
|
6
7
|
const ALLOWED_INCLUDES = ['count.posts'];
|
|
7
8
|
|
|
@@ -31,7 +32,7 @@ module.exports = {
|
|
|
31
32
|
},
|
|
32
33
|
permissions: true,
|
|
33
34
|
query(frame) {
|
|
34
|
-
return
|
|
35
|
+
return tagsPublicService.api.browse(frame.options);
|
|
35
36
|
}
|
|
36
37
|
},
|
|
37
38
|
|
|
@@ -7,7 +7,7 @@ module.exports = (model) => {
|
|
|
7
7
|
target: json.target,
|
|
8
8
|
timestamp: json.timestamp,
|
|
9
9
|
payload: json.payload,
|
|
10
|
-
|
|
10
|
+
resource: json.resource,
|
|
11
11
|
source_title: json.sourceTitle,
|
|
12
12
|
source_site_title: json.sourceSiteTitle,
|
|
13
13
|
source_excerpt: json.sourceExcerpt,
|
|
@@ -35,6 +35,7 @@ const author = (attrs, frame) => {
|
|
|
35
35
|
delete attrs.free_member_signup_notification;
|
|
36
36
|
delete attrs.paid_subscription_started_notification;
|
|
37
37
|
delete attrs.paid_subscription_canceled_notification;
|
|
38
|
+
delete attrs.mention_notifications;
|
|
38
39
|
|
|
39
40
|
// @NOTE: used for night shift
|
|
40
41
|
delete attrs.accessibility;
|
|
@@ -633,6 +633,11 @@
|
|
|
633
633
|
"name": "Edit links",
|
|
634
634
|
"action_type": "edit",
|
|
635
635
|
"object_type": "link"
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"name": "Browse mentions",
|
|
639
|
+
"action_type": "browse",
|
|
640
|
+
"object_type": "mention"
|
|
636
641
|
}
|
|
637
642
|
]
|
|
638
643
|
},
|
|
@@ -763,7 +768,8 @@
|
|
|
763
768
|
"newsletter": "all",
|
|
764
769
|
"explore": "read",
|
|
765
770
|
"comment": "all",
|
|
766
|
-
"link": "all"
|
|
771
|
+
"link": "all",
|
|
772
|
+
"mention": "browse"
|
|
767
773
|
},
|
|
768
774
|
"DB Backup Integration": {
|
|
769
775
|
"db": "all"
|
|
@@ -798,7 +804,8 @@
|
|
|
798
804
|
"newsletter": ["browse", "read", "add", "edit"],
|
|
799
805
|
"explore": "read",
|
|
800
806
|
"comment": "all",
|
|
801
|
-
"link": "all"
|
|
807
|
+
"link": "all",
|
|
808
|
+
"mention": "browse"
|
|
802
809
|
},
|
|
803
810
|
"Editor": {
|
|
804
811
|
"notification": "all",
|
|
@@ -154,6 +154,7 @@ module.exports = {
|
|
|
154
154
|
free_member_signup_notification: {type: 'boolean', nullable: false, defaultTo: true},
|
|
155
155
|
paid_subscription_started_notification: {type: 'boolean', nullable: false, defaultTo: true},
|
|
156
156
|
paid_subscription_canceled_notification: {type: 'boolean', nullable: false, defaultTo: false},
|
|
157
|
+
mention_notifications: {type: 'boolean', nullable: false, defaultTo: true},
|
|
157
158
|
created_at: {type: 'dateTime', nullable: false},
|
|
158
159
|
created_by: {type: 'string', maxlength: 24, nullable: false},
|
|
159
160
|
updated_at: {type: 'dateTime', nullable: true},
|
|
@@ -993,6 +994,7 @@ module.exports = {
|
|
|
993
994
|
resource_id: {type: 'string', maxlength: 24, nullable: true},
|
|
994
995
|
resource_type: {type: 'string', maxlength: 50, nullable: true},
|
|
995
996
|
created_at: {type: 'dateTime', nullable: false},
|
|
996
|
-
payload: {type: 'text', maxlength: 65535, nullable: true}
|
|
997
|
+
payload: {type: 'text', maxlength: 65535, nullable: true},
|
|
998
|
+
deleted: {type: 'boolean', nullable: false, defaultTo: false}
|
|
997
999
|
}
|
|
998
1000
|
};
|
|
@@ -270,18 +270,36 @@ class ImageSize {
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Returns the path of the original image for a given image path (we always store the original image in a separate file, suffixed with _o, while we store a resized version of the image on the original name)
|
|
275
|
+
* TODO: Preferrably we want to move this to a separate image utils package. Currently not really a good place to put it in image lib.
|
|
276
|
+
*/
|
|
277
|
+
async getOriginalImagePath(imagePath) {
|
|
274
278
|
const {dir, name, ext} = path.parse(imagePath);
|
|
279
|
+
const storageInstance = this.storage.getStorage('images');
|
|
280
|
+
|
|
281
|
+
const preferredUnoptimizedImagePath = path.join(dir, `${name}_o${ext}`);
|
|
282
|
+
const preferredUnoptimizedImagePathExists = await storageInstance.exists(preferredUnoptimizedImagePath);
|
|
283
|
+
if (preferredUnoptimizedImagePathExists) {
|
|
284
|
+
return preferredUnoptimizedImagePath;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Legacy format did some magic with the numbers that could cause bugs. We still need to support it for old files.
|
|
288
|
+
// refs https://github.com/TryGhost/Team/issues/481
|
|
275
289
|
const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
|
|
276
290
|
|
|
277
291
|
if (!imageNameMatched) {
|
|
278
|
-
return
|
|
292
|
+
return imagePath;
|
|
279
293
|
}
|
|
280
294
|
|
|
281
|
-
const
|
|
282
|
-
const
|
|
295
|
+
const legacyOriginalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
|
|
296
|
+
const legacyOriginalImageExists = await storageInstance.exists(legacyOriginalImagePath);
|
|
283
297
|
|
|
284
|
-
return
|
|
298
|
+
return legacyOriginalImageExists ? legacyOriginalImagePath : imagePath;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async getOriginalImageSizeFromStoragePath(imagePath) {
|
|
302
|
+
return this.getImageSizeFromStoragePath(await this.getOriginalImagePath(imagePath));
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
_getPathFromUrl(imageUrl) {
|
|
@@ -1,23 +1,56 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const urlUtils = require('../../shared/url-utils');
|
|
3
|
+
const config = require('../../shared/config');
|
|
4
|
+
const storage = require('../adapters/storage');
|
|
2
5
|
|
|
3
6
|
let nodes;
|
|
4
7
|
let lexicalHtmlRenderer;
|
|
5
8
|
let urlTransformMap;
|
|
6
9
|
|
|
10
|
+
function populateNodes() {
|
|
11
|
+
const {DEFAULT_NODES} = require('@tryghost/kg-default-nodes');
|
|
12
|
+
nodes = DEFAULT_NODES;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
module.exports = {
|
|
8
16
|
get lexicalHtmlRenderer() {
|
|
9
17
|
if (!lexicalHtmlRenderer) {
|
|
18
|
+
if (!nodes) {
|
|
19
|
+
populateNodes();
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
const LexicalHtmlRenderer = require('@tryghost/kg-lexical-html-renderer');
|
|
11
|
-
lexicalHtmlRenderer = new LexicalHtmlRenderer();
|
|
23
|
+
lexicalHtmlRenderer = new LexicalHtmlRenderer({nodes});
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
return lexicalHtmlRenderer;
|
|
15
27
|
},
|
|
16
28
|
|
|
29
|
+
render(lexical, userOptions = {}) {
|
|
30
|
+
const options = Object.assign({
|
|
31
|
+
siteUrl: config.get('url'),
|
|
32
|
+
imageOptimization: config.get('imageOptimization'),
|
|
33
|
+
canTransformImage(storagePath) {
|
|
34
|
+
const imageTransform = require('@tryghost/image-transform');
|
|
35
|
+
const {ext} = path.parse(storagePath);
|
|
36
|
+
|
|
37
|
+
// NOTE: the "saveRaw" check is smelly
|
|
38
|
+
return imageTransform.canTransformFiles()
|
|
39
|
+
&& imageTransform.shouldResizeFileExtension(ext)
|
|
40
|
+
&& typeof storage.getStorage('images').saveRaw === 'function';
|
|
41
|
+
},
|
|
42
|
+
createDocument() {
|
|
43
|
+
const {JSDOM} = require('jsdom');
|
|
44
|
+
return (new JSDOM()).window.document;
|
|
45
|
+
}
|
|
46
|
+
}, userOptions);
|
|
47
|
+
|
|
48
|
+
return this.lexicalHtmlRenderer.render(lexical, options);
|
|
49
|
+
},
|
|
50
|
+
|
|
17
51
|
get nodes() {
|
|
18
52
|
if (!nodes) {
|
|
19
|
-
|
|
20
|
-
nodes = DEFAULT_NODES;
|
|
53
|
+
populateNodes();
|
|
21
54
|
}
|
|
22
55
|
|
|
23
56
|
return nodes;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
const ghostBookshelf = require('./base');
|
|
2
2
|
|
|
3
3
|
const Mention = ghostBookshelf.Model.extend({
|
|
4
|
-
tableName: 'mentions'
|
|
4
|
+
tableName: 'mentions',
|
|
5
|
+
defaults: {
|
|
6
|
+
deleted: false
|
|
7
|
+
},
|
|
8
|
+
enforcedFilters() {
|
|
9
|
+
return 'deleted:false';
|
|
10
|
+
}
|
|
5
11
|
});
|
|
6
12
|
|
|
7
13
|
module.exports = {
|
|
@@ -681,7 +681,7 @@ Post = ghostBookshelf.Model.extend({
|
|
|
681
681
|
)
|
|
682
682
|
) {
|
|
683
683
|
try {
|
|
684
|
-
this.set('html', lexicalLib.
|
|
684
|
+
this.set('html', lexicalLib.render(this.get('lexical')));
|
|
685
685
|
} catch (err) {
|
|
686
686
|
throw new errors.ValidationError({
|
|
687
687
|
message: tpl(messages.invalidLexicalStructure),
|
|
@@ -68,7 +68,8 @@ User = ghostBookshelf.Model.extend({
|
|
|
68
68
|
comment_notifications: true,
|
|
69
69
|
free_member_signup_notification: true,
|
|
70
70
|
paid_subscription_started_notification: true,
|
|
71
|
-
paid_subscription_canceled_notification: false
|
|
71
|
+
paid_subscription_canceled_notification: false,
|
|
72
|
+
mention_notifications: true
|
|
72
73
|
};
|
|
73
74
|
},
|
|
74
75
|
|
|
@@ -504,6 +505,8 @@ User = ghostBookshelf.Model.extend({
|
|
|
504
505
|
filter += '+paid_subscription_started_notification:true';
|
|
505
506
|
} else if (type === 'paid-canceled') {
|
|
506
507
|
filter += '+paid_subscription_canceled_notification:true';
|
|
508
|
+
} else if (type === 'mention-received') {
|
|
509
|
+
filter += '+mention_notifications:true';
|
|
507
510
|
}
|
|
508
511
|
const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']});
|
|
509
512
|
return this.findAll(updatedOptions).then((users) => {
|
|
@@ -29,5 +29,12 @@ module.exports = {
|
|
|
29
29
|
const {adapterClassName, adapterConfig} = resolveAdapterOptions(name, adapterServiceConfig);
|
|
30
30
|
|
|
31
31
|
return adapterManager.getAdapter(name, adapterClassName, adapterConfig);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Force recreation of all instances instead of reusing cached instances. Use when editing config file during tests.
|
|
36
|
+
*/
|
|
37
|
+
clearCache() {
|
|
38
|
+
adapterManager.clearInstanceCache();
|
|
32
39
|
}
|
|
33
40
|
};
|
|
@@ -55,6 +55,24 @@ class EmailAnalyticsServiceWrapper {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
async fetchLatest() {
|
|
59
|
+
const fetchStartDate = new Date();
|
|
60
|
+
debug('Starting email analytics fetch of latest events');
|
|
61
|
+
const eventStats = await this.service.fetchLatest();
|
|
62
|
+
const fetchEndDate = new Date();
|
|
63
|
+
debug(`Finished fetching ${eventStats.totalEvents} analytics events in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
64
|
+
|
|
65
|
+
const aggregateStartDate = new Date();
|
|
66
|
+
debug(`Starting email analytics aggregation for ${eventStats.emailIds.length} emails`);
|
|
67
|
+
await this.service.aggregateStats(eventStats);
|
|
68
|
+
const aggregateEndDate = new Date();
|
|
69
|
+
debug(`Finished aggregating email analytics in ${aggregateEndDate.getTime() - aggregateStartDate.getTime()}ms`);
|
|
70
|
+
|
|
71
|
+
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
72
|
+
|
|
73
|
+
return eventStats;
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
async startFetch() {
|
|
59
77
|
if (this.fetching) {
|
|
60
78
|
logging.info('Email analytics fetch already running, skipping');
|
|
@@ -64,24 +82,15 @@ class EmailAnalyticsServiceWrapper {
|
|
|
64
82
|
|
|
65
83
|
logging.info('Email analytics fetch started');
|
|
66
84
|
try {
|
|
67
|
-
const
|
|
68
|
-
debug('Starting email analytics fetch of latest events');
|
|
69
|
-
const eventStats = await this.service.fetchLatest();
|
|
70
|
-
const fetchEndDate = new Date();
|
|
71
|
-
debug(`Finished fetching ${eventStats.totalEvents} analytics events in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
72
|
-
|
|
73
|
-
const aggregateStartDate = new Date();
|
|
74
|
-
debug(`Starting email analytics aggregation for ${eventStats.emailIds.length} emails`);
|
|
75
|
-
await this.service.aggregateStats(eventStats);
|
|
76
|
-
const aggregateEndDate = new Date();
|
|
77
|
-
debug(`Finished aggregating email analytics in ${aggregateEndDate.getTime() - aggregateStartDate.getTime()}ms`);
|
|
78
|
-
|
|
79
|
-
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate.getTime() - fetchStartDate.getTime()}ms`);
|
|
85
|
+
const eventStats = await this.fetchLatest();
|
|
80
86
|
|
|
81
87
|
this.fetching = false;
|
|
82
88
|
return eventStats;
|
|
83
89
|
} catch (e) {
|
|
84
90
|
logging.error(e, 'Error while fetching email analytics');
|
|
91
|
+
|
|
92
|
+
// Log again only the error, otherwise we lose the stack trace
|
|
93
|
+
logging.error(e);
|
|
85
94
|
}
|
|
86
95
|
this.fetching = false;
|
|
87
96
|
}
|
|
@@ -14,10 +14,12 @@ module.exports = {
|
|
|
14
14
|
) {
|
|
15
15
|
// use a random seconds value to avoid spikes to external APIs on the minute
|
|
16
16
|
const s = Math.floor(Math.random() * 60); // 0-59
|
|
17
|
+
const m = Math.floor(Math.random() * 60); // 0-59
|
|
18
|
+
const h = Math.floor(Math.random() * 6); // 0-5
|
|
17
19
|
|
|
18
|
-
// Run everyday at
|
|
20
|
+
// Run everyday at {0-5}:XX:XX AM to clean all expired complimentary subscriptions
|
|
19
21
|
jobsService.addJob({
|
|
20
|
-
at: `${s}
|
|
22
|
+
at: `${s} ${m} ${h} * * *`,
|
|
21
23
|
job: path.resolve(__dirname, 'clean-expired-comped.js'),
|
|
22
24
|
name: 'clean-expired-comped'
|
|
23
25
|
});
|