ghost 4.21.0 → 4.22.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.eslintrc.js +6 -0
  2. package/Gruntfile.js +2 -0
  3. package/content/themes/casper/assets/built/screen.css +1 -1
  4. package/content/themes/casper/assets/built/screen.css.map +1 -1
  5. package/content/themes/casper/assets/css/screen.css +263 -50
  6. package/content/themes/casper/default.hbs +12 -3
  7. package/content/themes/casper/index.hbs +25 -23
  8. package/content/themes/casper/package.json +91 -2
  9. package/content/themes/casper/partials/post-card.hbs +1 -1
  10. package/content/themes/casper/post.hbs +18 -14
  11. package/content/themes/casper/yarn.lock +245 -192
  12. package/core/boot.js +8 -0
  13. package/core/bridge.js +14 -0
  14. package/core/built/assets/{chunk.3.065ee3c3bdf674bd81a4.js → chunk.3.324fd0cc598c73650219.js} +59 -59
  15. package/core/built/assets/{ghost-dark-1328db4a7dd128305646305a8731bcfe.css → ghost-dark-39fb496d051565531062d7e047d1c0b1.css} +1 -1
  16. package/core/built/assets/{ghost.min-5abc69c04ad1d5301a857e01009b9c05.css → ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css} +1 -1
  17. package/core/built/assets/{ghost.min-6c546c322127ae6d1d1b0ddbf34be75b.js → ghost.min-7da921f6c6cac3fe10da1ba104575440.js} +1775 -1897
  18. package/core/built/assets/{vendor.min-c6ef90bfd7eff256e10b85583bfe9a74.js → vendor.min-413f887176a041e6dbf88214ca9a7481.js} +6849 -6688
  19. package/core/frontend/helpers/asset.js +9 -1
  20. package/core/frontend/helpers/ghost_head.js +13 -1
  21. package/core/frontend/services/card-assets/index.js +16 -0
  22. package/core/frontend/services/card-assets/service.js +109 -0
  23. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  24. package/core/frontend/services/theme-engine/config/index.js +1 -1
  25. package/core/frontend/src/cards/css/bookmark.css +83 -0
  26. package/core/frontend/src/cards/css/button.css +30 -0
  27. package/core/frontend/src/cards/css/callout.css +12 -0
  28. package/core/frontend/src/cards/css/gallery.css +36 -0
  29. package/core/frontend/src/cards/css/nft.css +85 -0
  30. package/core/frontend/src/cards/js/gallery.js +8 -0
  31. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  32. package/core/frontend/web/routes.js +0 -1
  33. package/core/frontend/web/site.js +13 -9
  34. package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
  35. package/core/server/adapters/storage/LocalImagesStorage.js +51 -0
  36. package/core/server/adapters/storage/LocalMediaStorage.js +24 -0
  37. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +64 -51
  38. package/core/server/adapters/storage/index.js +1 -1
  39. package/core/server/adapters/storage/utils.js +2 -2
  40. package/core/server/api/canary/files.js +19 -0
  41. package/core/server/api/canary/index.js +8 -0
  42. package/core/server/api/canary/media.js +42 -0
  43. package/core/server/api/canary/oembed.js +3 -0
  44. package/core/server/api/canary/redirects.js +1 -6
  45. package/core/server/api/canary/utils/serializers/input/index.js +4 -0
  46. package/core/server/api/canary/utils/serializers/input/media.js +8 -0
  47. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  48. package/core/server/api/canary/utils/serializers/output/config.js +21 -14
  49. package/core/server/api/canary/utils/serializers/output/files.js +27 -0
  50. package/core/server/api/canary/utils/serializers/output/index.js +8 -0
  51. package/core/server/api/canary/utils/serializers/output/media.js +37 -0
  52. package/core/server/api/canary/utils/validators/input/files.js +7 -0
  53. package/core/server/api/canary/utils/validators/input/index.js +8 -0
  54. package/core/server/api/canary/utils/validators/input/media.js +11 -0
  55. package/core/server/api/v2/redirects.js +1 -6
  56. package/core/server/api/v3/members.js +5 -1
  57. package/core/server/api/v3/redirects.js +1 -6
  58. package/core/server/data/migrations/utils.js +55 -16
  59. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  60. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  61. package/core/server/data/schema/default-settings.json +8 -0
  62. package/core/server/frontend/ghost.min.css +1 -1
  63. package/core/server/lib/image/blog-icon.js +2 -4
  64. package/core/server/lib/image/image-size.js +1 -1
  65. package/core/server/services/limits.js +3 -6
  66. package/core/server/services/mega/template.js +62 -1
  67. package/core/server/services/nft-oembed.js +71 -0
  68. package/core/server/services/oembed.js +145 -110
  69. package/core/server/services/offers/service.js +1 -31
  70. package/core/server/services/public-config/config.js +2 -1
  71. package/core/server/services/redirects/api.js +270 -0
  72. package/core/server/services/redirects/index.js +27 -12
  73. package/core/server/services/stripe/index.js +4 -2
  74. package/core/server/services/themes/ThemeStorage.js +5 -5
  75. package/core/server/services/url/Resource.js +1 -1
  76. package/core/server/services/url/Resources.js +28 -21
  77. package/core/server/services/url/UrlService.js +66 -8
  78. package/core/server/services/url/Urls.js +7 -2
  79. package/core/server/services/url/index.js +8 -1
  80. package/core/server/web/admin/views/default-prod.html +4 -4
  81. package/core/server/web/admin/views/default.html +4 -4
  82. package/core/server/web/api/canary/admin/routes.js +28 -4
  83. package/core/server/web/api/middleware/cors.js +7 -7
  84. package/core/server/web/api/middleware/upload.js +117 -10
  85. package/core/server/web/members/app.js +1 -1
  86. package/core/server/web/shared/middlewares/index.js +0 -4
  87. package/core/shared/config/defaults.json +5 -1
  88. package/core/shared/config/helpers.js +4 -0
  89. package/core/shared/config/overrides.json +8 -0
  90. package/core/shared/labs.js +12 -3
  91. package/package.json +28 -27
  92. package/urls.json +597 -0
  93. package/yarn.lock +972 -941
  94. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  95. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  96. package/core/server/services/redirects/settings.js +0 -234
  97. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -1,12 +1,11 @@
