ghost 4.15.0 → 4.17.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.
Files changed (137) hide show
  1. package/.eslintrc.js +7 -1
  2. package/content/themes/casper/assets/built/screen.css +1 -1
  3. package/content/themes/casper/assets/built/screen.css.map +1 -1
  4. package/content/themes/casper/assets/css/screen.css +1 -1
  5. package/content/themes/casper/default.hbs +2 -2
  6. package/content/themes/casper/package.json +1 -1
  7. package/content/themes/casper/page.hbs +28 -26
  8. package/content/themes/casper/partials/post-card.hbs +2 -2
  9. package/content/themes/casper/post.hbs +67 -65
  10. package/content/themes/casper/tag.hbs +2 -2
  11. package/core/boot.js +7 -7
  12. package/core/bridge.js +4 -3
  13. package/core/built/assets/{chunk.3.4b1d9e20e57164ac9c29.js → chunk.3.b80d3e1e6b8556aaff3c.js} +72 -71
  14. package/core/built/assets/ghost-dark-f7bf2dd8d8c702716f75bfa4ccd92df2.css +1 -0
  15. package/core/built/assets/{ghost.min-e35cfee26d942c364166f57f3dcc9e75.js → ghost.min-52a5420ffcea6bf17761b5c59cf020e2.js} +979 -908
  16. package/core/built/assets/ghost.min-741246f42f000c073999a5363434ea2c.css +1 -0
  17. package/core/built/assets/icons/discount-bubble.svg +1 -0
  18. package/core/built/assets/{vendor.min-ca33abc718f21a51327841d58f8875d0.js → vendor.min-1bfc9d56d27508db88ef417deb55f16f.js} +454 -434
  19. package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +2 -2
  20. package/core/frontend/apps/amp/lib/helpers/amp_components.js +2 -1
  21. package/core/frontend/apps/amp/lib/helpers/amp_content.js +5 -1
  22. package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
  23. package/core/frontend/apps/amp/lib/router.js +8 -4
  24. package/core/frontend/apps/private-blogging/index.js +13 -5
  25. package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
  26. package/core/frontend/apps/private-blogging/lib/middleware.js +8 -3
  27. package/core/frontend/helpers/asset.js +10 -2
  28. package/core/frontend/helpers/author.js +5 -3
  29. package/core/frontend/helpers/authors.js +4 -3
  30. package/core/frontend/helpers/body_class.js +1 -1
  31. package/core/frontend/helpers/cancel_link.js +9 -2
  32. package/core/frontend/helpers/concat.js +1 -1
  33. package/core/frontend/helpers/content.js +1 -1
  34. package/core/frontend/helpers/date.js +1 -1
  35. package/core/frontend/helpers/encode.js +1 -1
  36. package/core/frontend/helpers/excerpt.js +2 -1
  37. package/core/frontend/helpers/facebook_url.js +2 -1
  38. package/core/frontend/helpers/foreach.js +11 -2
  39. package/core/frontend/helpers/get.js +14 -3
  40. package/core/frontend/helpers/ghost_foot.js +2 -1
  41. package/core/frontend/helpers/ghost_head.js +10 -1
  42. package/core/frontend/helpers/has.js +8 -3
  43. package/core/frontend/helpers/img_url.js +9 -3
  44. package/core/frontend/helpers/is.js +7 -2
  45. package/core/frontend/helpers/lang.js +1 -1
  46. package/core/frontend/helpers/link.js +11 -2
  47. package/core/frontend/helpers/link_class.js +11 -2
  48. package/core/frontend/helpers/match.js +12 -3
  49. package/core/frontend/helpers/navigation.js +13 -4
  50. package/core/frontend/helpers/pagination.js +15 -5
  51. package/core/frontend/helpers/plural.js +8 -2
  52. package/core/frontend/helpers/post_class.js +1 -1
  53. package/core/frontend/helpers/prev_post.js +9 -2
  54. package/core/frontend/helpers/price.js +11 -6
  55. package/core/frontend/helpers/products.js +2 -1
  56. package/core/frontend/helpers/reading_time.js +4 -2
  57. package/core/frontend/helpers/t.js +1 -1
  58. package/core/frontend/helpers/tags.js +3 -1
  59. package/core/frontend/helpers/title.js +1 -1
  60. package/core/frontend/helpers/twitter_url.js +2 -1
  61. package/core/frontend/helpers/url.js +3 -1
  62. package/core/frontend/services/proxy.js +34 -57
  63. package/core/frontend/services/rendering.js +24 -0
  64. package/core/frontend/services/routing/controllers/channel.js +6 -2
  65. package/core/frontend/services/routing/controllers/collection.js +6 -2
  66. package/core/frontend/services/routing/middlewares/page-param.js +6 -2
  67. package/core/frontend/services/theme-engine/middleware.js +23 -6
  68. package/core/frontend/services/theme-engine/preview.js +31 -8
  69. package/core/server/adapters/scheduling/post-scheduling/scheduler-intergation.js +6 -4
  70. package/core/server/adapters/storage/LocalFileStorage.js +10 -4
  71. package/core/server/api/canary/custom-theme-settings.js +22 -0
  72. package/core/server/api/canary/index.js +4 -0
  73. package/core/server/api/canary/members.js +1 -1
  74. package/core/server/api/canary/redirects.js +5 -5
  75. package/core/server/api/canary/settings.js +16 -148
  76. package/core/server/api/canary/utils/serializers/output/custom-theme-settings.js +13 -0
  77. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  78. package/core/server/api/canary/utils/validators/input/settings.js +23 -1
  79. package/core/server/api/v2/redirects.js +3 -3
  80. package/core/server/api/v2/settings.js +3 -4
  81. package/core/server/api/v3/redirects.js +5 -5
  82. package/core/server/api/v3/settings.js +16 -136
  83. package/core/server/api/v3/utils/validators/input/settings.js +23 -1
  84. package/core/server/data/db/state-manager.js +1 -1
  85. package/core/server/data/exporter/table-lists.js +3 -1
  86. package/core/server/data/importer/import-manager.js +398 -0
  87. package/core/server/data/importer/importers/data/data-importer.js +162 -0
  88. package/core/server/data/importer/importers/data/index.js +1 -162
  89. package/core/server/data/importer/index.js +1 -379
  90. package/core/server/data/migrations/versions/4.16/01-add-custom-theme-settings-table.js +9 -0
  91. package/core/server/data/migrations/versions/4.17/01-add-custom-theme-settings-permissions.js +21 -0
  92. package/core/server/data/migrations/versions/4.17/02-add-offers-table.js +19 -0
  93. package/core/server/data/migrations/versions/4.17/03-add-offers-permissions.js +35 -0
  94. package/core/server/data/schema/fixtures/fixtures.json +32 -0
  95. package/core/server/data/schema/schema.js +33 -0
  96. package/core/server/models/custom-theme-setting.js +9 -0
  97. package/core/server/models/index.js +2 -0
  98. package/core/server/services/custom-theme-settings.js +8 -0
  99. package/core/server/services/members/api.js +4 -1
  100. package/core/server/services/redirects/index.js +15 -0
  101. package/core/{frontend → server}/services/redirects/settings.js +13 -6
  102. package/core/server/services/redirects/validation.js +44 -0
  103. package/core/{frontend/services/settings → server/services/route-settings}/default-routes.yaml +0 -0
  104. package/core/server/services/route-settings/default-settings-manager.js +62 -0
  105. package/core/server/services/route-settings/index.js +32 -1
  106. package/core/server/services/route-settings/route-settings.js +38 -12
  107. package/core/server/services/route-settings/settings-loader.js +102 -0
  108. package/core/{frontend/services/settings → server/services/route-settings}/validate.js +38 -28
  109. package/core/server/services/route-settings/yaml-parser.js +53 -0
  110. package/core/server/services/settings/index.js +13 -16
  111. package/core/server/services/settings/settings-bread-service.js +188 -0
  112. package/core/server/services/settings/settings-utils.js +32 -0
  113. package/core/server/services/themes/ThemeStorage.js +5 -4
  114. package/core/server/services/themes/activation-bridge.js +14 -0
  115. package/core/server/services/themes/validate.js +5 -2
  116. package/core/server/web/admin/views/default-prod.html +4 -4
  117. package/core/server/web/admin/views/default.html +4 -4
  118. package/core/server/web/api/canary/admin/routes.js +5 -1
  119. package/core/server/web/members/app.js +3 -0
  120. package/core/server/web/oauth/app.js +7 -8
  121. package/core/server/web/shared/middlewares/custom-redirects.js +82 -59
  122. package/core/server/web/site/routes.js +2 -2
  123. package/core/shared/config/defaults.json +2 -2
  124. package/core/shared/config/overrides.json +1 -1
  125. package/core/shared/custom-theme-settings-cache.js +3 -0
  126. package/core/shared/i18n/translations/en.json +2 -13
  127. package/core/shared/labs.js +2 -2
  128. package/package.json +42 -41
  129. package/yarn.lock +916 -901
  130. package/core/built/assets/ghost-dark-faf931d90e92535e6c03ca16793cbe7b.css +0 -1
  131. package/core/built/assets/ghost.min-7aa074ad556a8455155ac88ceaca03ab.css +0 -1
  132. package/core/frontend/services/redirects/index.js +0 -9
  133. package/core/frontend/services/redirects/validation.js +0 -28
  134. package/core/frontend/services/settings/ensure-settings.js +0 -47
  135. package/core/frontend/services/settings/index.js +0 -104
  136. package/core/frontend/services/settings/loader.js +0 -89
  137. package/core/frontend/services/settings/yaml-parser.js +0 -31
