ghost 5.26.4 → 5.28.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-manager-5.28.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.26.4.tgz → tryghost-api-framework-5.28.0.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.26.4.tgz → tryghost-api-version-compatibility-service-5.28.0.tgz} +0 -0
- package/components/tryghost-audience-feedback-5.28.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.28.0.tgz +0 -0
- package/components/tryghost-constants-5.28.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.26.4.tgz → tryghost-custom-theme-settings-service-5.28.0.tgz} +0 -0
- package/components/tryghost-data-generator-5.28.0.tgz +0 -0
- package/components/tryghost-domain-events-5.28.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.28.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.28.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.28.0.tgz +0 -0
- package/components/tryghost-email-events-5.28.0.tgz +0 -0
- package/components/tryghost-email-service-5.28.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.28.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.28.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.28.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.28.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.28.0.tgz +0 -0
- package/components/tryghost-job-manager-5.28.0.tgz +0 -0
- package/components/tryghost-link-redirects-5.28.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.28.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.28.0.tgz +0 -0
- package/components/tryghost-magic-link-5.28.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.28.0.tgz +0 -0
- package/components/{tryghost-member-attribution-5.26.4.tgz → tryghost-member-attribution-5.28.0.tgz} +0 -0
- package/components/{tryghost-member-events-5.26.4.tgz → tryghost-member-events-5.28.0.tgz} +0 -0
- package/components/{tryghost-members-api-5.26.4.tgz → tryghost-members-api-5.28.0.tgz} +0 -0
- package/components/tryghost-members-csv-5.28.0.tgz +0 -0
- package/components/tryghost-members-events-service-5.28.0.tgz +0 -0
- package/components/tryghost-members-importer-5.28.0.tgz +0 -0
- package/components/tryghost-members-offers-5.28.0.tgz +0 -0
- package/components/tryghost-members-payments-5.28.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.28.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.28.0.tgz +0 -0
- package/components/tryghost-minifier-5.28.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.28.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.28.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.28.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.28.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.28.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.28.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.28.0.tgz +0 -0
- package/components/tryghost-package-json-5.28.0.tgz +0 -0
- package/components/tryghost-referrers-5.28.0.tgz +0 -0
- package/components/tryghost-security-5.28.0.tgz +0 -0
- package/components/tryghost-session-service-5.28.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.28.0.tgz +0 -0
- package/components/{tryghost-staff-service-5.26.4.tgz → tryghost-staff-service-5.28.0.tgz} +0 -0
- package/components/tryghost-stats-service-5.28.0.tgz +0 -0
- package/components/tryghost-tiers-5.28.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.28.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.28.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.28.0.tgz +0 -0
- package/core/built/admin/assets/{chunk.143.f9454a947ce3ca9468a9.js → chunk.143.16c07be45a0c39262995.js} +6 -6
- package/core/built/admin/assets/{chunk.178.5730ec1241c554356843.js → chunk.178.93714202b2fc31b2b86a.js} +4 -4
- package/core/built/admin/assets/{chunk.613.25718fa355a25fc2a7c1.js → chunk.380.de3a9bf5161ab5300d23.js} +4790 -4306
- package/core/built/admin/assets/{chunk.613.25718fa355a25fc2a7c1.js.LICENSE.txt → chunk.380.de3a9bf5161ab5300d23.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/ghost-1532517fd24b662b42a85ea0881865a5.css +1 -0
- package/core/built/admin/assets/{ghost-5429d972966c85a0a24d766f93db999d.js → ghost-d8834e309d8e4800057d6d17fed9415b.js} +4311 -4289
- package/core/built/admin/assets/ghost-dark-3b8aff3cf9db67f6ad59abec25ae98be.css +1 -0
- package/core/built/admin/assets/{vendor-5aae14724f891e36c43786254980485c.js → vendor-dbf84caa0968ef5da0486055da6636da.js} +113 -63
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/get.js +2 -2
- package/core/frontend/web/middleware/error-handler.js +0 -2
- package/core/server/api/endpoints/themes.js +13 -9
- package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +2 -0
- package/core/server/data/migrations/versions/5.27/2022-12-13-16-15-add-usage-colums-to-tokens.js +20 -0
- package/core/server/data/migrations/versions/5.27/2023-01-04-04-12-drop-suppressions-table.js +14 -0
- package/core/server/data/migrations/versions/5.27/2023-01-04-04-13-add-suppressions-table.js +9 -0
- package/core/server/data/migrations/versions/5.28/2023-01-05-15-13-add-active-theme-permissions.js +12 -0
- package/core/server/data/schema/fixtures/fixtures.json +7 -2
- package/core/server/data/schema/schema.js +4 -1
- package/core/server/models/member.js +2 -1
- package/core/server/models/product.js +1 -1
- package/core/server/models/single-use-token.js +2 -25
- package/core/server/services/bulk-email/bulk-email-processor.js +23 -4
- package/core/server/services/email-service/wrapper.js +2 -1
- package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +13 -5
- package/core/server/services/mega/mega.js +9 -1
- package/core/server/services/members/SingleUseTokenProvider.js +51 -7
- package/core/server/services/members/api.js +8 -1
- package/core/server/services/members/jobs/clean-tokens.js +50 -0
- package/core/server/services/members/jobs/index.js +29 -4
- package/core/server/services/members/service.js +12 -19
- package/core/server/services/newsletters/index.js +8 -1
- package/core/server/services/settings/settings-service.js +8 -1
- package/core/server/services/themes/activate.js +3 -3
- package/core/server/services/themes/index.js +4 -2
- package/core/server/services/themes/storage.js +3 -1
- package/core/server/services/themes/to-json.js +6 -6
- package/core/server/services/themes/validate.js +80 -8
- package/core/server/web/api/endpoints/admin/routes.js +5 -0
- package/core/shared/config/defaults.json +3 -2
- package/core/shared/labs.js +2 -1
- package/package.json +133 -133
- package/yarn.lock +1840 -2000
- package/components/tryghost-adapter-manager-5.26.4.tgz +0 -0
- package/components/tryghost-audience-feedback-5.26.4.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.26.4.tgz +0 -0
- package/components/tryghost-constants-5.26.4.tgz +0 -0
- package/components/tryghost-data-generator-5.26.4.tgz +0 -0
- package/components/tryghost-domain-events-5.26.4.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.26.4.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.26.4.tgz +0 -0
- package/components/tryghost-email-content-generator-5.26.4.tgz +0 -0
- package/components/tryghost-email-events-5.26.4.tgz +0 -0
- package/components/tryghost-email-service-5.26.4.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.26.4.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.26.4.tgz +0 -0
- package/components/tryghost-extract-api-key-5.26.4.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.26.4.tgz +0 -0
- package/components/tryghost-importer-revue-5.26.4.tgz +0 -0
- package/components/tryghost-job-manager-5.26.4.tgz +0 -0
- package/components/tryghost-link-redirects-5.26.4.tgz +0 -0
- package/components/tryghost-link-replacer-5.26.4.tgz +0 -0
- package/components/tryghost-link-tracking-5.26.4.tgz +0 -0
- package/components/tryghost-magic-link-5.26.4.tgz +0 -0
- package/components/tryghost-mailgun-client-5.26.4.tgz +0 -0
- package/components/tryghost-members-csv-5.26.4.tgz +0 -0
- package/components/tryghost-members-events-service-5.26.4.tgz +0 -0
- package/components/tryghost-members-importer-5.26.4.tgz +0 -0
- package/components/tryghost-members-offers-5.26.4.tgz +0 -0
- package/components/tryghost-members-payments-5.26.4.tgz +0 -0
- package/components/tryghost-members-ssr-5.26.4.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.26.4.tgz +0 -0
- package/components/tryghost-minifier-5.26.4.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.26.4.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.26.4.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.26.4.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.26.4.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.26.4.tgz +0 -0
- package/components/tryghost-mw-vhost-5.26.4.tgz +0 -0
- package/components/tryghost-oembed-service-5.26.4.tgz +0 -0
- package/components/tryghost-package-json-5.26.4.tgz +0 -0
- package/components/tryghost-referrers-5.26.4.tgz +0 -0
- package/components/tryghost-security-5.26.4.tgz +0 -0
- package/components/tryghost-session-service-5.26.4.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.26.4.tgz +0 -0
- package/components/tryghost-stats-service-5.26.4.tgz +0 -0
- package/components/tryghost-tiers-5.26.4.tgz +0 -0
- package/components/tryghost-update-check-service-5.26.4.tgz +0 -0
- package/components/tryghost-verification-trigger-5.26.4.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.26.4.tgz +0 -0
- package/core/built/admin/assets/ghost-830670d910cea64d98ab083aafd1dffd.css +0 -1
- package/core/built/admin/assets/ghost-dark-e9195020925a360d83163e1e87b514a6.css +0 -1
|
@@ -67,11 +67,11 @@ const processImport = async (options) => {
|
|
|
67
67
|
return await membersImporter.process({...options, verificationTrigger});
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
const initVerificationTrigger = () => {
|
|
71
|
+
return new VerificationTrigger({
|
|
72
|
+
getApiTriggerThreshold: () => _.get(config.get('hostSettings'), 'emailVerification.apiThreshold'),
|
|
73
|
+
getAdminTriggerThreshold: () => _.get(config.get('hostSettings'), 'emailVerification.adminThreshold'),
|
|
74
|
+
getImportTriggerThreshold: () => _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
|
|
75
75
|
isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
|
|
76
76
|
isVerificationRequired: () => settingsCache.get('email_verification_required') === true,
|
|
77
77
|
sendVerificationEmail: async ({subject, message, amountTriggered}) => {
|
|
@@ -133,16 +133,8 @@ module.exports = {
|
|
|
133
133
|
getMembersApi: () => module.exports.api
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
(async () => {
|
|
139
|
-
try {
|
|
140
|
-
const collection = await models.SingleUseToken.fetchAll();
|
|
141
|
-
await collection.invokeThen('destroy');
|
|
142
|
-
} catch (err) {
|
|
143
|
-
logging.error(err);
|
|
144
|
-
}
|
|
145
|
-
})();
|
|
136
|
+
verificationTrigger = initVerificationTrigger();
|
|
137
|
+
module.exports.verificationTrigger = verificationTrigger;
|
|
146
138
|
|
|
147
139
|
if (!env?.startsWith('testing')) {
|
|
148
140
|
const membersMigrationJobName = 'members-migrations';
|
|
@@ -159,6 +151,9 @@ module.exports = {
|
|
|
159
151
|
|
|
160
152
|
// Schedule daily cron job to clean expired comp subs
|
|
161
153
|
memberJobs.scheduleExpiredCompCleanupJob();
|
|
154
|
+
|
|
155
|
+
// Schedule daily cron job to clean expired tokens
|
|
156
|
+
memberJobs.scheduleTokenCleanupJob();
|
|
162
157
|
},
|
|
163
158
|
contentGating: require('./content-gating'),
|
|
164
159
|
|
|
@@ -169,16 +164,14 @@ module.exports = {
|
|
|
169
164
|
},
|
|
170
165
|
|
|
171
166
|
ssr: null,
|
|
167
|
+
verificationTrigger: null,
|
|
172
168
|
|
|
173
169
|
stripeConnect: require('./stripe-connect'),
|
|
174
170
|
|
|
175
171
|
processImport: processImport,
|
|
176
172
|
|
|
177
173
|
stats: membersStats,
|
|
178
|
-
export: require('./exporter/query')
|
|
179
|
-
|
|
180
|
-
// Only for tests
|
|
181
|
-
_updateVerificationTrigger: updateVerificationTrigger
|
|
174
|
+
export: require('./exporter/query')
|
|
182
175
|
};
|
|
183
176
|
|
|
184
177
|
module.exports.middleware = require('./middleware');
|
|
@@ -7,12 +7,19 @@ const limitService = require('../limits');
|
|
|
7
7
|
const labs = require('../../../shared/labs');
|
|
8
8
|
|
|
9
9
|
const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
|
|
10
|
+
const MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE = 10 * 60 * 1000;
|
|
11
|
+
const MAGIC_LINK_TOKEN_MAX_USAGE_COUNT = 3;
|
|
10
12
|
|
|
11
13
|
module.exports = new NewslettersService({
|
|
12
14
|
NewsletterModel: models.Newsletter,
|
|
13
15
|
MemberModel: models.Member,
|
|
14
16
|
mail,
|
|
15
|
-
singleUseTokenProvider: new SingleUseTokenProvider(
|
|
17
|
+
singleUseTokenProvider: new SingleUseTokenProvider({
|
|
18
|
+
SingleUseTokenModel: models.SingleUseToken,
|
|
19
|
+
validityPeriod: MAGIC_LINK_TOKEN_VALIDITY,
|
|
20
|
+
validityPeriodAfterUsage: MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE,
|
|
21
|
+
maxUsageCount: MAGIC_LINK_TOKEN_MAX_USAGE_COUNT
|
|
22
|
+
}),
|
|
16
23
|
urlUtils,
|
|
17
24
|
limitService,
|
|
18
25
|
labs
|
|
@@ -17,6 +17,8 @@ const ObjectId = require('bson-objectid').default;
|
|
|
17
17
|
const settingsHelpers = require('../settings-helpers');
|
|
18
18
|
|
|
19
19
|
const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
|
|
20
|
+
const MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE = 10 * 60 * 1000;
|
|
21
|
+
const MAGIC_LINK_TOKEN_MAX_USAGE_COUNT = 3;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* @returns {SettingsBREADService} instance of the PostsService
|
|
@@ -27,7 +29,12 @@ const getSettingsBREADServiceInstance = () => {
|
|
|
27
29
|
settingsCache: SettingsCache,
|
|
28
30
|
labsService: labs,
|
|
29
31
|
mail,
|
|
30
|
-
singleUseTokenProvider: new SingleUseTokenProvider(
|
|
32
|
+
singleUseTokenProvider: new SingleUseTokenProvider({
|
|
33
|
+
SingleUseTokenModel: models.SingleUseToken,
|
|
34
|
+
validityPeriod: MAGIC_LINK_TOKEN_VALIDITY,
|
|
35
|
+
validityPeriodAfterUsage: MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE,
|
|
36
|
+
maxUsageCount: MAGIC_LINK_TOKEN_MAX_USAGE_COUNT
|
|
37
|
+
}),
|
|
31
38
|
urlUtils
|
|
32
39
|
});
|
|
33
40
|
};
|
|
@@ -21,7 +21,7 @@ module.exports.loadAndActivate = async (themeName) => {
|
|
|
21
21
|
const loadedTheme = await themeLoader.loadOneTheme(themeName);
|
|
22
22
|
// Validate
|
|
23
23
|
// @NOTE: this is now the only usage of check, rather than checkSafe...
|
|
24
|
-
const checkedTheme = await validate.check(loadedTheme);
|
|
24
|
+
const checkedTheme = await validate.check(themeName, loadedTheme);
|
|
25
25
|
|
|
26
26
|
if (!validate.canActivate(checkedTheme)) {
|
|
27
27
|
logging.error(validate.getThemeValidationError('activeThemeHasFatalErrors', themeName, checkedTheme));
|
|
@@ -57,6 +57,6 @@ module.exports.activate = async (themeName) => {
|
|
|
57
57
|
const checkedTheme = await validate.checkSafe(themeName, loadedTheme);
|
|
58
58
|
// Activate
|
|
59
59
|
await activator.activateFromAPI(themeName, loadedTheme, checkedTheme);
|
|
60
|
-
// Return the
|
|
61
|
-
return checkedTheme;
|
|
60
|
+
// Return the theme errors
|
|
61
|
+
return validate.getErrorsFromCheckedTheme(checkedTheme);
|
|
62
62
|
};
|
|
@@ -3,7 +3,7 @@ const themeLoader = require('./loader');
|
|
|
3
3
|
const storage = require('./storage');
|
|
4
4
|
const getJSON = require('./to-json');
|
|
5
5
|
const installer = require('./installer');
|
|
6
|
-
|
|
6
|
+
const validate = require('./validate');
|
|
7
7
|
const settingsCache = require('../../../shared/settings-cache');
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
@@ -11,8 +11,9 @@ module.exports = {
|
|
|
11
11
|
* Load the currently active theme
|
|
12
12
|
*/
|
|
13
13
|
init: async () => {
|
|
14
|
-
|
|
14
|
+
validate.init();
|
|
15
15
|
|
|
16
|
+
const themeName = settingsCache.get('active_theme');
|
|
16
17
|
return activate.loadAndActivate(themeName);
|
|
17
18
|
},
|
|
18
19
|
/**
|
|
@@ -25,6 +26,7 @@ module.exports = {
|
|
|
25
26
|
api: {
|
|
26
27
|
getJSON,
|
|
27
28
|
activate: activate.activate,
|
|
29
|
+
getThemeErrors: validate.getThemeErrors,
|
|
28
30
|
getZip: storage.getZip,
|
|
29
31
|
setFromZip: storage.setFromZip,
|
|
30
32
|
installFromGithub: installer.installFromGithub,
|
|
@@ -86,10 +86,12 @@ module.exports = {
|
|
|
86
86
|
await activator.activateFromAPIOverride(themeName, loadedTheme, checkedTheme);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const themeErrors = validate.getErrorsFromCheckedTheme(checkedTheme);
|
|
90
|
+
|
|
89
91
|
// @TODO: unify the name across gscan and Ghost!
|
|
90
92
|
return {
|
|
91
93
|
themeOverridden: overrideTheme,
|
|
92
|
-
theme: toJSON(themeName,
|
|
94
|
+
theme: toJSON(themeName, themeErrors)
|
|
93
95
|
};
|
|
94
96
|
} catch (error) {
|
|
95
97
|
// restore backup if we renamed an existing theme but saving failed
|
|
@@ -13,10 +13,10 @@ const settingsCache = require('../../../shared/settings-cache');
|
|
|
13
13
|
* @TODO: settingsCache.get('active_theme') vs. active.get().name
|
|
14
14
|
*
|
|
15
15
|
* @param {string} [name] - the theme to output
|
|
16
|
-
* @param {
|
|
16
|
+
* @param {{errors: Array, warnings: Array}} [themeErrors] - Error and warning results from checked theme (if available)
|
|
17
17
|
* @return {}
|
|
18
18
|
*/
|
|
19
|
-
module.exports = function toJSON(name,
|
|
19
|
+
module.exports = function toJSON(name, themeErrors) {
|
|
20
20
|
let themeResult;
|
|
21
21
|
let toFilter;
|
|
22
22
|
|
|
@@ -30,12 +30,12 @@ module.exports = function toJSON(name, checkedTheme) {
|
|
|
30
30
|
|
|
31
31
|
themeResult = packageJSON.filter(toFilter, settingsCache.get('active_theme'));
|
|
32
32
|
|
|
33
|
-
if (
|
|
34
|
-
themeResult[0].warnings = _.cloneDeep(
|
|
33
|
+
if (themeErrors && themeErrors.warnings.length) {
|
|
34
|
+
themeResult[0].warnings = _.cloneDeep(themeErrors.warnings);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if (
|
|
38
|
-
themeResult[0].errors = _.cloneDeep(
|
|
37
|
+
if (themeErrors && themeErrors.errors.length) {
|
|
38
|
+
themeResult[0].errors = _.cloneDeep(themeErrors.errors);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -5,20 +5,46 @@ const config = require('../../../shared/config');
|
|
|
5
5
|
const labs = require('../../../shared/labs');
|
|
6
6
|
const tpl = require('@tryghost/tpl');
|
|
7
7
|
const errors = require('@tryghost/errors');
|
|
8
|
+
const adapterManager = require('../adapter-manager');
|
|
9
|
+
const logging = require('@tryghost/logging');
|
|
10
|
+
const list = require('./list');
|
|
8
11
|
|
|
9
12
|
const messages = {
|
|
10
13
|
themeHasErrors: 'Theme "{theme}" is not compatible or contains errors.',
|
|
11
14
|
activeThemeHasFatalErrors: 'The currently active theme "{theme}" has fatal errors.',
|
|
12
|
-
activeThemeHasErrors: 'The currently active theme "{theme}" has errors, but will still work.'
|
|
15
|
+
activeThemeHasErrors: 'The currently active theme "{theme}" has errors, but will still work.',
|
|
16
|
+
themeNotLoaded: 'Theme "{themeName}" is not loaded and cannot be checked.'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} CacheStore
|
|
21
|
+
* @property {(key: string) => Promise<any>} get - get a value from the cache. Returns undefined if not found
|
|
22
|
+
* @property {(key: string, value: any) => Promise<void>} set - set a value in the cache
|
|
23
|
+
* @property {() => Promise<void>} reset - reset the cache
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The cache store for storing the result of the last theme validation
|
|
28
|
+
* @type {CacheStore}
|
|
29
|
+
*/
|
|
30
|
+
let gscanCacheStore;
|
|
31
|
+
|
|
32
|
+
module.exports.init = () => {
|
|
33
|
+
gscanCacheStore = adapterManager.getAdapter('cache:gscan');
|
|
13
34
|
};
|
|
14
35
|
|
|
15
36
|
const canActivate = function canActivate(checkedTheme) {
|
|
16
|
-
|
|
17
|
-
// CASE: development returns fatal and none fatal errors, theme is only invalid if fatal errors
|
|
18
|
-
return !checkedTheme.results.error.length || (config.get('env') === 'development') && !checkedTheme.results.hasFatalErrors;
|
|
37
|
+
return !checkedTheme.results.hasFatalErrors;
|
|
19
38
|
};
|
|
20
39
|
|
|
21
|
-
const
|
|
40
|
+
const getErrorsFromCheckedTheme = function getErrorsFromCheckedTheme(checkedTheme) {
|
|
41
|
+
return {
|
|
42
|
+
errors: checkedTheme.results.error ?? [],
|
|
43
|
+
warnings: checkedTheme.results.warning ?? []
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const check = async function check(themeName, theme, isZip) {
|
|
22
48
|
debug('Begin: Check');
|
|
23
49
|
// gscan can slow down boot time if we require on boot, for now nest the require.
|
|
24
50
|
const gscan = require('gscan');
|
|
@@ -41,16 +67,59 @@ const check = async function check(theme, isZip) {
|
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
checkedTheme = gscan.format(checkedTheme, {
|
|
44
|
-
onlyFatalErrors:
|
|
70
|
+
onlyFatalErrors: false,
|
|
45
71
|
checkVersion: checkedVersion
|
|
46
72
|
});
|
|
47
73
|
|
|
74
|
+
// In production we don't want to show warnings
|
|
75
|
+
// Warnings are meant for developers only
|
|
76
|
+
if (config.get('env') === 'production') {
|
|
77
|
+
checkedTheme.results.warning = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Cache warnings and errors
|
|
81
|
+
try {
|
|
82
|
+
await gscanCacheStore.set(themeName, getErrorsFromCheckedTheme(checkedTheme));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
logging.error('Failed to cache gscan result');
|
|
85
|
+
logging.error(err);
|
|
86
|
+
}
|
|
87
|
+
|
|
48
88
|
debug('End: Check');
|
|
49
89
|
return checkedTheme;
|
|
50
90
|
};
|
|
51
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Returns the last cached errors and warnings of check() if available.
|
|
94
|
+
* Otherwise runs check() on the loaded theme with that name (which will always cache the error and warning results)
|
|
95
|
+
* @returns {Promise<{errors: Array, warnings: Array}>}
|
|
96
|
+
*/
|
|
97
|
+
const getThemeErrors = async function getThemeErrors(themeName) {
|
|
98
|
+
try {
|
|
99
|
+
const cachedThemeErrors = await gscanCacheStore.get(themeName);
|
|
100
|
+
if (cachedThemeErrors) {
|
|
101
|
+
return cachedThemeErrors;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
logging.error('Failed to get gscan result from cache');
|
|
105
|
+
logging.error(err);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const loadedTheme = list.get(themeName);
|
|
109
|
+
|
|
110
|
+
if (!loadedTheme) {
|
|
111
|
+
throw new errors.ValidationError({
|
|
112
|
+
message: tpl(messages.themeNotLoaded, {themeName: themeName}),
|
|
113
|
+
errorDetails: themeName
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = await check(themeName, loadedTheme);
|
|
118
|
+
return getErrorsFromCheckedTheme(result);
|
|
119
|
+
};
|
|
120
|
+
|
|
52
121
|
const checkSafe = async function checkSafe(themeName, theme, isZip) {
|
|
53
|
-
const checkedTheme = await check(theme, isZip);
|
|
122
|
+
const checkedTheme = await check(themeName, theme, isZip);
|
|
54
123
|
|
|
55
124
|
if (canActivate(checkedTheme)) {
|
|
56
125
|
return checkedTheme;
|
|
@@ -74,7 +143,8 @@ const getThemeValidationError = (message, themeName, checkedTheme) => {
|
|
|
74
143
|
message: tpl(messages[message], {theme: themeName}),
|
|
75
144
|
errorDetails: Object.assign(
|
|
76
145
|
_.pick(checkedTheme, ['checkedVersion', 'name', 'path', 'version']), {
|
|
77
|
-
errors: checkedTheme.results.error
|
|
146
|
+
errors: checkedTheme.results.error,
|
|
147
|
+
warnings: checkedTheme.results.warning
|
|
78
148
|
}
|
|
79
149
|
)
|
|
80
150
|
});
|
|
@@ -83,4 +153,6 @@ const getThemeValidationError = (message, themeName, checkedTheme) => {
|
|
|
83
153
|
module.exports.check = check;
|
|
84
154
|
module.exports.checkSafe = checkSafe;
|
|
85
155
|
module.exports.canActivate = canActivate;
|
|
156
|
+
module.exports.getErrorsFromCheckedTheme = getErrorsFromCheckedTheme;
|
|
86
157
|
module.exports.getThemeValidationError = getThemeValidationError;
|
|
158
|
+
module.exports.getThemeErrors = getThemeErrors;
|
|
@@ -159,6 +159,11 @@ module.exports = function apiRoutes() {
|
|
|
159
159
|
http(api.themes.download)
|
|
160
160
|
);
|
|
161
161
|
|
|
162
|
+
router.get('/themes/active',
|
|
163
|
+
mw.authAdminApi,
|
|
164
|
+
http(api.themes.readActive)
|
|
165
|
+
);
|
|
166
|
+
|
|
162
167
|
router.post('/themes/upload',
|
|
163
168
|
mw.authAdminApi,
|
|
164
169
|
apiMw.upload.single('file'),
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"cache": {
|
|
28
28
|
"active": "Memory",
|
|
29
29
|
"settings": {},
|
|
30
|
-
"imageSizes": {}
|
|
30
|
+
"imageSizes": {},
|
|
31
|
+
"gscan": {}
|
|
31
32
|
}
|
|
32
33
|
},
|
|
33
34
|
"storage": {
|
|
@@ -169,7 +170,7 @@
|
|
|
169
170
|
},
|
|
170
171
|
"portal": {
|
|
171
172
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
172
|
-
"version": "2.
|
|
173
|
+
"version": "2.23"
|
|
173
174
|
},
|
|
174
175
|
"sodoSearch": {
|
|
175
176
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
package/core/shared/labs.js
CHANGED