ghost 4.20.2 → 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.
- package/.eslintrc.js +1 -1
- package/Gruntfile.js +1 -0
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/screen.css +263 -50
- package/content/themes/casper/default.hbs +12 -3
- package/content/themes/casper/index.hbs +25 -23
- package/content/themes/casper/package.json +91 -2
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +18 -14
- package/content/themes/casper/yarn.lock +245 -192
- package/core/boot.js +5 -0
- package/core/bridge.js +14 -0
- package/core/built/assets/{chunk.3.777d43e2ce954ba8b2f5.js → chunk.3.1148677ff3b78e5aeaee.js} +60 -60
- package/core/built/assets/{ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css → ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css} +1 -1
- package/core/built/assets/{ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css → ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css} +1 -1
- package/core/built/assets/{ghost.min-07b6a50c54b3e2e190332c28c7255d2f.js → ghost.min-efbfb823467b66f4acc66537d033aa55.js} +1770 -1906
- package/core/built/assets/{vendor.min-af502ac4142871500fc424f6a5a254ec.js → vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js} +985 -956
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +16 -4
- package/core/frontend/helpers/asset.js +9 -1
- package/core/frontend/helpers/ghost_head.js +13 -1
- package/core/frontend/meta/title.js +15 -5
- package/core/frontend/services/card-assets/index.js +16 -0
- package/core/frontend/services/card-assets/service.js +101 -0
- package/core/frontend/services/routing/router-manager.js +5 -3
- package/core/frontend/services/theme-engine/config/defaults.json +4 -1
- package/core/frontend/services/theme-engine/config/index.js +1 -1
- package/core/frontend/src/cards/css/bookmark.css +83 -0
- package/core/frontend/src/cards/css/gallery.css +36 -0
- package/core/frontend/src/cards/js/gallery.js +8 -0
- package/core/frontend/web/middleware/serve-public-file.js +10 -1
- package/core/frontend/web/site.js +10 -9
- package/core/server/adapters/storage/LocalImagesStorage.js +50 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +23 -0
- package/core/server/adapters/storage/{LocalFileStorage.js → LocalStorageBase.js} +36 -48
- package/core/server/adapters/storage/index.js +1 -1
- package/core/server/adapters/storage/utils.js +2 -2
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/media.js +22 -0
- package/core/server/api/canary/members.js +6 -103
- package/core/server/api/canary/membersStripeConnect.js +0 -10
- package/core/server/api/canary/redirects.js +1 -6
- package/core/server/api/canary/utils/serializers/input/pages.js +8 -0
- package/core/server/api/canary/utils/serializers/output/images.js +4 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/serializers/output/media.js +28 -0
- package/core/server/api/canary/utils/validators/input/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/media.js +7 -0
- package/core/server/api/v2/redirects.js +1 -6
- package/core/server/api/v2/utils/serializers/output/images.js +4 -0
- package/core/server/api/v3/members.js +5 -1
- package/core/server/api/v3/redirects.js +1 -6
- package/core/server/api/v3/utils/serializers/output/images.js +4 -0
- package/core/server/data/migrations/utils.js +55 -16
- package/core/server/data/migrations/versions/4.22/01-add-is-launch-complete-setting.js +8 -0
- package/core/server/data/migrations/versions/4.22/02-update-launch-complete-setting-from-user-data.js +39 -0
- package/core/server/data/schema/default-settings.json +8 -0
- package/core/server/frontend/ghost.min.css +1 -1
- package/core/server/lib/image/blog-icon.js +2 -4
- package/core/server/lib/image/image-size.js +1 -1
- package/core/server/services/limits.js +3 -6
- package/core/server/services/mega/template.js +4 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/emails/signup.js +2 -2
- package/core/server/services/members/stripe-connect.js +14 -0
- package/core/server/services/offers/service.js +1 -31
- package/core/server/services/redirects/api.js +270 -0
- package/core/server/services/redirects/index.js +27 -12
- package/core/server/services/themes/ThemeStorage.js +5 -5
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/canary/admin/routes.js +13 -4
- package/core/server/web/api/middleware/upload.js +117 -10
- package/core/server/web/members/app.js +1 -1
- package/core/server/web/shared/middlewares/index.js +0 -4
- package/core/shared/config/defaults.json +3 -1
- package/core/shared/config/helpers.js +2 -0
- package/core/shared/config/overrides.json +8 -0
- package/core/shared/labs.js +5 -3
- package/package.json +35 -34
- package/yarn.lock +997 -1016
- package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
- package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
- package/core/server/services/redirects/settings.js +0 -234
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -128
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
// # Local File
|
|
2
|
-
// The (default) module for storing
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
notFound: 'File not found',
|
|
18
|
+
notFoundWithRef: 'File not found: {file}',
|
|
19
|
+
cannotRead: 'Could not read file: {file}'
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
class
|
|
24
|
-
constructor() {
|
|
25
|
-
super();
|
|
26
|
-
|
|
27
|
-
this.storagePath = config.getContentPath('images');
|
|
28
|
-
}
|
|
29
|
-
|
|
22
|
+
class LocalStorageBase extends StorageBase {
|
|
30
23
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {String}
|
|
34
|
-
* @
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
36
|
+
this.storagePath = storagePath;
|
|
37
|
+
this.staticFileURLPrefix = staticFileURLPrefix;
|
|
38
|
+
this.errorMessages = errorMessages || messages;
|
|
51
39
|
}
|
|
52
40
|
|
|
53
41
|
/**
|
|
54
|
-
* Saves the
|
|
55
|
-
* -
|
|
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}
|
|
45
|
+
* @param {StorageBase.Image} file
|
|
59
46
|
* @param {String} targetDir
|
|
60
47
|
* @returns {Promise<String>}
|
|
61
48
|
*/
|
|
62
|
-
async save(
|
|
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(
|
|
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(
|
|
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('/',
|
|
79
|
-
urlUtils.
|
|
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('
|
|
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(
|
|
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
|
|
156
|
-
* - path of target
|
|
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(
|
|
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(
|
|
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 =
|
|
186
|
+
module.exports = LocalStorageBase;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const adapterManager = require('../../services/adapter-manager');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @param {'images'|'
|
|
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.
|
|
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.
|
|
46
|
+
const localImagePath = this.getLocalImagesStoragePath(imagePath);
|
|
47
47
|
|
|
48
48
|
if (localImagePath !== imagePath) {
|
|
49
49
|
return true;
|
|
@@ -105,6 +105,10 @@ 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
|
+
|
|
108
112
|
get tags() {
|
|
109
113
|
return shared.pipeline(require('./tags'), localUtils);
|
|
110
114
|
},
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const storage = require('../../adapters/storage');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
docName: 'media',
|
|
5
|
+
upload: {
|
|
6
|
+
statusCode: 201,
|
|
7
|
+
permissions: false,
|
|
8
|
+
async query(frame) {
|
|
9
|
+
let thumbnail = null;
|
|
10
|
+
if (frame.files.thumbnail && frame.files.thumbnail[0]) {
|
|
11
|
+
thumbnail = await storage.getStorage('media').save(frame.files.thumbnail[0]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const file = await storage.getStorage('media').save(frame.files.file[0]);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
filePath: file,
|
|
18
|
+
thumbnailPath: thumbnail
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -59,8 +59,7 @@ module.exports = {
|
|
|
59
59
|
permissions: true,
|
|
60
60
|
validation: {},
|
|
61
61
|
async query(frame) {
|
|
62
|
-
|
|
63
|
-
const page = await membersService.api.members.list(frame.options);
|
|
62
|
+
const page = await membersService.api.memberBREADService.browse(frame.options);
|
|
64
63
|
|
|
65
64
|
return page;
|
|
66
65
|
}
|
|
@@ -84,7 +83,7 @@ module.exports = {
|
|
|
84
83
|
},
|
|
85
84
|
permissions: true,
|
|
86
85
|
async query(frame) {
|
|
87
|
-
|
|
86
|
+
const member = await membersService.api.memberBREADService.read(frame.data, frame.options);
|
|
88
87
|
|
|
89
88
|
if (!member) {
|
|
90
89
|
throw new errors.NotFoundError({
|
|
@@ -115,72 +114,9 @@ module.exports = {
|
|
|
115
114
|
},
|
|
116
115
|
permissions: true,
|
|
117
116
|
async query(frame) {
|
|
118
|
-
|
|
119
|
-
frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
|
120
|
-
if (!labsService.isSet('multipleProducts')) {
|
|
121
|
-
delete frame.data.products;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
if (!membersService.config.isStripeConnected()
|
|
125
|
-
&& (frame.data.members[0].stripe_customer_id || frame.data.members[0].comped)) {
|
|
126
|
-
const property = frame.data.members[0].comped ? 'comped' : 'stripe_customer_id';
|
|
127
|
-
|
|
128
|
-
throw new errors.ValidationError({
|
|
129
|
-
message: tpl(messages.stripeNotConnected.message),
|
|
130
|
-
context: tpl(messages.stripeNotConnected.context),
|
|
131
|
-
help: tpl(messages.stripeNotConnected.help),
|
|
132
|
-
property
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
member = await membersService.api.members.create(frame.data.members[0], frame.options);
|
|
137
|
-
|
|
138
|
-
if (frame.data.members[0].stripe_customer_id) {
|
|
139
|
-
await membersService.api.members.linkStripeCustomer({
|
|
140
|
-
customer_id: frame.data.members[0].stripe_customer_id,
|
|
141
|
-
member_id: member.id
|
|
142
|
-
}, frame.options);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!labsService.isSet('multipleProducts')) {
|
|
146
|
-
if (frame.data.members[0].comped) {
|
|
147
|
-
await membersService.api.members.setComplimentarySubscription(member);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (frame.options.send_email) {
|
|
152
|
-
await membersService.api.sendEmailWithMagicLink({email: member.get('email'), requestedType: frame.options.email_type});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return member;
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
|
|
158
|
-
throw new errors.ValidationError({
|
|
159
|
-
message: tpl(messages.memberAlreadyExists.message),
|
|
160
|
-
context: tpl(messages.memberAlreadyExists.context, {
|
|
161
|
-
action: 'add'
|
|
162
|
-
})
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// NOTE: failed to link Stripe customer/plan/subscription or have thrown custom Stripe connection error.
|
|
167
|
-
// It's a bit ugly doing regex matching to detect errors, but it's the easiest way that works without
|
|
168
|
-
// introducing additional logic/data format into current error handling
|
|
169
|
-
const isStripeLinkingError = error.message && (error.message.match(/customer|plan|subscription/g));
|
|
170
|
-
if (member && isStripeLinkingError) {
|
|
171
|
-
if (error.message.indexOf('customer') && error.code === 'resource_missing') {
|
|
172
|
-
error.message = `Member not imported. ${error.message}`;
|
|
173
|
-
error.context = tpl(messages.stripeCustomerNotFound.context);
|
|
174
|
-
error.help = tpl(messages.stripeCustomerNotFound.help);
|
|
175
|
-
}
|
|
117
|
+
const member = await membersService.api.memberBREADService.add(frame.data.members[0], frame.options);
|
|
176
118
|
|
|
177
|
-
|
|
178
|
-
id: member.get('id')
|
|
179
|
-
}, frame.options);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
119
|
+
return member;
|
|
184
120
|
}
|
|
185
121
|
},
|
|
186
122
|
|
|
@@ -199,42 +135,9 @@ module.exports = {
|
|
|
199
135
|
},
|
|
200
136
|
permissions: true,
|
|
201
137
|
async query(frame) {
|
|
202
|
-
|
|
203
|
-
delete frame.data.products;
|
|
204
|
-
}
|
|
205
|
-
try {
|
|
206
|
-
frame.options.withRelated = ['stripeSubscriptions', 'products', 'labels', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct'];
|
|
207
|
-
const member = await membersService.api.members.update(frame.data.members[0], frame.options);
|
|
208
|
-
|
|
209
|
-
const hasCompedSubscription = !!member.related('stripeSubscriptions').find(sub => sub.get('plan_nickname') === 'Complimentary' && sub.get('status') === 'active');
|
|
210
|
-
|
|
211
|
-
if (!labsService.isSet('multipleProducts')) {
|
|
212
|
-
if (typeof frame.data.members[0].comped === 'boolean') {
|
|
213
|
-
if (frame.data.members[0].comped && !hasCompedSubscription) {
|
|
214
|
-
await membersService.api.members.setComplimentarySubscription(member);
|
|
215
|
-
} else if (!(frame.data.members[0].comped) && hasCompedSubscription) {
|
|
216
|
-
await membersService.api.members.cancelComplimentarySubscription(member);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
await member.load(['stripeSubscriptions', 'products', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await member.load(['stripeSubscriptions.customer', 'stripeSubscriptions.stripePrice', 'stripeSubscriptions.stripePrice.stripeProduct']);
|
|
224
|
-
|
|
225
|
-
return member;
|
|
226
|
-
} catch (error) {
|
|
227
|
-
if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
|
|
228
|
-
throw new errors.ValidationError({
|
|
229
|
-
message: tpl(messages.memberAlreadyExists.message),
|
|
230
|
-
context: tpl(messages.memberAlreadyExists.context, {
|
|
231
|
-
action: 'edit'
|
|
232
|
-
})
|
|
233
|
-
});
|
|
234
|
-
}
|
|
138
|
+
const member = await membersService.api.memberBREADService.edit(frame.data.members[0], frame.options);
|
|
235
139
|
|
|
236
|
-
|
|
237
|
-
}
|
|
140
|
+
return member;
|
|
238
141
|
}
|
|
239
142
|
},
|
|
240
143
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
const membersService = require('../../services/members');
|
|
2
|
-
const config = require('../../../shared/config');
|
|
3
|
-
const urlUtils = require('../../../shared/url-utils');
|
|
4
|
-
const {BadRequestError} = require('@tryghost/errors');
|
|
5
2
|
|
|
6
3
|
module.exports = {
|
|
7
4
|
docName: 'members_stripe_connect',
|
|
@@ -18,13 +15,6 @@ module.exports = {
|
|
|
18
15
|
}
|
|
19
16
|
},
|
|
20
17
|
query(frame) {
|
|
21
|
-
const siteUrl = urlUtils.getSiteUrl();
|
|
22
|
-
const productionMode = config.get('env') === 'production';
|
|
23
|
-
const siteUrlUsingSSL = /^https/.test(siteUrl);
|
|
24
|
-
const cannotConnectToStripe = productionMode && !siteUrlUsingSSL;
|
|
25
|
-
if (cannotConnectToStripe) {
|
|
26
|
-
throw new BadRequestError('Cannot connect to stripe unless site is using https://');
|
|
27
|
-
}
|
|
28
18
|
// This is something you have to do if you want to use the "framework" with access to the raw req/res
|
|
29
19
|
frame.response = async function (req, res) {
|
|
30
20
|
function setSessionProp(prop, val) {
|
|
@@ -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
|
};
|
|
@@ -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);
|
|
@@ -8,6 +8,10 @@ module.exports = {
|
|
|
8
8
|
return frame.response = {
|
|
9
9
|
images: [{
|
|
10
10
|
url: mapper.mapImage(path),
|
|
11
|
+
// NOTE: ref field is here to have reference point on the client
|
|
12
|
+
// for example when substituting existing images in the mobiledoc
|
|
13
|
+
// this field would serve as an identifier to find images to replace
|
|
14
|
+
// once the response is back. Think of it as ID on the client's side.
|
|
11
15
|
ref: frame.data.ref || null
|
|
12
16
|
}]
|
|
13
17
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
};
|
|
@@ -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
|
};
|
|
@@ -8,6 +8,10 @@ module.exports = {
|
|
|
8
8
|
return frame.response = {
|
|
9
9
|
images: [{
|
|
10
10
|
url: mapper.mapImage(path),
|
|
11
|
+
// NOTE: ref field is here to have reference point on the client
|
|
12
|
+
// for example when substituting existing images in the mobiledoc
|
|
13
|
+
// this field would serve as an identifier to find images to replace
|
|
14
|
+
// once the response is back. Think of it as ID on the client's side.
|
|
11
15
|
ref: frame.data.ref || null
|
|
12
16
|
}]
|
|
13
17
|
};
|
|
@@ -154,7 +154,11 @@ module.exports = {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
if (frame.options.send_email) {
|
|
157
|
-
await membersService.api.sendEmailWithMagicLink({
|
|
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
|
};
|
|
@@ -8,6 +8,10 @@ module.exports = {
|
|
|
8
8
|
return frame.response = {
|
|
9
9
|
images: [{
|
|
10
10
|
url: mapper.mapImage(path),
|
|
11
|
+
// NOTE: ref field is here to have reference point on the client
|
|
12
|
+
// for example when substituting existing images in the mobiledoc
|
|
13
|
+
// this field would serve as an identifier to find images to replace
|
|
14
|
+
// once the response is back. Think of it as ID on the client's side.
|
|
11
15
|
ref: frame.data.ref || null
|
|
12
16
|
}]
|
|
13
17
|
};
|