@@ -1,7 +1,11 @@
1
1
  const models = require('../../../models');
2
- const i18n = require('../../../../shared/i18n');
2
+ const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
4
 
5
+ const messages = {
6
+ resourceNotFound: '{resource} not found.'
7
+ };
8
+
5
9
  /**
6
10
  * @description Load the internal scheduler integration
7
11
  *
@@ -12,9 +16,7 @@ const getSchedulerIntegration = function () {
12
16
  .then((integration) => {
13
17
  if (!integration) {
14
18
  throw new errors.NotFoundError({
15
- message: i18n.t('errors.api.resource.resourceNotFound', {
16
- resource: 'Integration'
17
- })
19
+ message: tpl(messages.resourceNotFound, {resource: 'Integration'})
18
20
  });
19
21
  }
20
22
  return integration.toJSON();
@@ -7,13 +7,19 @@ const path = require('path');
7
7
  const Promise = require('bluebird');
8
8
  const moment = require('moment');
9
9
  const config = require('../../../shared/config');
10
- const i18n = require('../../../shared/i18n');
10
+ const tpl = require('@tryghost/tpl');
11
11
  const logging = require('@tryghost/logging');
12
12
  const errors = require('@tryghost/errors');
13
13
  const constants = require('@tryghost/constants');
14
14
  const urlUtils = require('../../../shared/url-utils');
15
15
  const StorageBase = require('ghost-storage-base');
16
16
 
17
+ const messages = {
18
+ imageNotFound: 'Image not found',
19
+ imageNotFoundWithRef: 'Image not found: {img}',
20
+ cannotReadImage: 'Could not read image: {img}'
21
+ };
22
+
17
23
  class LocalFileStore extends StorageBase {
18
24
  constructor() {
19
25
  super();
@@ -119,7 +125,7 @@ class LocalFileStore extends StorageBase {
119
125
  if (err) {
120
126
  if (err.statusCode === 404) {
121
127
  return next(new errors.NotFoundError({
122
- message: i18n.t('errors.errors.imageNotFound'),
128
+ message: tpl(messages.imageNotFound),
123
129
  code: 'STATIC_FILE_NOT_FOUND',
124
130
  property: err.path
125
131
  }));
@@ -169,7 +175,7 @@ class LocalFileStore extends StorageBase {
169
175
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
170
176
  return reject(new errors.NotFoundError({
171
177
  err: err,
172
- message: i18n.t('errors.errors.imageNotFoundWithRef', {img: options.path})
178
+ message: tpl(messages.imageNotFoundWithRef, {img: options.path})
173
179
  }));
174
180
  }
175
181
 
@@ -183,7 +189,7 @@ class LocalFileStore extends StorageBase {
183
189
 
184
190
  return reject(new errors.GhostError({
185
191
  err: err,
186
- message: i18n.t('errors.errors.cannotReadImage', {img: options.path})
192
+ message: tpl(messages.cannotReadImage, {img: options.path})
187
193
  }));
188
194
  }
189
195
 
@@ -0,0 +1,22 @@
1
+ const customThemeSettingsService = require('../../services/custom-theme-settings');
2
+
3
+ module.exports = {
4
+ docName: 'custom_theme_settings',
5
+
6
+ browse: {
7
+ permissions: true,
8
+ query() {
9
+ return customThemeSettingsService.listSettings();
10
+ }
11
+ },
12
+
13
+ edit: {
14
+ headers: {
15
+ cacheInvalidate: true
16
+ },
17
+ permissions: true,
18
+ query(frame) {
19
+ return customThemeSettingsService.updateSettings(frame.data.custom_theme_settings);
20
+ }
21
+ }
22
+ };
@@ -153,6 +153,10 @@ module.exports = {
153
153
  return shared.pipeline(require('./snippets'), localUtils);
154
154
  },
155
155
 
156
+ get customThemeSettings() {
157
+ return shared.pipeline(require('./custom-theme-settings'), localUtils);
158
+ },
159
+
156
160
  get serializers() {
157
161
  return require('./utils/serializers');
158
162
  },
@@ -411,7 +411,7 @@ module.exports = {
411
411
  method: 'edit'
412
412
  },
413
413
  async query(frame) {
414
- return membersService.api.members.bulkEdit(frame.data, frame.options);
414
+ return membersService.api.members.bulkEdit(frame.data.bulk, frame.options);
415
415
  }
416
416
  },
417
417
 
@@ -1,7 +1,7 @@
1
1
  const path = require('path');
2
2
 
3
3
  const web = require('../../web');
4
- const redirects = require('../../../frontend/services/redirects');
4
+ const redirects = require('../../services/redirects');
5
5
 
6
6
  module.exports = {
7
7
  docName: 'redirects',
@@ -11,7 +11,7 @@ module.exports = {
11
11
  disposition: {
12
12
  type: 'file',
13
13
  value() {
14
- return redirects.settings.getRedirectsFilePath()
14
+ return redirects.api.getRedirectsFilePath()
15
15
  .then((filePath) => {
16
16
  // TODO: Default file type is .json for backward compatibility.
17
17
  // When .yaml becomes default or .json is removed at v4,
@@ -26,13 +26,13 @@ module.exports = {
26
26
  permissions: true,
27
27
  response: {
28
28
  async format() {
29
- const filePath = await redirects.settings.getRedirectsFilePath();
29
+ const filePath = await redirects.api.getRedirectsFilePath();
30
30
 
31
31
  return filePath === null || path.extname(filePath) === '.json' ? 'json' : 'plain';
32
32
  }
33
33
  },
34
34
  query() {
35
- return redirects.settings.get();
35
+ return redirects.api.get();
36
36
  }
37
37
  },
38
38
 
@@ -42,7 +42,7 @@ module.exports = {
42
42
  cacheInvalidate: true
43
43
  },
44
44
  query(frame) {
45
- return redirects.settings.setFromFilePath(frame.file.path, frame.file.ext)
45
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
46
  .then(() => {
47
47
  // CASE: trigger that redirects are getting re-registered
48
48
  web.shared.middlewares.customRedirects.reload();
@@ -1,15 +1,14 @@
1
1
  const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
- const validator = require('@tryghost/validator');
4
3
  const models = require('../../models');
5
4
  const routeSettings = require('../../services/route-settings');
6
- const frontendSettings = require('../../../frontend/services/settings');
7
5
  const i18n = require('../../../shared/i18n');
8
- const {BadRequestError, NoPermissionError, NotFoundError} = require('@tryghost/errors');
6
+ const {BadRequestError} = require('@tryghost/errors');
9
7
  const settingsService = require('../../services/settings');
10
- const settingsCache = require('../../../shared/settings-cache');
11
8
  const membersService = require('../../services/members');
12
9
 
10
+ const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
11
+
13
12
  module.exports = {
14
13
  docName: 'settings',
15
14
 
@@ -17,26 +16,7 @@ module.exports = {
17
16
  options: ['group'],
18
17
  permissions: true,
19
18
  query(frame) {
20
- let settings = settingsCache.getAll();
21
-
22
- // CASE: no context passed (functional call)
23
- if (!frame.options.context) {
24
- return Promise.resolve(settings.filter((setting) => {
25
- return setting.group === 'site';
26
- }));
27
- }
28
-
29
- if (!frame.options.context.internal) {
30
- // CASE: omit core settings unless internal request
31
- settings = _.filter(settings, (setting) => {
32
- const isCore = setting.group === 'core';
33
- return !isCore;
34
- });
35
- // CASE: omit secret settings unless internal request
36
- settings = settings.map(settingsService.hideValueIfSecret);
37
- }
38
-
39
- return settings;
19
+ return settingsBREADService.browse(frame.options.context);
40
20
  }
41
21
  },
42
22
 
@@ -55,41 +35,7 @@ module.exports = {
55
35
  }
56
36
  },
57
37
  query(frame) {
58
- let setting;
59
- if (frame.options.key === 'slack') {
60
- const slackURL = settingsCache.get('slack_url', {resolve: false});
61
- const slackUsername = settingsCache.get('slack_username', {resolve: false});
62
-
63
- setting = slackURL || slackUsername;
64
- setting.key = 'slack';
65
- setting.value = [{
66
- url: slackURL && slackURL.value,
67
- username: slackUsername && slackUsername.value
68
- }];
69
- } else {
70
- setting = settingsCache.get(frame.options.key, {resolve: false});
71
- }
72
-
73
- if (!setting) {
74
- return Promise.reject(new NotFoundError({
75
- message: i18n.t('errors.api.settings.problemFindingSetting', {
76
- key: frame.options.key
77
- })
78
- }));
79
- }
80
-
81
- // @TODO: handle in settings model permissible fn
82
- if (setting.group === 'core' && !(frame.options.context && frame.options.context.internal)) {
83
- return Promise.reject(new NoPermissionError({
84
- message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')
85
- }));
86
- }
87
-
88
- setting = settingsService.hideValueIfSecret(setting);
89
-
90
- return {
91
- [frame.options.key]: setting
92
- };
38
+ return settingsBREADService.read(frame.options.key, frame.options.context);
93
39
  }
94
40
  },
95
41
 
@@ -153,17 +99,7 @@ module.exports = {
153
99
  ],
154
100
  async query(frame) {
155
101
  const {email, type} = frame.data;
156
- if (typeof email !== 'string' || !validator.isEmail(email)) {
157
- throw new BadRequestError({
158
- message: i18n.t('errors.api.settings.invalidEmailReceived')
159
- });
160
- }
161
102
 
162
- if (!type || !['fromAddressUpdate', 'supportAddressUpdate'].includes(type)) {
163
- throw new BadRequestError({
164
- message: 'Invalid email type recieved'
165
- });
166
- }
167
103
  try {
168
104
  // Send magic link to update fromAddress
169
105
  await membersService.settings.sendEmailAddressUpdateMagicLink({
@@ -225,91 +161,23 @@ module.exports = {
225
161
  permissions: {
226
162
  unsafeAttrsObject(frame) {
227
163
  return _.find(frame.data.settings, {key: 'labs'});
228
- },
229
- async before(frame) {
230
- if (frame.options.context && frame.options.context.internal) {
231
- return;
232
- }
233
-
234
- const firstCoreSetting = frame.data.settings.find(setting => setting.group === 'core');
235
- if (firstCoreSetting) {
236
- throw new NoPermissionError({
237
- message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')
238
- });
239
- }
240
164
  }
241
165
  },
242
166
  async query(frame) {
167
+ let stripeConnectData;
243
168
  const stripeConnectIntegrationToken = frame.data.settings.find(setting => setting.key === 'stripe_connect_integration_token');
244
169
 
245
- const settings = frame.data.settings.filter((setting) => {
246
- // The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
247
- return ![
248
- 'stripe_connect_integration_token',
249
- 'stripe_connect_publishable_key',
250
- 'stripe_connect_secret_key',
251
- 'stripe_connect_livemode',
252
- 'stripe_connect_account_id',
253
- 'stripe_connect_display_name'
254
- ].includes(setting.key)
255
- // Remove obfuscated settings
256
- && !(setting.value === settingsService.obfuscatedSetting && settingsService.isSecretSetting(setting));
257
- });
258
-
259
- const getSetting = setting => settingsCache.get(setting.key, {resolve: false});
260
-
261
- const firstUnknownSetting = settings.find(setting => !getSetting(setting));
262
-
263
- if (firstUnknownSetting) {
264
- throw new NotFoundError({
265
- message: i18n.t('errors.api.settings.problemFindingSetting', {
266
- key: firstUnknownSetting.key
267
- })
268
- });
269
- }
270
-
271
- if (!(frame.options.context && frame.options.context.internal)) {
272
- const firstCoreSetting = settings.find(setting => getSetting(setting).group === 'core');
273
- if (firstCoreSetting) {
274
- throw new NoPermissionError({
275
- message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')
276
- });
277
- }
278
- }
279
-
280
170
  if (stripeConnectIntegrationToken && stripeConnectIntegrationToken.value) {
281
171
  const getSessionProp = prop => frame.original.session[prop];
282
- try {
283
- const data = await membersService.stripeConnect.getStripeConnectTokenData(stripeConnectIntegrationToken.value, getSessionProp);
284
- settings.push({
285
- key: 'stripe_connect_publishable_key',
286
- value: data.public_key
287
- });
288
- settings.push({
289
- key: 'stripe_connect_secret_key',
290
- value: data.secret_key
291
- });
292
- settings.push({
293
- key: 'stripe_connect_livemode',
294
- value: data.livemode
295
- });
296
- settings.push({
297
- key: 'stripe_connect_display_name',
298
- value: data.display_name
299
- });
300
- settings.push({
301
- key: 'stripe_connect_account_id',
302
- value: data.account_id
303
- });
304
- } catch (err) {
305
- throw new BadRequestError({
306
- err,
307
- message: 'The Stripe Connect token could not be parsed.'
308
- });
309
- }
172
+
173
+ stripeConnectData = await settingsBREADService.getStripeConnectData(
174
+ stripeConnectIntegrationToken,
175
+ getSessionProp,
176
+ membersService.stripeConnect.getStripeConnectTokenData
177
+ );
310
178
  }
311
179
 
312
- return models.Settings.edit(settings, frame.options);
180
+ return await settingsBREADService.edit(frame.data.settings, frame.options, stripeConnectData);
313
181
  }
314
182
  },
315
183
 
@@ -321,8 +189,8 @@ module.exports = {
321
189
  method: 'edit'
322
190
  },
323
191
  async query(frame) {
324
- await routeSettings.setFromFilePath(frame.file.path);
325
- const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
192
+ await routeSettings.api.setFromFilePath(frame.file.path);
193
+ const getRoutesHash = () => routeSettings.api.getCurrentHash();
326
194
  await settingsService.syncRoutesHash(getRoutesHash);
327
195
  }
328
196
  },
@@ -341,7 +209,7 @@ module.exports = {
341
209
  method: 'browse'
342
210
  },
343
211
  query() {
344
- return routeSettings.get();
212
+ return routeSettings.api.get();
345
213
  }
346
214
  }
347
215
  };
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ browse(models, apiConfig, frame) {
3
+ frame.response = {
4
+ customThemeSettings: models
5
+ };
6
+ },
7
+
8
+ edit(models, apiConfig, frame) {
9
+ frame.response = {
10
+ customThemeSettings: models
11
+ };
12
+ }
13
+ };
@@ -139,5 +139,9 @@ module.exports = {
139
139
 
140
140
  get snippets() {
141
141
  return require('./snippets');
142
+ },
143
+
144
+ get custom_theme_settings() {
145
+ return require('./custom-theme-settings');
142
146
  }
143
147
  };
@@ -1,7 +1,13 @@
1
1
  const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
3
  const i18n = require('../../../../../../shared/i18n');
4
- const {NotFoundError, ValidationError} = require('@tryghost/errors');
4
+ const {NotFoundError, ValidationError, BadRequestError} = require('@tryghost/errors');
5
+ const validator = require('@tryghost/validator');
6
+
7
+ const messages = {
8
+ invalidEmailReceived: 'Please send a valid email',
9
+ invalidEmailTypeReceived: 'Invalid email type received'
10
+ };
5
11
 
6
12
  module.exports = {
7
13
  read(apiConfig, frame) {
@@ -62,5 +68,21 @@ module.exports = {
62
68
  if (errors.length) {
63
69
  return Promise.reject(errors[0]);
64
70
  }
71
+ },
72
+
73
+ updateMembersEmail(apiConfig, frame) {
74
+ const {email, type} = frame.data;
75
+
76
+ if (typeof email !== 'string' || !validator.isEmail(email)) {
77
+ throw new BadRequestError({
78
+ message: messages.invalidEmailReceived
79
+ });
80
+ }
81
+
82
+ if (!type || !['fromAddressUpdate', 'supportAddressUpdate'].includes(type)) {
83
+ throw new BadRequestError({
84
+ message: messages.invalidEmailTypeReceived
85
+ });
86
+ }
65
87
  }
66
88
  };
@@ -1,5 +1,5 @@
1
1
  const web = require('../../web');
2
- const redirects = require('../../../frontend/services/redirects');
2
+ const redirects = require('../../services/redirects');
3
3
 
4
4
  module.exports = {
5
5
  docName: 'redirects',
@@ -13,7 +13,7 @@ module.exports = {
13
13
  },
14
14
  permissions: true,
15
15
  query() {
16
- return redirects.settings.get();
16
+ return redirects.api.get();
17
17
  }
18
18
  },
19
19
 
@@ -23,7 +23,7 @@ module.exports = {
23
23
  cacheInvalidate: true
24
24
  },
25
25
  query(frame) {
26
- return redirects.settings.setFromFilePath(frame.file.path)
26
+ return redirects.api.setFromFilePath(frame.file.path)
27
27
  .then(() => {
28
28
  // CASE: trigger that redirects are getting re-registered
29
29
  web.shared.middlewares.customRedirects.reload();
@@ -2,7 +2,6 @@ const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
3
  const models = require('../../models');
4
4
  const routeSettings = require('../../services/route-settings');
5
- const frontendSettings = require('../../../frontend/services/settings');
6
5
  const i18n = require('../../../shared/i18n');
7
6
  const {NoPermissionError, NotFoundError} = require('@tryghost/errors');
8
7
  const settingsService = require('../../services/settings');
@@ -168,8 +167,8 @@ module.exports = {
168
167
  method: 'edit'
169
168
  },
170
169
  async query(frame) {
171
- await routeSettings.setFromFilePath(frame.file.path);
172
- const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
170
+ await routeSettings.api.setFromFilePath(frame.file.path);
171
+ const getRoutesHash = () => routeSettings.api.getCurrentHash();
173
172
  await settingsService.syncRoutesHash(getRoutesHash);
174
173
  }
175
174
  },
@@ -188,7 +187,7 @@ module.exports = {
188
187
  method: 'browse'
189
188
  },
190
189
  query() {
191
- return routeSettings.get();
190
+ return routeSettings.api.get();
192
191
  }
193
192
  }
194
193
  };
@@ -1,7 +1,7 @@
1
1
  const path = require('path');
2
2
 
3
3
  const web = require('../../web');
4
- const redirects = require('../../../frontend/services/redirects');
4
+ const redirects = require('../../services/redirects');
5
5
 
6
6
  module.exports = {
7
7
  docName: 'redirects',
@@ -11,7 +11,7 @@ module.exports = {
11
11
  disposition: {
12
12
  type: 'file',
13
13
  value() {
14
- return redirects.settings.getRedirectsFilePath()
14
+ return redirects.api.getRedirectsFilePath()
15
15
  .then((filePath) => {
16
16
  // TODO: Default file type is .json for backward compatibility.
17
17
  // When .yaml becomes default or .json is removed at v4,
@@ -26,13 +26,13 @@ module.exports = {
26
26
  permissions: true,
27
27
  response: {
28
28
  async format() {
29
- const filePath = await redirects.settings.getRedirectsFilePath();
29
+ const filePath = await redirects.api.getRedirectsFilePath();
30
30
 
31
31
  return filePath === null || path.extname(filePath) === '.json' ? 'json' : 'plain';
32
32
  }
33
33
  },
34
34
  query() {
35
- return redirects.settings.get();
35
+ return redirects.api.get();
36
36
  }
37
37
  },
38
38
 
@@ -42,7 +42,7 @@ module.exports = {
42
42
  cacheInvalidate: true
43
43
  },
44
44
  query(frame) {
45
- return redirects.settings.setFromFilePath(frame.file.path, frame.file.ext)
45
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
46
  .then(() => {
47
47
  // CASE: trigger that redirects are getting re-registered
48
48
  web.shared.middlewares.customRedirects.reload();