ghost 5.36.1 → 5.37.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-adapter-cache-memory-ttl-5.37.0.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.37.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.37.0.tgz +0 -0
- package/components/tryghost-api-framework-5.37.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.37.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.37.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.37.0.tgz +0 -0
- package/components/tryghost-constants-5.37.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.36.1.tgz → tryghost-custom-theme-settings-service-5.37.0.tgz} +0 -0
- package/components/tryghost-data-generator-5.37.0.tgz +0 -0
- package/components/tryghost-domain-events-5.37.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.37.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.37.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.37.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.37.0.tgz +0 -0
- package/components/tryghost-email-events-5.37.0.tgz +0 -0
- package/components/{tryghost-email-service-5.36.1.tgz → tryghost-email-service-5.37.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.37.0.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.37.0.tgz +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.36.1.tgz → tryghost-express-dynamic-redirects-5.37.0.tgz} +0 -0
- package/components/tryghost-external-media-inliner-5.37.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.37.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.37.0.tgz +0 -0
- package/components/tryghost-i18n-5.37.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.37.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.37.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.36.1.tgz → tryghost-job-manager-5.37.0.tgz} +0 -0
- package/components/tryghost-link-redirects-5.37.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.37.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.37.0.tgz +0 -0
- package/components/tryghost-magic-link-5.37.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.37.0.tgz +0 -0
- package/components/{tryghost-member-attribution-5.36.1.tgz → tryghost-member-attribution-5.37.0.tgz} +0 -0
- package/components/tryghost-member-events-5.37.0.tgz +0 -0
- package/components/tryghost-members-api-5.37.0.tgz +0 -0
- package/components/tryghost-members-csv-5.37.0.tgz +0 -0
- package/components/{tryghost-members-events-service-5.36.1.tgz → tryghost-members-events-service-5.37.0.tgz} +0 -0
- package/components/tryghost-members-importer-5.37.0.tgz +0 -0
- package/components/{tryghost-members-offers-5.36.1.tgz → tryghost-members-offers-5.37.0.tgz} +0 -0
- package/components/tryghost-members-payments-5.37.0.tgz +0 -0
- package/components/{tryghost-members-ssr-5.36.1.tgz → tryghost-members-ssr-5.37.0.tgz} +0 -0
- package/components/tryghost-members-stripe-service-5.37.0.tgz +0 -0
- package/components/tryghost-milestones-5.37.0.tgz +0 -0
- package/components/tryghost-minifier-5.37.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.37.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.37.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.37.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.37.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.37.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.37.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.37.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.37.0.tgz +0 -0
- package/components/tryghost-package-json-5.37.0.tgz +0 -0
- package/components/tryghost-referrers-5.37.0.tgz +0 -0
- package/components/tryghost-security-5.37.0.tgz +0 -0
- package/components/tryghost-session-service-5.37.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.37.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.37.0.tgz +0 -0
- package/components/{tryghost-staff-service-5.36.1.tgz → tryghost-staff-service-5.37.0.tgz} +0 -0
- package/components/tryghost-stats-service-5.37.0.tgz +0 -0
- package/components/tryghost-tiers-5.37.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.37.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.37.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.37.0.tgz +0 -0
- package/components/tryghost-webmentions-5.37.0.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 +44 -30
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/error.hbs +2 -2
- package/content/themes/casper/package.json +1 -1
- package/core/boot.js +4 -2
- package/core/built/admin/assets/chunk.143.27cd10a38f877e715b35.js +49 -0
- package/core/built/admin/assets/chunk.178.dd6cf17fb0986acf19d6.js +10 -0
- package/core/built/admin/assets/{chunk.502.800e1515996bcc900013.js → chunk.652.bb618bc5abf23bed4e87.js} +855 -784
- package/core/built/admin/assets/{chunk.79.53e8aa9671b2d5dae8ba.js → chunk.79.4a959c324df25480b90e.js} +191 -184
- package/core/built/admin/assets/{ghost-b828e9e3c161aae92909c2e163656bb1.js → ghost-2948791640be026b987b88f89034bc85.js} +260 -238
- package/core/built/admin/assets/ghost-dark-6ea4b338f17a43c204b7c1e207b90cd7.css +1 -0
- package/core/built/admin/assets/ghost-efbe4dcc249d119a955b038aae5c980d.css +1 -0
- package/core/built/admin/assets/{vendor-c4684647d4f5213e5dbb6763de430e7e.js → vendor-b982e3bf1020bff77b2a3c44d5f59e55.js} +176 -191
- package/core/built/admin/index.html +6 -6
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +1 -5
- package/core/frontend/meta/asset-url.js +9 -0
- package/core/server/api/endpoints/db.js +17 -0
- package/core/server/api/endpoints/mentions.js +2 -1
- package/core/server/api/endpoints/utils/serializers/output/posts.js +2 -2
- package/core/server/data/db/backup.js +13 -13
- package/core/server/data/importer/handlers/image.js +2 -2
- package/core/server/data/importer/import-manager.js +56 -4
- package/core/server/data/importer/importers/ContentFileImporter.js +128 -0
- package/core/server/data/schema/commands.js +21 -10
- package/core/server/models/mention.js +13 -0
- package/core/server/run-update-check.js +3 -1
- package/core/server/services/media-inliner/index.js +1 -0
- package/core/server/services/media-inliner/service.js +16 -0
- package/core/server/services/members/stats/members-stats.js +13 -9
- package/core/server/services/mentions/BookshelfMentionRepository.js +12 -1
- package/core/server/services/mentions/MentionController.js +7 -1
- package/core/server/services/mentions/WebmentionMetadata.js +3 -2
- package/core/server/services/mentions/service.js +1 -4
- package/core/server/{analytics-events.js → services/segment/index.js} +4 -3
- package/core/server/services/stripe/config.js +4 -0
- package/core/server/update-check.js +5 -3
- package/core/server/web/api/endpoints/admin/app.js +5 -4
- package/core/server/web/api/endpoints/admin/routes.js +6 -0
- package/core/server/web/api/middleware/index.js +1 -2
- package/core/shared/config/overrides.json +34 -0
- package/core/shared/labs.js +3 -2
- package/package.json +153 -148
- package/yarn.lock +765 -820
- package/components/tryghost-adapter-cache-memory-ttl-5.36.1.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.36.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.36.1.tgz +0 -0
- package/components/tryghost-api-framework-5.36.1.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.36.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.36.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.36.1.tgz +0 -0
- package/components/tryghost-constants-5.36.1.tgz +0 -0
- package/components/tryghost-data-generator-5.36.1.tgz +0 -0
- package/components/tryghost-domain-events-5.36.1.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.36.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.36.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.36.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.36.1.tgz +0 -0
- package/components/tryghost-email-events-5.36.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.36.1.tgz +0 -0
- package/components/tryghost-event-aware-cache-wrapper-5.36.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.36.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.36.1.tgz +0 -0
- package/components/tryghost-i18n-5.36.1.tgz +0 -0
- package/components/tryghost-importer-revue-5.36.1.tgz +0 -0
- package/components/tryghost-link-redirects-5.36.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.36.1.tgz +0 -0
- package/components/tryghost-link-tracking-5.36.1.tgz +0 -0
- package/components/tryghost-magic-link-5.36.1.tgz +0 -0
- package/components/tryghost-mailgun-client-5.36.1.tgz +0 -0
- package/components/tryghost-member-events-5.36.1.tgz +0 -0
- package/components/tryghost-members-api-5.36.1.tgz +0 -0
- package/components/tryghost-members-csv-5.36.1.tgz +0 -0
- package/components/tryghost-members-importer-5.36.1.tgz +0 -0
- package/components/tryghost-members-payments-5.36.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.36.1.tgz +0 -0
- package/components/tryghost-milestones-5.36.1.tgz +0 -0
- package/components/tryghost-minifier-5.36.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.36.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.36.1.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.36.1.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.36.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.36.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.36.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.36.1.tgz +0 -0
- package/components/tryghost-package-json-5.36.1.tgz +0 -0
- package/components/tryghost-referrers-5.36.1.tgz +0 -0
- package/components/tryghost-security-5.36.1.tgz +0 -0
- package/components/tryghost-session-service-5.36.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.36.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.36.1.tgz +0 -0
- package/components/tryghost-stats-service-5.36.1.tgz +0 -0
- package/components/tryghost-tiers-5.36.1.tgz +0 -0
- package/components/tryghost-update-check-service-5.36.1.tgz +0 -0
- package/components/tryghost-verification-trigger-5.36.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.36.1.tgz +0 -0
- package/components/tryghost-webmentions-5.36.1.tgz +0 -0
- package/core/built/admin/assets/chunk.143.26ea9f26571d656653f0.js +0 -49
- package/core/built/admin/assets/chunk.178.dd71b3a764b73facc400.js +0 -11
- package/core/built/admin/assets/ghost-7ecf5c7934d90798485ee5ac2956f7fe.css +0 -1
- package/core/built/admin/assets/ghost-dark-e50717df8e57d3e7fee67a0bcea895ad.css +0 -1
- package/core/frontend/src/cards/css/before-after.css +0 -81
- package/core/frontend/src/cards/js/before-after.js +0 -36
- package/core/server/data/importer/importers/image.js +0 -76
- package/core/server/data/schema/clients/index.js +0 -7
- package/core/server/data/schema/clients/mysql.js +0 -34
- package/core/server/data/schema/clients/sqlite3.js +0 -39
- package/core/server/services/mentions/WebmentionRequest.js +0 -20
- package/core/server/web/admin/middleware.js +0 -17
- package/core/server/web/api/middleware/version-match.js +0 -31
- /package/core/built/admin/assets/{chunk.502.800e1515996bcc900013.js.LICENSE.txt → chunk.652.bb618bc5abf23bed4e87.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%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.37%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%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-efbe4dcc249d119a955b038aae5c980d.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-b982e3bf1020bff77b2a3c44d5f59e55.js"></script>
|
|
60
|
+
<script src="assets/chunk.652.bb618bc5abf23bed4e87.js"></script>
|
|
61
|
+
<script src="assets/chunk.143.27cd10a38f877e715b35.js"></script>
|
|
62
|
+
<script src="assets/ghost-2948791640be026b987b88f89034bc85.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Converts normal HTML into AMP HTML with Amperize module and uses a cache to return it from
|
|
8
8
|
// there if available. The cacheId is a combination of `updated_at` and the `slug`.
|
|
9
|
-
const {DateTime
|
|
9
|
+
const {DateTime} = require('luxon');
|
|
10
10
|
const errors = require('@tryghost/errors');
|
|
11
11
|
const logging = require('@tryghost/logging');
|
|
12
12
|
|
|
@@ -120,8 +120,6 @@ function getAmperizeHTML(html, post) {
|
|
|
120
120
|
|
|
121
121
|
amperize = amperize || new Amperize();
|
|
122
122
|
|
|
123
|
-
const startedAtMoment = DateTime.now();
|
|
124
|
-
|
|
125
123
|
let cacheDateTime;
|
|
126
124
|
let postDateTime;
|
|
127
125
|
|
|
@@ -136,8 +134,6 @@ function getAmperizeHTML(html, post) {
|
|
|
136
134
|
if (!amperizeCache[post.id] || cacheDateTime.diff(postDateTime).valueOf() < 0) {
|
|
137
135
|
return new Promise((resolve) => {
|
|
138
136
|
amperize.parse(html, (err, res) => {
|
|
139
|
-
logging.info('amp.parse', post.url, Interval.fromDateTimes(startedAtMoment, DateTime.now()).length('milliseconds') + 'ms');
|
|
140
|
-
|
|
141
137
|
if (err) {
|
|
142
138
|
if (err.src) {
|
|
143
139
|
// This is a valid 500 GhostError because it means the amperize parser is unable to handle some Ghost HTML.
|
|
@@ -2,6 +2,7 @@ const crypto = require('crypto');
|
|
|
2
2
|
const config = require('../../shared/config');
|
|
3
3
|
const {blogIcon} = require('../../server/lib/image');
|
|
4
4
|
const urlUtils = require('../../shared/url-utils');
|
|
5
|
+
const {SafeString} = require('../services/handlebars');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Serve either uploaded favicon or default
|
|
@@ -11,7 +12,15 @@ function getFaviconUrl() {
|
|
|
11
12
|
return blogIcon.getIconUrl();
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Prepare URL for an asset
|
|
17
|
+
* @param {string|SafeString} path — the asset’s path
|
|
18
|
+
* @param {boolean} hasMinFile — flag for the existence of a minified version for the asset
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
14
21
|
function getAssetUrl(path, hasMinFile) {
|
|
22
|
+
path = path instanceof SafeString ? path.string : path;
|
|
23
|
+
|
|
15
24
|
// CASE: favicon - this is special path with its own functionality
|
|
16
25
|
if (path.match(/\/?favicon\.(ico|png)$/)) {
|
|
17
26
|
// @TODO, resolve this - we should only be resolving subdirectory and extension.
|
|
@@ -3,6 +3,7 @@ const moment = require('moment-timezone');
|
|
|
3
3
|
const dbBackup = require('../../data/db/backup');
|
|
4
4
|
const exporter = require('../../data/exporter');
|
|
5
5
|
const importer = require('../../data/importer');
|
|
6
|
+
const mediaInliner = require('../../services/media-inliner');
|
|
6
7
|
const errors = require('@tryghost/errors');
|
|
7
8
|
const models = require('../../models');
|
|
8
9
|
const settingsCache = require('../../../shared/settings-cache');
|
|
@@ -93,6 +94,22 @@ module.exports = {
|
|
|
93
94
|
}
|
|
94
95
|
},
|
|
95
96
|
|
|
97
|
+
inlineMedia: {
|
|
98
|
+
permissions: {
|
|
99
|
+
method: 'importContent'
|
|
100
|
+
},
|
|
101
|
+
validation: {
|
|
102
|
+
options: {
|
|
103
|
+
include: {
|
|
104
|
+
values: ['domains']
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
async query(frame) {
|
|
109
|
+
return mediaInliner.api.startMediaInliner(frame.data.domains);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
96
113
|
deleteAllContent: {
|
|
97
114
|
headers: {
|
|
98
115
|
cacheInvalidate: true
|
|
@@ -12,10 +12,10 @@ module.exports = {
|
|
|
12
12
|
}
|
|
13
13
|
let posts = [];
|
|
14
14
|
|
|
15
|
-
const tiersModels = await membersService.api
|
|
15
|
+
const tiersModels = await membersService.api?.productRepository.list({
|
|
16
16
|
limit: 'all'
|
|
17
17
|
});
|
|
18
|
-
const tiers = tiersModels
|
|
18
|
+
const tiers = tiersModels?.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
|
|
19
19
|
if (models.meta) {
|
|
20
20
|
for (let model of models.data) {
|
|
21
21
|
let post = await mappers.posts(model, frame, {tiers});
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const Promise = require('bluebird');
|
|
7
6
|
const config = require('../../../shared/config');
|
|
8
7
|
const logging = require('@tryghost/logging');
|
|
9
8
|
const urlUtils = require('../../../shared/url-utils');
|
|
@@ -32,7 +31,7 @@ const readBackup = async (filename) => {
|
|
|
32
31
|
const exists = await fs.pathExists(backupPath);
|
|
33
32
|
|
|
34
33
|
if (exists) {
|
|
35
|
-
const backupFile = await fs.readFile(backupPath);
|
|
34
|
+
const backupFile = await fs.readFile(backupPath, 'utf8');
|
|
36
35
|
return JSON.parse(backupFile);
|
|
37
36
|
} else {
|
|
38
37
|
return null;
|
|
@@ -40,23 +39,24 @@ const readBackup = async (filename) => {
|
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* @
|
|
42
|
+
* Does an export, and stores this in a local file
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @returns {Promise<String>}
|
|
46
46
|
*/
|
|
47
47
|
const backup = async function backup(options = {}) {
|
|
48
48
|
logging.info('Creating database backup');
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
filename: exporter.fileName(options)
|
|
53
|
-
};
|
|
50
|
+
const filename = await exporter.fileName(options);
|
|
51
|
+
const data = await exporter.doExport(options);
|
|
54
52
|
|
|
55
|
-
const
|
|
56
|
-
|
|
53
|
+
const filePath = await writeExportFile({
|
|
54
|
+
data,
|
|
55
|
+
filename
|
|
56
|
+
});
|
|
57
57
|
|
|
58
|
-
logging.info(
|
|
59
|
-
return
|
|
58
|
+
logging.info(`Database backup written to ${filePath}`);
|
|
59
|
+
return filePath;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
module.exports = {
|
|
@@ -15,7 +15,7 @@ ImageHandler = {
|
|
|
15
15
|
const store = storage.getStorage('images');
|
|
16
16
|
const baseDirRegex = baseDir ? new RegExp('^' + baseDir + '/') : new RegExp('');
|
|
17
17
|
|
|
18
|
-
const imageFolderRegexes = _.map(
|
|
18
|
+
const imageFolderRegexes = _.map(store.staticFileURLPrefix.split('/'), function (dir) {
|
|
19
19
|
return new RegExp('^' + dir + '/');
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -36,7 +36,7 @@ ImageHandler = {
|
|
|
36
36
|
|
|
37
37
|
return Promise.all(files.map(function (image) {
|
|
38
38
|
return store.getUniqueFileName(image, image.targetDir).then(function (targetFilename) {
|
|
39
|
-
image.newPath = urlUtils.urlJoin('/', urlUtils.getSubdir(),
|
|
39
|
+
image.newPath = urlUtils.urlJoin('/', urlUtils.getSubdir(), store.staticFileURLPrefix,
|
|
40
40
|
path.relative(config.getContentPath('images'), targetFilename));
|
|
41
41
|
|
|
42
42
|
return image;
|
|
@@ -11,15 +11,19 @@ const debug = require('@tryghost/debug')('import-manager');
|
|
|
11
11
|
const logging = require('@tryghost/logging');
|
|
12
12
|
const errors = require('@tryghost/errors');
|
|
13
13
|
const ImageHandler = require('./handlers/image');
|
|
14
|
+
const ImporterContentFileHandler = require('@tryghost/importer-handler-content-files');
|
|
14
15
|
const RevueHandler = require('./handlers/revue');
|
|
15
16
|
const JSONHandler = require('./handlers/json');
|
|
16
17
|
const MarkdownHandler = require('./handlers/markdown');
|
|
17
|
-
const
|
|
18
|
+
const ContentFileImporter = require('./importers/ContentFileImporter');
|
|
18
19
|
const RevueImporter = require('@tryghost/importer-revue');
|
|
19
20
|
const DataImporter = require('./importers/data');
|
|
20
21
|
const urlUtils = require('../../../shared/url-utils');
|
|
21
22
|
const {GhostMailer} = require('../../services/mail');
|
|
22
23
|
const jobManager = require('../../services/jobs');
|
|
24
|
+
const mediaStorage = require('../../adapters/storage').getStorage('media');
|
|
25
|
+
const imageStorage = require('../../adapters/storage').getStorage('images');
|
|
26
|
+
const fileStorage = require('../../adapters/storage').getStorage('files');
|
|
23
27
|
|
|
24
28
|
const emailTemplate = require('./email-template');
|
|
25
29
|
const ghostMailer = new GhostMailer();
|
|
@@ -51,15 +55,55 @@ let defaults = {
|
|
|
51
55
|
|
|
52
56
|
class ImportManager {
|
|
53
57
|
constructor() {
|
|
58
|
+
const mediaHandler = new ImporterContentFileHandler({
|
|
59
|
+
type: 'media',
|
|
60
|
+
// @NOTE: making the second parameter strict folder "content/media" brakes the glob pattern
|
|
61
|
+
// in the importer, so we need to keep it as general "content" unless
|
|
62
|
+
// it becomes a strict requirement
|
|
63
|
+
directories: ['media', 'content'],
|
|
64
|
+
extensions: config.get('uploads').media.extensions,
|
|
65
|
+
contentTypes: config.get('uploads').media.contentTypes,
|
|
66
|
+
contentPath: config.getContentPath('media'),
|
|
67
|
+
urlUtils: urlUtils,
|
|
68
|
+
storage: mediaStorage
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const filesHandler = new ImporterContentFileHandler({
|
|
72
|
+
type: 'files',
|
|
73
|
+
// @NOTE: making the second parameter strict folder "content/files" brakes the glob pattern
|
|
74
|
+
// in the importer, so we need to keep it as general "content" unless
|
|
75
|
+
// it becomes a strict requirement
|
|
76
|
+
directories: ['files', 'content'],
|
|
77
|
+
extensions: config.get('uploads').files.extensions,
|
|
78
|
+
contentTypes: config.get('uploads').files.contentTypes,
|
|
79
|
+
contentPath: config.getContentPath('files'),
|
|
80
|
+
urlUtils: urlUtils,
|
|
81
|
+
storage: fileStorage
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const imageImporter = new ContentFileImporter({
|
|
85
|
+
type: 'images',
|
|
86
|
+
store: imageStorage
|
|
87
|
+
});
|
|
88
|
+
const mediaImporter = new ContentFileImporter({
|
|
89
|
+
type: 'media',
|
|
90
|
+
store: mediaStorage
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const contentFilesImporter = new ContentFileImporter({
|
|
94
|
+
type: 'files',
|
|
95
|
+
store: fileStorage
|
|
96
|
+
});
|
|
97
|
+
|
|
54
98
|
/**
|
|
55
99
|
* @type {Importer[]} importers
|
|
56
100
|
*/
|
|
57
|
-
this.importers = [
|
|
101
|
+
this.importers = [imageImporter, mediaImporter, contentFilesImporter, RevueImporter, DataImporter];
|
|
58
102
|
|
|
59
103
|
/**
|
|
60
104
|
* @type {Handler[]}
|
|
61
105
|
*/
|
|
62
|
-
this.handlers = [ImageHandler, RevueHandler, JSONHandler, MarkdownHandler];
|
|
106
|
+
this.handlers = [ImageHandler, mediaHandler, filesHandler, RevueHandler, JSONHandler, MarkdownHandler];
|
|
63
107
|
|
|
64
108
|
// Keep track of file to cleanup at the end
|
|
65
109
|
/**
|
|
@@ -294,7 +338,15 @@ class ImportManager {
|
|
|
294
338
|
*/
|
|
295
339
|
async processFile(file, ext) {
|
|
296
340
|
const fileHandlers = _.filter(this.handlers, function (handler) {
|
|
297
|
-
|
|
341
|
+
let match = _.includes(handler.extensions, ext);
|
|
342
|
+
|
|
343
|
+
// CASE: content file handlers should ignore files in the root directory
|
|
344
|
+
if (match && handler.directories && handler.directories.length) {
|
|
345
|
+
const dir = path.dirname(file.path)?.split('/')[1];
|
|
346
|
+
match = _.includes(handler.directories, dir);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return match;
|
|
298
350
|
});
|
|
299
351
|
|
|
300
352
|
const importData = {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
let replaceImage;
|
|
3
|
+
let preProcessPosts;
|
|
4
|
+
let preProcessTags;
|
|
5
|
+
let preProcessUsers;
|
|
6
|
+
|
|
7
|
+
replaceImage = function (markdown, image) {
|
|
8
|
+
if (!markdown) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Normalizes to include a trailing slash if there was one
|
|
13
|
+
const regex = new RegExp('(/)?' + image.originalPath, 'gm');
|
|
14
|
+
|
|
15
|
+
return markdown.replace(regex, image.newPath);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} data
|
|
20
|
+
* @param {Object[]} data.posts
|
|
21
|
+
* @param {Object} contentFile
|
|
22
|
+
* @param {String} contentFile.originalPath
|
|
23
|
+
* @param {String} contentFile.newPath
|
|
24
|
+
*/
|
|
25
|
+
preProcessPosts = function (data, contentFile) {
|
|
26
|
+
_.each(data.posts, function (post) {
|
|
27
|
+
post.markdown = replaceImage(post.markdown, contentFile);
|
|
28
|
+
if (post.html) {
|
|
29
|
+
post.html = replaceImage(post.html, contentFile);
|
|
30
|
+
}
|
|
31
|
+
if (post.feature_image) {
|
|
32
|
+
post.feature_image = replaceImage(post.feature_image, contentFile);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
preProcessTags = function (data, image) {
|
|
38
|
+
_.each(data.tags, function (tag) {
|
|
39
|
+
if (tag.feature_image) {
|
|
40
|
+
tag.feature_image = replaceImage(tag.feature_image, image);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
preProcessUsers = function (data, image) {
|
|
46
|
+
_.each(data.users, function (user) {
|
|
47
|
+
if (user.cover_image) {
|
|
48
|
+
user.cover_image = replaceImage(user.cover_image, image);
|
|
49
|
+
}
|
|
50
|
+
if (user.profile_image) {
|
|
51
|
+
user.profile_image = replaceImage(user.profile_image, image);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
class ContentFileImporter {
|
|
57
|
+
/** @property {string} */
|
|
58
|
+
type;
|
|
59
|
+
|
|
60
|
+
/** @property {import('ghost-storage-base')} */
|
|
61
|
+
#store;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} deps
|
|
66
|
+
* @param {'images' | 'media' | 'files'} deps.type - importer type
|
|
67
|
+
* @param {import('ghost-storage-base')} deps.store
|
|
68
|
+
*/
|
|
69
|
+
constructor(deps) {
|
|
70
|
+
this.type = deps.type;
|
|
71
|
+
this.#store = deps.store;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
preProcess(importData) {
|
|
75
|
+
if (this.type === 'images') {
|
|
76
|
+
if (importData.images && importData.data && importData.data.data) {
|
|
77
|
+
_.each(importData.images, function (image) {
|
|
78
|
+
preProcessPosts(importData.data.data, image);
|
|
79
|
+
preProcessTags(importData.data.data, image);
|
|
80
|
+
preProcessUsers(importData.data.data, image);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
importData.preProcessedByImage = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// @NOTE: the type === 'media' check does not belong here and should be abstracted away
|
|
88
|
+
// to make this importer more generic
|
|
89
|
+
if (this.type === 'media') {
|
|
90
|
+
if (importData.media && importData.data && importData.data.data) {
|
|
91
|
+
_.each(importData.media, function (file) {
|
|
92
|
+
preProcessPosts(importData.data.data, file);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
importData.preProcessedByMedia = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.type === 'files') {
|
|
100
|
+
if (importData.files && importData.data && importData.data.data) {
|
|
101
|
+
_.each(importData.files, function (file) {
|
|
102
|
+
preProcessPosts(importData.data.data, file);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
importData.preProcessedByFiles = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return importData;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param {Object[]} contentFilesData
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
117
|
+
doImport(contentFilesData) {
|
|
118
|
+
const store = this.#store;
|
|
119
|
+
|
|
120
|
+
return Promise.all(contentFilesData.map(function (contentFile) {
|
|
121
|
+
return store.save(contentFile, contentFile.targetDir).then(function (result) {
|
|
122
|
+
return {originalPath: contentFile.originalPath, newPath: contentFile.newPath, stored: result};
|
|
123
|
+
});
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = ContentFileImporter;
|
|
@@ -6,7 +6,6 @@ const tpl = require('@tryghost/tpl');
|
|
|
6
6
|
const db = require('../db');
|
|
7
7
|
const DatabaseInfo = require('@tryghost/database-info');
|
|
8
8
|
const schema = require('./schema');
|
|
9
|
-
const clients = require('./clients');
|
|
10
9
|
|
|
11
10
|
const messages = {
|
|
12
11
|
hasPrimaryKeySQLiteError: 'Must use hasPrimaryKeySQLite on an SQLite3 database',
|
|
@@ -432,11 +431,15 @@ function deleteTable(table, transaction = db.knex) {
|
|
|
432
431
|
/**
|
|
433
432
|
* @param {import('knex').Knex} [transaction] - connection to the DB
|
|
434
433
|
*/
|
|
435
|
-
function getTables(transaction = db.knex) {
|
|
434
|
+
async function getTables(transaction = db.knex) {
|
|
436
435
|
const client = transaction.client.config.client;
|
|
437
436
|
|
|
438
|
-
if (
|
|
439
|
-
|
|
437
|
+
if (client === 'sqlite3') {
|
|
438
|
+
const response = await transaction.raw('select * from sqlite_master where type = "table"');
|
|
439
|
+
return _.reject(_.map(response, 'tbl_name'), name => name === 'sqlite_sequence');
|
|
440
|
+
} else if (client === 'mysql2') {
|
|
441
|
+
const response = await transaction.raw('show tables');
|
|
442
|
+
return _.flatten(_.map(response[0], entry => _.values(entry)));
|
|
440
443
|
}
|
|
441
444
|
|
|
442
445
|
return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
|
|
@@ -446,11 +449,15 @@ function getTables(transaction = db.knex) {
|
|
|
446
449
|
* @param {string} table
|
|
447
450
|
* @param {import('knex').Knex} [transaction] - connection to the DB
|
|
448
451
|
*/
|
|
449
|
-
function getIndexes(table, transaction = db.knex) {
|
|
452
|
+
async function getIndexes(table, transaction = db.knex) {
|
|
450
453
|
const client = transaction.client.config.client;
|
|
451
454
|
|
|
452
|
-
if (
|
|
453
|
-
|
|
455
|
+
if (client === 'sqlite3') {
|
|
456
|
+
const response = await transaction.raw(`pragma index_list("${table}")`);
|
|
457
|
+
return _.flatten(_.map(response, 'name'));
|
|
458
|
+
} else if (client === 'mysql2') {
|
|
459
|
+
const response = await transaction.raw(`SHOW INDEXES from ${table}`);
|
|
460
|
+
return _.flatten(_.map(response[0], 'Key_name'));
|
|
454
461
|
}
|
|
455
462
|
|
|
456
463
|
return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
|
|
@@ -460,11 +467,15 @@ function getIndexes(table, transaction = db.knex) {
|
|
|
460
467
|
* @param {string} table
|
|
461
468
|
* @param {import('knex').Knex} [transaction] - connection to the DB
|
|
462
469
|
*/
|
|
463
|
-
function getColumns(table, transaction = db.knex) {
|
|
470
|
+
async function getColumns(table, transaction = db.knex) {
|
|
464
471
|
const client = transaction.client.config.client;
|
|
465
472
|
|
|
466
|
-
if (
|
|
467
|
-
|
|
473
|
+
if (client === 'sqlite3') {
|
|
474
|
+
const response = await transaction.raw(`pragma table_info("${table}")`);
|
|
475
|
+
return _.flatten(_.map(response, 'name'));
|
|
476
|
+
} else if (client === 'mysql2') {
|
|
477
|
+
const response = await transaction.raw(`SHOW COLUMNS from ${table}`);
|
|
478
|
+
return _.flatten(_.map(response[0], 'Field'));
|
|
468
479
|
}
|
|
469
480
|
|
|
470
481
|
return Promise.reject(tpl(messages.noSupportForDatabase, {client: client}));
|
|
@@ -9,6 +9,19 @@ const Mention = ghostBookshelf.Model.extend({
|
|
|
9
9
|
enforcedFilters() {
|
|
10
10
|
return 'deleted:false';
|
|
11
11
|
}
|
|
12
|
+
}, {
|
|
13
|
+
permittedOptions(methodName) {
|
|
14
|
+
let options = ghostBookshelf.Model.permittedOptions.call(this, methodName);
|
|
15
|
+
const validOptions = {
|
|
16
|
+
findPage: ['selectRaw', 'whereRaw']
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (validOptions[methodName]) {
|
|
20
|
+
options = options.concat(validOptions[methodName]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return options;
|
|
24
|
+
}
|
|
12
25
|
});
|
|
13
26
|
|
|
14
27
|
module.exports = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./service');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
async init() {
|
|
3
|
+
const debug = require('@tryghost/debug')('mediaInliner');
|
|
4
|
+
|
|
5
|
+
this.api = {
|
|
6
|
+
// @NOTE: the inlining should become an offloaded job
|
|
7
|
+
// startMediaInliner: mediaInliner.inlineMedia
|
|
8
|
+
startMediaInliner: (domains) => {
|
|
9
|
+
debug('[Inliner] Starting media inlining job for domains: ', domains);
|
|
10
|
+
return {
|
|
11
|
+
status: 'success'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const moment = require('moment-timezone');
|
|
2
|
-
const Promise = require('bluebird');
|
|
3
2
|
|
|
4
3
|
const dateFormat = 'YYYY-MM-DD HH:mm:ss';
|
|
5
4
|
class MembersStats {
|
|
@@ -131,14 +130,19 @@ class MembersStats {
|
|
|
131
130
|
const totalMembers = await this.getTotalMembers();
|
|
132
131
|
|
|
133
132
|
// perform final calculations in parallel
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
133
|
+
const [total, totalInRange, totalOnDate, newToday] = await Promise.all([
|
|
134
|
+
totalMembers,
|
|
135
|
+
this.getTotalMembersInRange({days, totalMembers, siteTimezone}),
|
|
136
|
+
this.getTotalMembersOnDatesInRange({days, totalMembers, siteTimezone}),
|
|
137
|
+
this.getNewMembersToday({siteTimezone})
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
total,
|
|
142
|
+
total_in_range: totalInRange,
|
|
143
|
+
total_on_date: totalOnDate,
|
|
144
|
+
new_today: newToday
|
|
145
|
+
};
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
|
|
@@ -65,7 +65,18 @@ module.exports = class BookshelfMentionRepository {
|
|
|
65
65
|
* @returns {Promise<Page<import('@tryghost/webmentions/lib/Mention')>>}
|
|
66
66
|
*/
|
|
67
67
|
async getPage(options) {
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @type {GetPageOptions & {whereRaw?: string}}
|
|
70
|
+
*/
|
|
71
|
+
const _options = {
|
|
72
|
+
...options
|
|
73
|
+
};
|
|
74
|
+
delete _options.unique;
|
|
75
|
+
if (options.unique) {
|
|
76
|
+
_options.whereRaw = 'NOT EXISTS (select id from mentions as m where m.id > mentions.id and m.source = mentions.source)';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const page = await this.#MentionModel.findPage(_options);
|
|
69
80
|
|
|
70
81
|
return {
|
|
71
82
|
data: await Promise.all(page.data.map(model => this.#modelToMention(model))),
|
|
@@ -80,11 +80,17 @@ module.exports = class MentionController {
|
|
|
80
80
|
order = 'created_at asc';
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
let unique;
|
|
84
|
+
if (frame.options.unique && (frame.options.unique === 'true' || frame.options.unique === true)) {
|
|
85
|
+
unique = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
const mentions = await this.#api.listMentions({
|
|
84
89
|
filter: frame.options.filter,
|
|
85
90
|
order,
|
|
86
91
|
limit,
|
|
87
|
-
page
|
|
92
|
+
page,
|
|
93
|
+
unique
|
|
88
94
|
});
|
|
89
95
|
|
|
90
96
|
const resources = await Promise.all(mentions.data.map((mention) => {
|
|
@@ -6,14 +6,15 @@ module.exports = class WebmentionMetadata {
|
|
|
6
6
|
* @returns {Promise<import('@tryghost/webmentions/lib/MentionsAPI').WebmentionMetadata>}
|
|
7
7
|
*/
|
|
8
8
|
async fetch(url) {
|
|
9
|
-
const data = await oembedService.fetchOembedDataFromUrl(url.href, '
|
|
9
|
+
const data = await oembedService.fetchOembedDataFromUrl(url.href, 'mention');
|
|
10
10
|
const result = {
|
|
11
11
|
siteTitle: data.metadata.publisher,
|
|
12
12
|
title: data.metadata.title,
|
|
13
13
|
excerpt: data.metadata.description,
|
|
14
14
|
author: data.metadata.author,
|
|
15
15
|
image: data.metadata.thumbnail ? new URL(data.metadata.thumbnail) : null,
|
|
16
|
-
favicon: data.metadata.icon ? new URL(data.metadata.icon) : null
|
|
16
|
+
favicon: data.metadata.icon ? new URL(data.metadata.icon) : null,
|
|
17
|
+
body: data.body
|
|
17
18
|
};
|
|
18
19
|
return result;
|
|
19
20
|
}
|