1
- // # Local File System Image Storage module
2
- // The (default) module for storing images, using the local file system
1
+ // # Local File Base Storage module
2
+ // The (default) module for storing files using the local file system
3
3
  const serveStatic = require('../../../shared/express').static;
4
4
 
5
5
  const fs = require('fs-extra');
6
6
  const path = require('path');
7
7
  const Promise = require('bluebird');
8
8
  const moment = require('moment');
9
- const config = require('../../../shared/config');
10
9
  const tpl = require('@tryghost/tpl');
11
10
  const logging = require('@tryghost/logging');
12
11
  const errors = require('@tryghost/errors');
@@ -15,74 +14,87 @@ const urlUtils = require('../../../shared/url-utils');
15
14
  const StorageBase = require('ghost-storage-base');
16
15
 
17
16
  const messages = {
18
- imageNotFound: 'Image not found',
19
- imageNotFoundWithRef: 'Image not found: {img}',
20
- cannotReadImage: 'Could not read image: {img}'
17
+ notFound: 'File not found',
18
+ notFoundWithRef: 'File not found: {file}',
19
+ cannotRead: 'Could not read file: {file}',
20
+ invalidUrlParameter: `The URL "{url}" is not a valid URL for this site.`
21
21
  };
22
22
 
23
- class LocalFileStore extends StorageBase {
24
- constructor() {
25
- super();
26
-
27
- this.storagePath = config.getContentPath('images');
28
- }
29
-
23
+ class LocalStorageBase extends StorageBase {
30
24
  /**
31
- * Saves a buffer in the targetPath
32
- * @param {Buffer} buffer is an instance of Buffer
33
- * @param {String} targetPath path to which the buffer should be written
34
- * @returns {Promise<String>} a URL to retrieve the data
25
+ *
26
+ * @param {Object} options
27
+ * @param {String} options.storagePath
28
+ * @param {String} options.siteUrl
29
+ * @param {String} [options.staticFileURLPrefix]
30
+ * @param {Object} [options.errorMessages]
31
+ * @param {String} [options.errorMessages.notFound]
32
+ * @param {String} [options.errorMessages.notFoundWithRef]
33
+ * @param {String} [options.errorMessages.cannotRead]
35
34
  */
36
- async saveRaw(buffer, targetPath) {
37
- const storagePath = path.join(this.storagePath, targetPath);
38
- const targetDir = path.dirname(storagePath);
39
-
40
- await fs.mkdirs(targetDir);
41
- await fs.writeFile(storagePath, buffer);
42
-
43
- // For local file system storage can use relative path so add a slash
44
- const fullUrl = (
45
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
46
- urlUtils.STATIC_IMAGE_URL_PREFIX,
47
- targetPath)
48
- ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
35
+ constructor({storagePath, staticFileURLPrefix, siteUrl, errorMessages}) {
36
+ super();
49
37
 
50
- return fullUrl;
38
+ this.storagePath = storagePath;
39
+ this.staticFileURLPrefix = staticFileURLPrefix;
40
+ this.siteUrl = siteUrl;
41
+ this.staticFileUrl = `${siteUrl}${staticFileURLPrefix}`;
42
+ this.errorMessages = errorMessages || messages;
51
43
  }
52
44
 
53
45
  /**
54
- * Saves the image to storage (the file system)
55
- * - image is the express image object
56
- * - returns a promise which ultimately returns the full url to the uploaded image
46
+ * Saves the file to storage (the file system)
47
+ * - returns a promise which ultimately returns the full url to the uploaded file
57
48
  *
58
- * @param {StorageBase.Image} image
49
+ * @param {StorageBase.Image} file
59
50
  * @param {String} targetDir
60
51
  * @returns {Promise<String>}
61
52
  */
62
- async save(image, targetDir) {
53
+ async save(file, targetDir) {
63
54
  let targetFilename;
64
55
 
65
56
  // NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
66
57
  targetDir = targetDir || this.getTargetDir(this.storagePath);
67
58
 
68
- const filename = await this.getUniqueFileName(image, targetDir);
59
+ const filename = await this.getUniqueFileName(file, targetDir);
69
60
 
70
61
  targetFilename = filename;
71
62
  await fs.mkdirs(targetDir);
72
63
 
73
- await fs.copy(image.path, targetFilename);
64
+ await fs.copy(file.path, targetFilename);
74
65
 
75
66
  // The src for the image must be in URI format, not a file system path, which in Windows uses \
76
67
  // For local file system storage can use relative path so add a slash
77
68
  const fullUrl = (
78
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
79
- urlUtils.STATIC_IMAGE_URL_PREFIX,
69
+ urlUtils.urlJoin('/',
70
+ urlUtils.getSubdir(),
71
+ this.staticFileURLPrefix,
80
72
  path.relative(this.storagePath, targetFilename))
81
73
  ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
82
74
 
83
75
  return fullUrl;
84
76
  }
85
77
 
78
+ /**
79
+ *
80
+ * @param {String} url full url under which the stored content is served, result of save method
81
+ * @returns {String} path under which the content is stored
82
+ */
83
+ urlToPath(url) {
84
+ let filePath;
85
+
86
+ if (url.match(this.staticFileUrl)) {
87
+ filePath = url.replace(this.staticFileUrl, '');
88
+ filePath = path.join(this.storagePath, filePath);
89
+ } else {
90
+ throw new errors.IncorrectUsageError({
91
+ message: tpl(messages.invalidUrlParameter, {url})
92
+ });
93
+ }
94
+
95
+ return filePath;
96
+ }
97
+
86
98
  exists(fileName, targetDir) {
87
99
  const filePath = path.join(targetDir || this.storagePath, fileName);
88
100
 
@@ -103,7 +115,7 @@ class LocalFileStore extends StorageBase {
103
115
  * @returns {serveStaticContent}
104
116
  */
105
117
  serve() {
106
- const {storagePath} = this;
118
+ const {storagePath, errorMessages} = this;
107
119
 
108
120
  return function serveStaticContent(req, res, next) {
109
121
  const startedAtMoment = moment();
@@ -114,14 +126,14 @@ class LocalFileStore extends StorageBase {
114
126
  maxAge: constants.ONE_YEAR_MS,
115
127
  fallthrough: false,
116
128
  onEnd: () => {
117
- logging.info('LocalFileStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
129
+ logging.info('LocalStorageBase.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
118
130
  }
119
131
  }
120
132
  )(req, res, (err) => {
121
133
  if (err) {
122
134
  if (err.statusCode === 404) {
123
135
  return next(new errors.NotFoundError({
124
- message: tpl(messages.imageNotFound),
136
+ message: tpl(errorMessages.notFound),
125
137
  code: 'STATIC_FILE_NOT_FOUND',
126
138
  property: err.path
127
139
  }));
@@ -144,16 +156,17 @@ class LocalFileStore extends StorageBase {
144
156
  }
145
157
 
146
158
  /**
147
- * Not implemented.
159
+ * @param {String} filePath
148
160
  * @returns {Promise.<*>}
149
161
  */
150
- delete() {
151
- return Promise.reject('not implemented');
162
+ async delete(fileName, targetDir) {
163
+ const filePath = path.join(targetDir, fileName);
164
+ return await fs.remove(filePath);
152
165
  }
153
166
 
154
167
  /**
155
- * Reads bytes from disk for a target image
156
- * - path of target image (without content path!)
168
+ * Reads bytes from disk for a target file
169
+ * - path of target file (without content path!)
157
170
  *
158
171
  * @param options
159
172
  */
@@ -171,7 +184,7 @@ class LocalFileStore extends StorageBase {
171
184
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
172
185
  return reject(new errors.NotFoundError({
173
186
  err: err,
174
- message: tpl(messages.imageNotFoundWithRef, {img: options.path})
187
+ message: tpl(this.errorMessages.notFoundWithRef, {file: options.path})
175
188
  }));
176
189
  }
177
190
 
@@ -185,7 +198,7 @@ class LocalFileStore extends StorageBase {
185
198
 
186
199
  return reject(new errors.GhostError({
187
200
  err: err,
188
- message: tpl(messages.cannotReadImage, {img: options.path})
201
+ message: tpl(this.errorMessages.cannotRead, {file: options.path})
189
202
  }));
190
203
  }
191
204
 
@@ -195,4 +208,4 @@ class LocalFileStore extends StorageBase {
195
208
  }
196
209
  }
197
210
 
198
- module.exports = LocalFileStore;
211
+ module.exports = LocalStorageBase;
@@ -1,7 +1,7 @@
1
1
  const adapterManager = require('../../services/adapter-manager');
2
2
 
3
3
  /**
4
- * @param {'images'|'videos'|'audios'} [feature] - name for the "feature" to enable through adapter, e.g.: images or videos storage
4
+ * @param {'images'|'media'|'files'} [feature] - name for the "feature" to enable through adapter, e.g.: images or media storage
5
5
  * @returns {Object} adapter instance
6
6
  */
7
7
  function getStorage(feature) {
@@ -12,7 +12,7 @@ const urlUtils = require('../../../shared/url-utils');
12
12
  * @description Takes a url or filepath and returns a filepath with is readable
13
13
  * for the local file storage.
14
14
  */
15
- exports.getLocalFileStoragePath = function getLocalFileStoragePath(imagePath) {
15
+ exports.getLocalImagesStoragePath = function getLocalImagesStoragePath(imagePath) {
16
16
  // The '/' in urlJoin is necessary to add the '/' to `content/images`, if no subdirectory is setup
17
17
  const urlRegExp = new RegExp(`^${urlUtils.urlJoin(
18
18
  urlUtils.urlFor('home', true),
@@ -43,7 +43,7 @@ exports.getLocalFileStoragePath = function getLocalFileStoragePath(imagePath) {
43
43
  */
44
44
 
45
45
  exports.isLocalImage = function isLocalImage(imagePath) {
46
- const localImagePath = this.getLocalFileStoragePath(imagePath);
46
+ const localImagePath = this.getLocalImagesStoragePath(imagePath);
47
47
 
48
48
  if (localImagePath !== imagePath) {
49
49
  return true;
@@ -0,0 +1,19 @@
1
+ const storage = require('../../adapters/storage');
2
+
3
+ module.exports = {
4
+ docName: 'files',
5
+ upload: {
6
+ statusCode: 201,
7
+ permissions: false,
8
+ async query(frame) {
9
+ const filePath = await storage.getStorage('files').save({
10
+ name: frame.file.originalname,
11
+ path: frame.file.path
12
+ });
13
+
14
+ return {
15
+ filePath
16
+ };
17
+ }
18
+ }
19
+ };
@@ -105,6 +105,14 @@ module.exports = {
105
105
  return shared.pipeline(require('./images'), localUtils);
106
106
  },
107
107
 
108
+ get media() {
109
+ return shared.pipeline(require('./media'), localUtils);
110
+ },
111
+
112
+ get files() {
113
+ return shared.pipeline(require('./files'), localUtils);
114
+ },
115
+
108
116
  get tags() {
109
117
  return shared.pipeline(require('./tags'), localUtils);
110
118
  },
@@ -0,0 +1,42 @@
1
+ const path = require('path');
2
+ const storage = require('../../adapters/storage');
3
+
4
+ module.exports = {
5
+ docName: 'media',
6
+ upload: {
7
+ statusCode: 201,
8
+ permissions: false,
9
+ async query(frame) {
10
+ let thumbnailPath = null;
11
+ if (frame.files.thumbnail && frame.files.thumbnail[0]) {
12
+ thumbnailPath = await storage.getStorage('media').save(frame.files.thumbnail[0]);
13
+ }
14
+
15
+ const filePath = await storage.getStorage('media').save(frame.files.file[0]);
16
+
17
+ return {
18
+ filePath,
19
+ thumbnailPath
20
+ };
21
+ }
22
+ },
23
+
24
+ uploadThumbnail: {
25
+ permissions: false,
26
+ options: [
27
+ 'url'
28
+ ],
29
+ async query(frame) {
30
+ const mediaStorage = storage.getStorage('media');
31
+ const targetDir = path.dirname(mediaStorage.urlToPath(frame.data.url));
32
+
33
+ // NOTE: need to cleanup otherwise the parent media name won't match thumb name
34
+ // due to "unique name" generation during save
35
+ if (mediaStorage.exists(frame.file.name, targetDir)) {
36
+ await mediaStorage.delete(frame.file.name, targetDir);
37
+ }
38
+
39
+ return await mediaStorage.save(frame.file, targetDir);
40
+ }
41
+ }
42
+ };
@@ -3,6 +3,9 @@ const externalRequest = require('../../lib/request-external');
3
3
 
4
4
  const OEmbed = require('../../services/oembed');
5
5
  const oembed = new OEmbed({config, externalRequest});
6
+ const NFT = require('../../services/nft-oembed');
7
+ const nft = new NFT();
8
+ oembed.registerProvider(nft);
6
9
 
7
10
  module.exports = {
8
11
  docName: 'oembed',
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };
@@ -35,6 +35,10 @@ module.exports = {
35
35
  return require('./members');
36
36
  },
37
37
 
38
+ get media() {
39
+ return require('./media');
40
+ },
41
+
38
42
  get products() {
39
43
  return require('./products');
40
44
  },
@@ -0,0 +1,8 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ uploadThumbnail(apiConfig, frame) {
5
+ const parentFileName = path.basename(frame.data.url, path.extname(frame.data.url));
6
+ frame.file.name = `${parentFileName}_thumb${frame.file.ext}`;
7
+ }
8
+ };
@@ -102,6 +102,13 @@ const forceStatusFilter = (frame) => {
102
102
  }
103
103
  };
104
104
 
105
+ const transformPageVisibilityFilters = (frame) => {
106
+ if (frame.data.pages[0].visibility === 'filter' && frame.data.pages[0].visibility_filter) {
107
+ frame.data.pages[0].visibility = frame.data.pages[0].visibility_filter;
108
+ }
109
+ delete frame.data.pages[0].visibility_filter;
110
+ };
111
+
105
112
  module.exports = {
106
113
  browse(apiConfig, frame) {
107
114
  debug('browse');
@@ -180,6 +187,7 @@ module.exports = {
180
187
  });
181
188
  }
182
189
 
190
+ transformPageVisibilityFilters(frame);
183
191
  handlePostsMeta(frame);
184
192
  defaultFormat(frame);
185
193
  defaultRelations(frame);
@@ -1,25 +1,32 @@
1
1
  const _ = require('lodash');
2
+ const labs = require('../../../../../../shared/labs');
2
3
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:config');
3
4
 
4
5
  module.exports = {
5
6
  all(data, apiConfig, frame) {
6
7
  debug('all');
7
8
 
9
+ const keys = [
10
+ 'version',
11
+ 'environment',
12
+ 'database',
13
+ 'mail',
14
+ 'useGravatar',
15
+ 'labs',
16
+ 'clientExtensions',
17
+ 'enableDeveloperExperiments',
18
+ 'stripeDirect',
19
+ 'mailgunIsConfigured',
20
+ 'emailAnalytics',
21
+ 'hostSettings'
22
+ ];
23
+
24
+ if (labs.isSet('gifsCard')) {
25
+ keys.push('tenorApiKey');
26
+ }
27
+
8
28
  frame.response = {
9
- config: _.pick(data, [
10
- 'version',
11
- 'environment',
12
- 'database',
13
- 'mail',
14
- 'useGravatar',
15
- 'labs',
16
- 'clientExtensions',
17
- 'enableDeveloperExperiments',
18
- 'stripeDirect',
19
- 'mailgunIsConfigured',
20
- 'emailAnalytics',
21
- 'hostSettings'
22
- ])
29
+ config: _.pick(data, keys)
23
30
  };
24
31
  }
25
32
  };
@@ -0,0 +1,27 @@
1
+ const config = require('../../../../../../shared/config');
2
+ const {STATIC_FILES_URL_PREFIX} = require('@tryghost/constants');
3
+
4
+ function getURL(urlPath) {
5
+ const media = new RegExp('^' + config.getSubdir() + '/' + STATIC_FILES_URL_PREFIX);
6
+ const absolute = media.test(urlPath) ? true : false;
7
+
8
+ if (absolute) {
9
+ // Remove the sub-directory from the URL because ghostConfig will add it back.
10
+ urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
11
+ const baseUrl = config.getSiteUrl().replace(/\/$/, '');
12
+ urlPath = baseUrl + urlPath;
13
+ }
14
+
15
+ return urlPath;
16
+ }
17
+
18
+ module.exports = {
19
+ upload({filePath}, apiConfig, frame) {
20
+ return frame.response = {
21
+ files: [{
22
+ url: getURL(filePath),
23
+ ref: frame.data.ref || null
24
+ }]
25
+ };
26
+ }
27
+ };
@@ -85,6 +85,14 @@ module.exports = {
85
85
  return require('./images');
86
86
  },
87
87
 
88
+ get media() {
89
+ return require('./media');
90
+ },
91
+
92
+ get files() {
93
+ return require('./files');
94
+ },
95
+
88
96
  get tags() {
89
97
  return require('./tags');
90
98
  },
@@ -0,0 +1,37 @@
1
+ const config = require('../../../../../../shared/config');
2
+ const {STATIC_MEDIA_URL_PREFIX} = require('@tryghost/constants');
3
+
4
+ function getURL(urlPath) {
5
+ const media = new RegExp('^' + config.getSubdir() + '/' + STATIC_MEDIA_URL_PREFIX);
6
+ const absolute = media.test(urlPath) ? true : false;
7
+
8
+ if (absolute) {
9
+ // Remove the sub-directory from the URL because ghostConfig will add it back.
10
+ urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
11
+ const baseUrl = config.getSiteUrl().replace(/\/$/, '');
12
+ urlPath = baseUrl + urlPath;
13
+ }
14
+
15
+ return urlPath;
16
+ }
17
+
18
+ module.exports = {
19
+ upload({filePath, thumbnailPath}, apiConfig, frame) {
20
+ return frame.response = {
21
+ media: [{
22
+ url: getURL(filePath),
23
+ thumbnail_url: getURL(thumbnailPath),
24
+ ref: frame.data.ref || null
25
+ }]
26
+ };
27
+ },
28
+
29
+ uploadThumbnail(path, apiConfig, frame) {
30
+ return frame.response = {
31
+ media: [{
32
+ url: getURL(path),
33
+ ref: frame.data.ref || null
34
+ }]
35
+ };
36
+ }
37
+ };
@@ -0,0 +1,7 @@
1
+ const limitService = require('../../../../../services/limits');
2
+
3
+ module.exports = {
4
+ async upload(apiConfig, frame) {
5
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
6
+ }
7
+ };
@@ -27,6 +27,14 @@ module.exports = {
27
27
  return require('./members');
28
28
  },
29
29
 
30
+ get media() {
31
+ return require('./media');
32
+ },
33
+
34
+ get files() {
35
+ return require('./files');
36
+ },
37
+
30
38
  get settings() {
31
39
  return require('./settings');
32
40
  },
@@ -0,0 +1,11 @@
1
+ const limitService = require('../../../../../services/limits');
2
+
3
+ module.exports = {
4
+ async upload(apiConfig, frame) {
5
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
6
+ },
7
+
8
+ async uploadThumbnail(apiConfig, frame) {
9
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
10
+ }
11
+ };
@@ -1,4 +1,3 @@
1
- const web = require('../../web');
2
1
  const redirects = require('../../services/redirects');
3
2
 
4
3
  module.exports = {
@@ -23,11 +22,7 @@ module.exports = {
23
22
  cacheInvalidate: true
24
23
  },
25
24
  query(frame) {
26
- return redirects.api.setFromFilePath(frame.file.path)
27
- .then(() => {
28
- // CASE: trigger that redirects are getting re-registered
29
- web.shared.middlewares.customRedirects.reload();
30
- });
25
+ return redirects.api.setFromFilePath(frame.file.path);
31
26
  }
32
27
  }
33
28
  };
@@ -154,7 +154,11 @@ module.exports = {
154
154
  }
155
155
 
156
156
  if (frame.options.send_email) {
157
- await membersService.api.sendEmailWithMagicLink({email: member.get('email'), requestedType: frame.options.email_type});
157
+ await membersService.api.sendEmailWithMagicLink({
158
+ email: member.get('email'), requestedType: frame.options.email_type, options: {
159
+ forceEmailType: true
160
+ }
161
+ });
158
162
  }
159
163
 
160
164
  return member;
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const web = require('../../web');
4
3
  const redirects = require('../../services/redirects');
5
4
 
6
5
  module.exports = {
@@ -42,11 +41,7 @@ module.exports = {
42
41
  cacheInvalidate: true
43
42
  },
44
43
  query(frame) {
45
- return redirects.api.setFromFilePath(frame.file.path, frame.file.ext)
46
- .then(() => {
47
- // CASE: trigger that redirects are getting re-registered
48
- web.shared.middlewares.customRedirects.reload();
49
- });
44
+ return redirects.api.setFromFilePath(frame.file.path, frame.file.ext);
50
45
  }
51
46
  }
52
47
  };