ghost 4.21.0 → 4.22.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.
Files changed (73) hide show
  1. package/Gruntfile.js +1 -0
  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 +263 -50
  5. package/content/themes/casper/default.hbs +12 -3
  6. package/content/themes/casper/index.hbs +25 -23
  7. package/content/themes/casper/package.json +91 -2
  8. package/content/themes/casper/partials/post-card.hbs +1 -1
  9. package/content/themes/casper/post.hbs +18 -14
  10. package/content/themes/casper/yarn.lock +245 -192
  11. package/core/boot.js +5 -0
  12. package/core/bridge.js +14 -0
  13. package/core/built/assets/{chunk.3.065ee3c3bdf674bd81a4.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
  14. package/core/built/assets/{ghost-dark-1328db4a7dd128305646305a8731bcfe.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
  15. package/core/built/assets/{ghost.min-5abc69c04ad1d5301a857e01009b9c05.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
  16. package/core/built/assets/{ghost.min-6c546c322127ae6d1d1b0ddbf34be75b.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1742 -1891
  17. package/core/built/assets/{vendor.min-c6ef90bfd7eff256e10b85583bfe9a74.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +601 -571
  18. package/core/frontend/helpers/asset.js +9 -1
  19. package/core/frontend/helpers/ghost_head.js +13 -1
  20. package/core/frontend/services/card-assets/index.js +16 -0
  21. package/core/frontend/services/card-assets/service.js +101 -0
  22. package/core/frontend/services/theme-engine/config/defaults.json +4 -1
  23. package/core/frontend/services/theme-engine/config/index.js +1 -1
  24. package/core/frontend/src/cards/css/bookmark.css +83 -0
  25. package/core/frontend/src/cards/css/gallery.css +36 -0
  26. package/core/frontend/src/cards/js/gallery.js +8 -0
  27. package/core/frontend/web/middleware/serve-public-file.js +10 -1
  28. package/core/frontend/web/site.js +10 -9
  29. package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
  30. package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
  31. package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
  32. package/core/server/adapters/storage/index.js +1 -1
  33. package/core/server/adapters/storage/utils.js +2 -2
  34. package/core/server/api/canary/index.js +4 -0
  35. package/core/server/api/canary/media.js +22 -0
  36. package/core/server/api/canary/redirects.js +1 -6
  37. package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
  38. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  39. package/core/server/api/canary/utils/serializers/output/media.js +28 -0
  40. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  41. package/core/server/api/canary/utils/validators/input/media.js +7 -0
  42. package/core/server/api/v2/redirects.js +1 -6
  43. package/core/server/api/v3/members.js +5 -1
  44. package/core/server/api/v3/redirects.js +1 -6
  45. package/core/server/data/migrations/utils.js +55 -16
  46. package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
  47. package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
  48. package/core/server/data/schema/default-settings.json +8 -0
  49. package/core/server/frontend/ghost.min.css +1 -1
  50. package/core/server/lib/image/blog-icon.js +2 -4
  51. package/core/server/lib/image/image-size.js +1 -1
  52. package/core/server/services/limits.js +3 -6
  53. package/core/server/services/mega/template.js +4 -0
  54. package/core/server/services/offers/service.js +1 -31
  55. package/core/server/services/redirects/api.js +270 -0
  56. package/core/server/services/redirects/index.js +27 -12
  57. package/core/server/services/themes/ThemeStorage.js +5 -5
  58. package/core/server/web/admin/views/default-prod.html +4 -4
  59. package/core/server/web/admin/views/default.html +4 -4
  60. package/core/server/web/api/canary/admin/routes.js +13 -4
  61. package/core/server/web/api/middleware/upload.js +117 -10
  62. package/core/server/web/members/app.js +1 -1
  63. package/core/server/web/shared/middlewares/index.js +0 -4
  64. package/core/shared/config/defaults.json +3 -1
  65. package/core/shared/config/helpers.js +2 -0
  66. package/core/shared/config/overrides.json +8 -0
  67. package/core/shared/labs.js +5 -3
  68. package/package.json +14 -13
  69. package/yarn.lock +875 -851
  70. package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
  71. package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
  72. package/core/server/services/redirects/settings.js +0 -234
  73. package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
@@ -2,7 +2,7 @@
2
2
  // Usage: `{{asset "css/screen.css"}}`
3
3
  //
4
4
  // Returns the path to the specified asset.
5
- const {metaData} = require('../services/proxy');
5
+ const {metaData, urlUtils} = require('../services/proxy');
6
6
  const {SafeString} = require('../services/rendering');
7
7
 
8
8
  const errors = require('@tryghost/errors');
@@ -22,6 +22,14 @@ module.exports = function asset(path, options) {
22
22
  message: tpl(messages.pathIsRequired)
23
23
  });
24
24
  }
25
+ if (typeof urlUtils.getSiteUrl() !== 'undefined'
26
+ && typeof urlUtils.getAdminUrl() !== 'undefined'
27
+ && urlUtils.getSiteUrl() !== urlUtils.getAdminUrl()) {
28
+ const target = new URL(getAssetUrl(path, hasMinFile), urlUtils.getSiteUrl());
29
+ return new SafeString(
30
+ target.href
31
+ );
32
+ }
25
33
 
26
34
  return new SafeString(
27
35
  getAssetUrl(path, hasMinFile)
@@ -5,12 +5,16 @@
5
5
  const {metaData, settingsCache, config, blogIcon, urlUtils, labs} = require('../services/proxy');
6
6
  const {escapeExpression, SafeString} = require('../services/rendering');
7
7
 
8
+ // BAD REQUIRE
9
+ // @TODO fix this require
10
+ const cardAssetService = require('../services/card-assets');
11
+
8
12
  const logging = require('@tryghost/logging');
9
13
  const _ = require('lodash');
10
14
  const debug = require('@tryghost/debug')('ghost_head');
11
15
  const templateStyles = require('./tpl/styles');
12
16
 
13
- const getMetaData = metaData.get;
17
+ const {get: getMetaData, getAssetUrl} = metaData;
14
18
 
15
19
  function writeMetaTag(property, content, type) {
16
20
  type = type || property.substring(0, 7) === 'twitter' ? 'name' : 'property';
@@ -193,6 +197,14 @@ module.exports = function ghost_head(options) { // eslint-disable-line camelcase
193
197
  if (!_.includes(context, 'amp')) {
194
198
  head.push(getMembersHelper(options.data));
195
199
 
200
+ // @TODO do this in a more "frameworky" way
201
+ if (cardAssetService.hasFile('js')) {
202
+ head.push(`<script async src="${getAssetUrl('public/cards.min.js')}"></script>`);
203
+ }
204
+ if (cardAssetService.hasFile('css')) {
205
+ head.push(`<link rel="stylesheet" type="text/css" href="${getAssetUrl('public/cards.min.css')}">`);
206
+ }
207
+
196
208
  if (!_.isEmpty(globalCodeinjection)) {
197
209
  head.push(globalCodeinjection);
198
210
  }
@@ -0,0 +1,16 @@
1
+ const debug = require('@tryghost/debug')('card-assets');
2
+ const themeEngine = require('../theme-engine');
3
+
4
+ const CardAssetService = require('./service');
5
+ let cardAssetService = new CardAssetService();
6
+
7
+ const initFn = async () => {
8
+ const cardAssetConfig = themeEngine.getActive().config('card_assets');
9
+ debug('initialising with config', cardAssetConfig);
10
+
11
+ await cardAssetService.load(cardAssetConfig);
12
+ };
13
+
14
+ module.exports = cardAssetService;
15
+
16
+ module.exports.init = initFn;
@@ -0,0 +1,101 @@
1
+ const Minifier = require('@tryghost/minifier');
2
+ const _ = require('lodash');
3
+ const path = require('path');
4
+ const fs = require('fs').promises;
5
+
6
+ class CardAssetService {
7
+ constructor(options = {}) {
8
+ // @TODO: use our config paths concept
9
+ this.src = options.src || path.join(__dirname, '../../src/cards');
10
+ this.dest = options.dest || path.join(__dirname, '../../public');
11
+ this.minifier = new Minifier({src: this.src, dest: this.dest});
12
+
13
+ if ('config' in options) {
14
+ this.config = options.config;
15
+ }
16
+
17
+ this.files = [];
18
+ }
19
+
20
+ generateGlobs() {
21
+ // CASE: The theme has asked for all card assets to be included by default
22
+ if (this.config === true) {
23
+ return {
24
+ 'cards.min.css': 'css/*.css',
25
+ 'cards.min.js': 'js/*.js'
26
+ };
27
+ }
28
+
29
+ // CASE: the theme has declared an include directive, we should include exactly these assets
30
+ // Include rules take precedence over exclude rules.
31
+ if (_.has(this.config, 'include')) {
32
+ return {
33
+ 'cards.min.css': `css/(${this.config.include.join('|')}).css`,
34
+ 'cards.min.js': `js/(${this.config.include.join('|')}).js`
35
+ };
36
+ }
37
+
38
+ // CASE: the theme has declared an exclude directive, we should include exactly these assets
39
+ if (_.has(this.config, 'exclude')) {
40
+ return {
41
+ 'cards.min.css': `css/!(${this.config.exclude.join('|')}).css`,
42
+ 'cards.min.js': `js/!(${this.config.exclude.join('|')}).js`
43
+ };
44
+ }
45
+
46
+ // CASE: theme has asked that no assets be included
47
+ // CASE: we didn't understand config, don't do anything
48
+ return {};
49
+ }
50
+
51
+ async minify(globs) {
52
+ return await this.minifier.minify(globs);
53
+ }
54
+
55
+ async clearFiles() {
56
+ this.files = [];
57
+
58
+ // @deprecated switch this to use fs.rm when we drop support for Node v12
59
+ try {
60
+ await fs.unlink(path.join(this.dest, 'cards.min.css'));
61
+ } catch (error) {
62
+ // Don't worry if the file didn't exist
63
+ if (error.code !== 'ENOENT') {
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ try {
69
+ await fs.unlink(path.join(this.dest, 'cards.min.js'));
70
+ } catch (error) {
71
+ // Don't worry if the file didn't exist
72
+ if (error.code !== 'ENOENT') {
73
+ throw error;
74
+ }
75
+ }
76
+ }
77
+
78
+ hasFile(type) {
79
+ return this.files.indexOf(`cards.min.${type}`) > -1;
80
+ }
81
+
82
+ /**
83
+ * A theme can declare which cards it supports, and we'll do the rest
84
+ *
85
+ * @param {Array|boolean} config
86
+ * @returns
87
+ */
88
+ async load(config) {
89
+ if (config) {
90
+ this.config = config;
91
+ }
92
+
93
+ await this.clearFiles();
94
+
95
+ const globs = this.generateGlobs();
96
+
97
+ this.files = await this.minify(globs);
98
+ }
99
+ }
100
+
101
+ module.exports = CardAssetService;
@@ -1,3 +1,6 @@
1
1
  {
2
- "posts_per_page": 5
2
+ "posts_per_page": 5,
3
+ "card_assets": {
4
+ "exclude": ["bookmark", "gallery"]
5
+ }
3
6
  }
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const defaultConfig = require('./defaults');
3
- const allowedKeys = ['posts_per_page', 'image_sizes'];
3
+ const allowedKeys = ['posts_per_page', 'image_sizes', 'card_assets'];
4
4
 
5
5
  module.exports.create = function configLoader(packageJson) {
6
6
  let config = _.cloneDeep(defaultConfig);
@@ -0,0 +1,83 @@
1
+ /* style.css */
2
+
3
+ .kg-bookmark-card {
4
+ width: 100%;
5
+ position: relative;
6
+ }
7
+
8
+ .kg-bookmark-container {
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ flex-direction: row-reverse;
12
+ color: currentColor;
13
+ font-family: inherit;
14
+ text-decoration: none;
15
+ border: 1px solid rgba(0, 0, 0, 0.1);
16
+ }
17
+
18
+ .kg-bookmark-container:hover {
19
+ text-decoration: none;
20
+ }
21
+
22
+ .kg-bookmark-content {
23
+ flex-basis: 0;
24
+ flex-grow: 999;
25
+ padding: 20px;
26
+ order: 1;
27
+ }
28
+
29
+ .kg-bookmark-title {
30
+ font-weight: 600;
31
+ }
32
+
33
+ .kg-bookmark-metadata,
34
+ .kg-bookmark-description {
35
+ margin-top: .5em;
36
+ }
37
+
38
+ .kg-bookmark-metadata {
39
+ align-items: center;
40
+ white-space: nowrap;
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ }
44
+
45
+ .kg-bookmark-description {
46
+ display: -webkit-box;
47
+ -webkit-box-orient: vertical;
48
+ -webkit-line-clamp: 2;
49
+ overflow: hidden;
50
+ }
51
+
52
+ .kg-bookmark-icon {
53
+ display: inline-block;
54
+ width: 1em;
55
+ height: 1em;
56
+ vertical-align: text-bottom;
57
+ margin-right: .5em;
58
+ margin-bottom: .05em;
59
+ }
60
+
61
+ .kg-bookmark-thumbnail {
62
+ display: flex;
63
+ flex-basis: 24rem;
64
+ flex-grow: 1;
65
+ }
66
+
67
+ .kg-bookmark-thumbnail img {
68
+ max-width: 100%;
69
+ height: auto;
70
+ vertical-align: bottom;
71
+ object-fit: cover;
72
+ }
73
+
74
+ .kg-bookmark-author {
75
+ white-space: nowrap;
76
+ text-overflow: ellipsis;
77
+ overflow: hidden;
78
+ }
79
+
80
+ .kg-bookmark-publisher::before {
81
+ content: "•";
82
+ margin: 0 .5em;
83
+ }
@@ -0,0 +1,36 @@
1
+ .kg-gallery-card {
2
+ margin: 0 0 1.5em;
3
+ }
4
+
5
+ .kg-gallery-card figcaption {
6
+ margin: -1.0em 0 1.5em;
7
+ }
8
+
9
+ .kg-gallery-container {
10
+ display: flex;
11
+ flex-direction: column;
12
+ margin: 1.5em auto;
13
+ max-width: 1040px;
14
+ width: 100vw;
15
+ }
16
+
17
+ .kg-gallery-row {
18
+ display: flex;
19
+ flex-direction: row;
20
+ justify-content: center;
21
+ }
22
+
23
+ .kg-gallery-image img {
24
+ display: block;
25
+ margin: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ }
29
+
30
+ .kg-gallery-row:not(:first-of-type) {
31
+ margin: 0.75em 0 0 0;
32
+ }
33
+
34
+ .kg-gallery-image:not(:first-of-type) {
35
+ margin: 0 0 0 0.75em;
36
+ }
@@ -0,0 +1,8 @@
1
+ var images = document.querySelectorAll('.kg-gallery-image img');
2
+ images.forEach(function (image) {
3
+ var container = image.closest('.kg-gallery-image');
4
+ var width = image.attributes.width.value;
5
+ var height = image.attributes.height.value;
6
+ var ratio = width / height;
7
+ container.style.flex = ratio + ' 1 0%';
8
+ })
@@ -7,7 +7,8 @@ const urlUtils = require('../../../shared/url-utils');
7
7
  const tpl = require('@tryghost/tpl');
8
8
 
9
9
  const messages = {
10
- imageNotFound: 'Image not found'
10
+ imageNotFound: 'Image not found',
11
+ fileNotFound: 'File not found'
11
12
  };
12
13
 
13
14
  function createPublicFileMiddleware(file, type, maxAge) {
@@ -43,6 +44,14 @@ function createPublicFileMiddleware(file, type, maxAge) {
43
44
  // modify text files before caching+serving to ensure URL placeholders are transformed
44
45
  fs.readFile(filePath, (err, buf) => {
45
46
  if (err) {
47
+ // Downgrade to a simple 404 if the file didn't exist
48
+ if (err.code === 'ENOENT') {
49
+ err = new errors.NotFoundError({
50
+ message: tpl(messages.fileNotFound),
51
+ code: 'PUBLIC_FILE_NOT_FOUND',
52
+ property: err.path
53
+ });
54
+ }
46
55
  return next(err);
47
56
  }
48
57
 
@@ -17,11 +17,14 @@ const themeEngine = require('../services/theme-engine');
17
17
  const themeMiddleware = themeEngine.middleware;
18
18
  const membersService = require('../../server/services/members');
19
19
  const offersService = require('../../server/services/offers');
20
+ const customRedirects = require('../../server/services/redirects');
20
21
  const siteRoutes = require('./routes');
21
22
  const shared = require('../../server/web/shared');
22
23
  const mw = require('./middleware');
24
+ const labs = require('../../shared/labs');
23
25
 
24
26
  const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
27
+ const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
25
28
 
26
29
  let router;
27
30
 
@@ -89,7 +92,7 @@ module.exports = function setupSiteApp(options = {}) {
89
92
 
90
93
  // you can extend Ghost with a custom redirects file
91
94
  // see https://github.com/TryGhost/Ghost/issues/7707
92
- shared.middlewares.customRedirects.use(siteApp);
95
+ siteApp.use(customRedirects.middleware);
93
96
 
94
97
  // (Optionally) redirect any requests to /ghost to the admin panel
95
98
  siteApp.use(mw.redirectGhostToAdmin());
@@ -106,16 +109,14 @@ module.exports = function setupSiteApp(options = {}) {
106
109
  siteApp.use(mw.servePublicFile('public/ghost.css', 'text/css', constants.ONE_HOUR_S));
107
110
  siteApp.use(mw.servePublicFile('public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));
108
111
 
112
+ // Card assets
113
+ siteApp.use(mw.servePublicFile('public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
114
+ siteApp.use(mw.servePublicFile('public/cards.min.js', 'text/js', constants.ONE_YEAR_S));
115
+
109
116
  // Serve blog images using the storage adapter
110
117
  siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
111
-
112
- // @TODO find this a better home
113
- // We do this here, at the top level, because helpers require so much stuff.
114
- // Moving this to being inside themes, where it probably should be requires the proxy to be refactored
115
- // Else we end up with circular dependencies
116
- // themeEngine.loadCoreHelpers();
117
- // themeEngine.registerHandlebarsHelpers();
118
- // debug('Helpers done');
118
+ // Serve blog media using the storage adapter
119
+ siteApp.use(STATIC_MEDIA_URL_PREFIX, labs.enabledMiddleware('mediaAPI'), storage.getStorage('media').serve());
119
120
 
120
121
  // Global handling for member session, ensures a member is logged in to the frontend
121
122
  siteApp.use(membersService.middleware.loadMemberSession);
@@ -0,0 +1,50 @@
1
+ // # Local File System Image Storage module
2
+ // The (default) module for storing images, using the local file system
3
+
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const config = require('../../../shared/config');
7
+
8
+ const urlUtils = require('../../../shared/url-utils');
9
+ const LocalStorageBase = require('./LocalStorageBase');
10
+
11
+ let messages = {
12
+ notFound: 'Image not found',
13
+ notFoundWithRef: 'Image not found: {file}',
14
+ cannotRead: 'Could not read image: {file}'
15
+ };
16
+
17
+ class LocalImagesStorage extends LocalStorageBase {
18
+ constructor() {
19
+ super({
20
+ storagePath: config.getContentPath('images'),
21
+ staticFileURLPrefix: urlUtils.STATIC_IMAGE_URL_PREFIX,
22
+ errorMessages: messages
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Saves a buffer in the targetPath
28
+ * @param {Buffer} buffer is an instance of Buffer
29
+ * @param {String} targetPath path to which the buffer should be written
30
+ * @returns {Promise<String>} a URL to retrieve the data
31
+ */
32
+ async saveRaw(buffer, targetPath) {
33
+ const storagePath = path.join(this.storagePath, targetPath);
34
+ const targetDir = path.dirname(storagePath);
35
+
36
+ await fs.mkdirs(targetDir);
37
+ await fs.writeFile(storagePath, buffer);
38
+
39
+ // For local file system storage can use relative path so add a slash
40
+ const fullUrl = (
41
+ urlUtils.urlJoin('/', urlUtils.getSubdir(),
42
+ this.staticFileURLPrefix,
43
+ targetPath)
44
+ ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
45
+
46
+ return fullUrl;
47
+ }
48
+ }
49
+
50
+ module.exports = LocalImagesStorage;
@@ -0,0 +1,23 @@
1
+ // # Local File System Video Storage module
2
+ // The (default) module for storing media, using the local file system
3
+ const config = require('../../../shared/config');
4
+ const constants = require('@tryghost/constants');
5
+ const LocalStorageBase = require('./LocalStorageBase');
6
+
7
+ const messages = {
8
+ notFound: 'Media file not found',
9
+ notFoundWithRef: 'Media file not found: {file}',
10
+ cannotRead: 'Could not read media file: {file}'
11
+ };
12
+
13
+ class LocalMediaStore extends LocalStorageBase {
14
+ constructor() {
15
+ super({
16
+ storagePath: config.getContentPath('media'),
17
+ staticFileURLPrefix: constants.STATIC_MEDIA_URL_PREFIX,
18
+ errorMessages: messages
19
+ });
20
+ }
21
+ }
22
+
23
+ module.exports = LocalMediaStore;
@@ -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,68 +14,57 @@ 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}'
21
20
  };
22
21
 
23
- class LocalFileStore extends StorageBase {
24
- constructor() {
25
- super();
26
-
27
- this.storagePath = config.getContentPath('images');
28
- }
29
-
22
+ class LocalStorageBase extends StorageBase {
30
23
  /**
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
24
+ *
25
+ * @param {Object} options
26
+ * @param {String} options.storagePath
27
+ * @param {String} [options.staticFileURLPrefix]
28
+ * @param {Object} [options.errorMessages]
29
+ * @param {String} [options.errorMessages.notFound]
30
+ * @param {String} [options.errorMessages.notFoundWithRef]
31
+ * @param {String} [options.errorMessages.cannotRead]
35
32
  */
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'), '/');
33
+ constructor({storagePath, staticFileURLPrefix, errorMessages}) {
34
+ super();
49
35
 
50
- return fullUrl;
36
+ this.storagePath = storagePath;
37
+ this.staticFileURLPrefix = staticFileURLPrefix;
38
+ this.errorMessages = errorMessages || messages;
51
39
  }
52
40
 
53
41
  /**
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
42
+ * Saves the file to storage (the file system)
43
+ * - returns a promise which ultimately returns the full url to the uploaded file
57
44
  *
58
- * @param {StorageBase.Image} image
45
+ * @param {StorageBase.Image} file
59
46
  * @param {String} targetDir
60
47
  * @returns {Promise<String>}
61
48
  */
62
- async save(image, targetDir) {
49
+ async save(file, targetDir) {
63
50
  let targetFilename;
64
51
 
65
52
  // NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
66
53
  targetDir = targetDir || this.getTargetDir(this.storagePath);
67
54
 
68
- const filename = await this.getUniqueFileName(image, targetDir);
55
+ const filename = await this.getUniqueFileName(file, targetDir);
69
56
 
70
57
  targetFilename = filename;
71
58
  await fs.mkdirs(targetDir);
72
59
 
73
- await fs.copy(image.path, targetFilename);
60
+ await fs.copy(file.path, targetFilename);
74
61
 
75
62
  // The src for the image must be in URI format, not a file system path, which in Windows uses \
76
63
  // For local file system storage can use relative path so add a slash
77
64
  const fullUrl = (
78
- urlUtils.urlJoin('/', urlUtils.getSubdir(),
79
- urlUtils.STATIC_IMAGE_URL_PREFIX,
65
+ urlUtils.urlJoin('/',
66
+ urlUtils.getSubdir(),
67
+ this.staticFileURLPrefix,
80
68
  path.relative(this.storagePath, targetFilename))
81
69
  ).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
82
70
 
@@ -103,7 +91,7 @@ class LocalFileStore extends StorageBase {
103
91
  * @returns {serveStaticContent}
104
92
  */
105
93
  serve() {
106
- const {storagePath} = this;
94
+ const {storagePath, errorMessages} = this;
107
95
 
108
96
  return function serveStaticContent(req, res, next) {
109
97
  const startedAtMoment = moment();
@@ -114,14 +102,14 @@ class LocalFileStore extends StorageBase {
114
102
  maxAge: constants.ONE_YEAR_MS,
115
103
  fallthrough: false,
116
104
  onEnd: () => {
117
- logging.info('LocalFileStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
105
+ logging.info('LocalStorageBase.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
118
106
  }
119
107
  }
120
108
  )(req, res, (err) => {
121
109
  if (err) {
122
110
  if (err.statusCode === 404) {
123
111
  return next(new errors.NotFoundError({
124
- message: tpl(messages.imageNotFound),
112
+ message: tpl(errorMessages.notFound),
125
113
  code: 'STATIC_FILE_NOT_FOUND',
126
114
  property: err.path
127
115
  }));
@@ -152,8 +140,8 @@ class LocalFileStore extends StorageBase {
152
140
  }
153
141
 
154
142
  /**
155
- * Reads bytes from disk for a target image
156
- * - path of target image (without content path!)
143
+ * Reads bytes from disk for a target file
144
+ * - path of target file (without content path!)
157
145
  *
158
146
  * @param options
159
147
  */
@@ -171,7 +159,7 @@ class LocalFileStore extends StorageBase {
171
159
  if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
172
160
  return reject(new errors.NotFoundError({
173
161
  err: err,
174
- message: tpl(messages.imageNotFoundWithRef, {img: options.path})
162
+ message: tpl(this.errorMessages.notFoundWithRef, {file: options.path})
175
163
  }));
176
164
  }
177
165
 
@@ -185,7 +173,7 @@ class LocalFileStore extends StorageBase {
185
173
 
186
174
  return reject(new errors.GhostError({
187
175
  err: err,
188
- message: tpl(messages.cannotReadImage, {img: options.path})
176
+ message: tpl(this.errorMessages.cannotRead, {file: options.path})
189
177
  }));
190
178
  }
191
179
 
@@ -195,4 +183,4 @@ class LocalFileStore extends StorageBase {
195
183
  }
196
184
  }
197
185
 
198
- module.exports = LocalFileStore;
186
+ 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;