ghost 4.41.3 → 4.43.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/content/themes/casper/package.json +2 -3
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/core/built/assets/ghost-dark-1933079797e24ccb8839657020830be5.css +1 -0
- package/core/built/assets/{ghost.min-1abf114ca26a71e8e1f09054f3592614.js → ghost.min-2a278873d60d6a13a4c05a396e5bed5e.js} +533 -398
- package/core/built/assets/ghost.min-38f3c38c0c6a1864f57079b068a0b0ce.css +1 -0
- package/core/built/assets/{vendor.min-9094db77ba3190cb10876f8e42e1d90d.js → vendor.min-21f79c68a284acb1b70039f3f63e5507.js} +68 -68
- package/core/built/assets/{vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css → vendor.min-ba66b98f7c24fa40e061c7ffc94f4e23.css} +214 -0
- package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +1 -1
- package/core/frontend/apps/amp/lib/helpers/amp_components.js +1 -1
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +1 -1
- package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
- package/core/frontend/apps/amp/lib/router.js +6 -5
- package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
- package/core/frontend/apps/private-blogging/lib/router.js +2 -2
- package/core/frontend/helpers/asset.js +1 -1
- package/core/frontend/helpers/author.js +1 -1
- package/core/frontend/helpers/authors.js +1 -1
- package/core/frontend/helpers/body_class.js +1 -1
- package/core/frontend/helpers/cancel_link.js +1 -1
- package/core/frontend/helpers/concat.js +1 -1
- package/core/frontend/helpers/content.js +1 -1
- package/core/frontend/helpers/date.js +1 -1
- package/core/frontend/helpers/encode.js +1 -1
- package/core/frontend/helpers/excerpt.js +1 -1
- package/core/frontend/helpers/facebook_url.js +1 -1
- package/core/frontend/helpers/foreach.js +2 -2
- package/core/frontend/helpers/get.js +1 -1
- package/core/frontend/helpers/ghost_foot.js +1 -1
- package/core/frontend/helpers/ghost_head.js +1 -1
- package/core/frontend/helpers/lang.js +1 -1
- package/core/frontend/helpers/link.js +1 -1
- package/core/frontend/helpers/link_class.js +1 -1
- package/core/frontend/helpers/match.js +1 -1
- package/core/frontend/helpers/navigation.js +1 -1
- package/core/frontend/helpers/pagination.js +1 -1
- package/core/frontend/helpers/plural.js +1 -1
- package/core/frontend/helpers/post_class.js +1 -1
- package/core/frontend/helpers/prev_post.js +6 -5
- package/core/frontend/helpers/price.js +1 -0
- package/core/frontend/helpers/products.js +1 -1
- package/core/frontend/helpers/reading_time.js +2 -2
- package/core/frontend/helpers/t.js +1 -1
- package/core/frontend/helpers/tags.js +1 -1
- package/core/frontend/helpers/tiers.js +1 -1
- package/core/frontend/helpers/title.js +1 -1
- package/core/frontend/helpers/twitter_url.js +1 -1
- package/core/frontend/helpers/url.js +1 -1
- package/core/frontend/meta/url.js +4 -4
- package/core/{server/data/schema → frontend/services/data}/checks.js +4 -4
- package/core/frontend/services/{routing/helpers → data}/entry-lookup.js +3 -3
- package/core/frontend/services/{routing/helpers → data}/fetch-data.js +3 -3
- package/core/frontend/services/data/index.js +5 -0
- package/core/frontend/services/{rendering.js → handlebars.js} +2 -1
- package/core/frontend/services/helpers/handlebars.js +1 -1
- package/core/frontend/services/proxy.js +2 -4
- package/core/frontend/services/{routing/helpers → rendering}/context.js +0 -0
- package/core/frontend/services/{routing/helpers → rendering}/error.js +0 -0
- package/core/frontend/services/{routing/helpers → rendering}/format-response.js +1 -1
- package/core/frontend/services/{routing/helpers → rendering}/index.js +0 -8
- package/core/frontend/services/{routing/helpers → rendering}/render-entries.js +1 -1
- package/core/frontend/services/{routing/helpers → rendering}/render-entry.js +1 -1
- package/core/frontend/services/{routing/helpers → rendering}/renderer.js +1 -1
- package/core/frontend/services/{routing/helpers → rendering}/secure.js +0 -0
- package/core/frontend/services/{routing/helpers → rendering}/templates.js +2 -2
- package/core/frontend/services/routing/CollectionRouter.js +1 -1
- package/core/frontend/services/routing/controllers/channel.js +9 -9
- package/core/frontend/services/routing/controllers/collection.js +9 -9
- package/core/frontend/services/routing/controllers/email-post.js +5 -6
- package/core/frontend/services/routing/controllers/entry.js +6 -6
- package/core/frontend/services/routing/controllers/preview.js +5 -6
- package/core/frontend/services/routing/controllers/rss.js +4 -3
- package/core/frontend/services/routing/controllers/static.js +5 -5
- package/core/frontend/services/routing/controllers/unsubscribe.js +2 -2
- package/core/frontend/services/routing/index.js +0 -4
- package/core/frontend/web/middleware/error-handler.js +2 -2
- package/core/server/api/canary/email-preview.js +2 -1
- package/core/server/api/canary/{email.js → emails.js} +0 -0
- package/core/server/api/canary/index.js +9 -1
- package/core/server/api/canary/members.js +0 -45
- package/core/server/api/canary/newsletters.js +45 -0
- package/core/server/api/canary/stats.js +23 -0
- package/core/server/api/canary/utils/serializers/output/email-previews.js +7 -0
- package/core/server/api/canary/utils/serializers/output/index.js +2 -22
- package/core/server/api/canary/utils/serializers/output/mappers/index.js +1 -0
- package/core/server/api/canary/utils/serializers/output/mappers/snippets.js +36 -0
- package/core/server/api/canary/utils/serializers/output/members.js +5 -2
- package/core/server/api/canary/utils/serializers/output/oembed.js +2 -2
- package/core/server/api/canary/utils/serializers/output/redirects.js +2 -2
- package/core/server/api/canary/utils/serializers/output/schedules.js +2 -2
- package/core/server/api/canary/utils/serializers/output/slack.js +2 -2
- package/core/server/api/canary/utils/serializers/output/themes.js +2 -2
- package/core/server/api/canary/utils/serializers/output/users.js +0 -23
- package/core/server/api/canary/utils/validators/input/index.js +6 -0
- package/core/server/api/shared/http.js +52 -51
- package/core/server/api/shared/serializers/handle.js +25 -26
- package/core/server/data/exporter/table-lists.js +2 -0
- package/core/server/data/migrations/utils.js +34 -2
- package/core/server/data/migrations/versions/4.42/2022-03-21-17-17-add.js +25 -0
- package/core/server/data/migrations/versions/4.42/2022-03-30-15-44-add-newsletter-permissions.js +28 -0
- package/core/server/data/migrations/versions/4.43/2022-03-28-19-26-recreate-newsletter-table.js +29 -0
- package/core/server/data/migrations/versions/4.43/2022-03-29-14-45-add-members-newsletters-table.js +7 -0
- package/core/server/data/migrations/versions/4.43/2022-04-01-10-13-add-post-newsletter-relation.js +108 -0
- package/core/server/data/migrations/versions/4.43/2022-04-06-09-47-add-type-column-to-paid-subscription-events.js +7 -0
- package/core/server/data/migrations/versions/4.43/2022-04-06-14-56-add-email-newsletter-relation.js +8 -0
- package/core/server/data/migrations/versions/4.43/2022-04-08-10-45-add-subscription-id-to-mrr-events.js +7 -0
- package/core/server/data/schema/commands.js +19 -14
- package/core/server/data/schema/index.js +0 -1
- package/core/server/data/schema/schema.js +36 -0
- package/core/server/models/base/bookshelf.js +1 -1
- package/core/server/models/base/plugins/crud.js +8 -0
- package/core/server/models/member.js +18 -1
- package/core/server/models/newsletter.js +43 -0
- package/core/server/models/post.js +4 -1
- package/core/server/services/auth/setup.js +4 -1
- package/core/server/services/mega/template.js +25 -13
- package/core/server/services/members/api.js +3 -1
- package/core/server/services/members/middleware.js +13 -3
- package/core/server/services/members/service.js +2 -1
- package/core/server/services/members/utils.js +13 -1
- package/core/server/services/newsletters/index.js +10 -0
- package/core/server/services/newsletters/service.js +24 -0
- package/core/server/services/slack.js +11 -3
- package/core/server/services/stats/index.js +1 -0
- package/core/server/services/stats/lib/members-stats-service.js +161 -0
- package/core/server/services/stats/lib/mrr-stats-service.js +154 -0
- package/core/server/services/stats/service.js +8 -0
- package/core/server/services/stripe/service.js +1 -0
- package/core/server/services/webhooks/webhooks-service.js +3 -1
- package/core/server/web/admin/views/default-prod.html +5 -5
- package/core/server/web/admin/views/default.html +5 -5
- package/core/server/web/api/canary/admin/routes.js +9 -2
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/env/config.development.json +26 -0
- package/core/shared/config/env/config.production.json +21 -0
- package/core/shared/config/env/config.testing-mysql.json +59 -0
- package/core/shared/config/env/config.testing.json +58 -0
- package/package.json +50 -50
- package/yarn.lock +700 -769
- package/content/themes/casper/assets/css/csscomb.json +0 -240
- package/core/built/assets/ghost-dark-146c4c688b47d45c4aa018ee0f79cebc.css +0 -1
- package/core/built/assets/ghost.min-a73b150c7eecc4641d377cc73fb5eecd.css +0 -1
- package/core/server/api/canary/utils/serializers/output/email-preview.js +0 -10
- package/core/server/api/canary/utils/serializers/output/emails.js +0 -22
- package/core/server/api/canary/utils/serializers/output/identities.js +0 -7
- package/core/server/api/canary/utils/serializers/output/member-signin-urls.js +0 -7
- package/core/server/api/canary/utils/serializers/output/snippets.js +0 -107
- package/core/server/api/canary/utils/serializers/output/webhooks.js +0 -15
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const models = require('../../models');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
docName: 'newsletters',
|
|
5
|
+
|
|
6
|
+
browse: {
|
|
7
|
+
options: [
|
|
8
|
+
'filter',
|
|
9
|
+
'fields',
|
|
10
|
+
'limit',
|
|
11
|
+
'order',
|
|
12
|
+
'page'
|
|
13
|
+
],
|
|
14
|
+
permissions: true,
|
|
15
|
+
query(frame) {
|
|
16
|
+
return models.Newsletter.findPage(frame.options);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
add: {
|
|
21
|
+
statusCode: 201,
|
|
22
|
+
permissions: true,
|
|
23
|
+
async query(frame) {
|
|
24
|
+
return models.Newsletter.add(frame.data.newsletters[0], frame.options);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
edit: {
|
|
29
|
+
headers: {},
|
|
30
|
+
options: [
|
|
31
|
+
'id'
|
|
32
|
+
],
|
|
33
|
+
validation: {
|
|
34
|
+
options: {
|
|
35
|
+
id: {
|
|
36
|
+
required: true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
permissions: true,
|
|
41
|
+
async query(frame) {
|
|
42
|
+
return models.Newsletter.edit(frame.data.newsletters[0], frame.options);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const statsService = require('../../services/stats');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
docName: 'stats',
|
|
5
|
+
memberCountHistory: {
|
|
6
|
+
permissions: {
|
|
7
|
+
docName: 'members',
|
|
8
|
+
method: 'browse'
|
|
9
|
+
},
|
|
10
|
+
async query() {
|
|
11
|
+
return await statsService.members.getCountHistory();
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
mrr: {
|
|
15
|
+
permissions: {
|
|
16
|
+
docName: 'members',
|
|
17
|
+
method: 'browse'
|
|
18
|
+
},
|
|
19
|
+
async query() {
|
|
20
|
+
return await statsService.mrr.getHistory();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -41,10 +41,6 @@ module.exports = {
|
|
|
41
41
|
return require('./schedules');
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
-
get webhooks() {
|
|
45
|
-
return require('./webhooks');
|
|
46
|
-
},
|
|
47
|
-
|
|
48
44
|
get posts() {
|
|
49
45
|
return require('./posts');
|
|
50
46
|
},
|
|
@@ -73,14 +69,6 @@ module.exports = {
|
|
|
73
69
|
return require('./tiers');
|
|
74
70
|
},
|
|
75
71
|
|
|
76
|
-
get member_signin_urls() {
|
|
77
|
-
return require('./member-signin-urls');
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
get identities() {
|
|
81
|
-
return require('./identities');
|
|
82
|
-
},
|
|
83
|
-
|
|
84
72
|
get images() {
|
|
85
73
|
return require('./images');
|
|
86
74
|
},
|
|
@@ -121,16 +109,8 @@ module.exports = {
|
|
|
121
109
|
return require('./site');
|
|
122
110
|
},
|
|
123
111
|
|
|
124
|
-
get
|
|
125
|
-
return require('./email-
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
get emails() {
|
|
129
|
-
return require('./emails');
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
get snippets() {
|
|
133
|
-
return require('./snippets');
|
|
112
|
+
get email_previews() {
|
|
113
|
+
return require('./email-previews');
|
|
134
114
|
},
|
|
135
115
|
|
|
136
116
|
get custom_theme_settings() {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('bookshelf').Model} snippet
|
|
3
|
+
* @param {Frame} frame
|
|
4
|
+
*
|
|
5
|
+
* @returns {SerializedSnippet}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = (snippet, frame) => {
|
|
8
|
+
const json = snippet.toJSON(frame.options);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
id: json.id,
|
|
12
|
+
name: json.name,
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
mobiledoc: json.mobiledoc,
|
|
15
|
+
created_at: json.created_at,
|
|
16
|
+
updated_at: json.updated_at,
|
|
17
|
+
created_by: json.created_by,
|
|
18
|
+
updated_by: json.updated_by
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} SerializedSnippet
|
|
24
|
+
* @prop {string} id
|
|
25
|
+
* @prop {string=} name
|
|
26
|
+
* @prop {string=} mobiledoc
|
|
27
|
+
* @prop {string} created_at
|
|
28
|
+
* @prop {string} updated_at
|
|
29
|
+
* @prop {string} created_by
|
|
30
|
+
* @prop {string} updated_by
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object<string, any>} Frame
|
|
35
|
+
* @prop {Object} options
|
|
36
|
+
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
|
|
3
3
|
const {unparse} = require('@tryghost/members-csv');
|
|
4
|
+
const labsService = require('../../../../../../shared/labs');
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
hasActiveStripeSubscriptions: createSerializer('hasActiveStripeSubscriptions', passthrough),
|
|
@@ -20,8 +21,6 @@ module.exports = {
|
|
|
20
21
|
importCSV: createSerializer('importCSV', passthrough),
|
|
21
22
|
memberStats: createSerializer('memberStats', passthrough),
|
|
22
23
|
mrrStats: createSerializer('mrrStats', passthrough),
|
|
23
|
-
subscriberStats: createSerializer('subscriberStats', passthrough),
|
|
24
|
-
grossVolumeStats: createSerializer('grossVolumeStats', passthrough),
|
|
25
24
|
activityFeed: createSerializer('activityFeed', passthrough)
|
|
26
25
|
};
|
|
27
26
|
|
|
@@ -133,6 +132,10 @@ function serializeMember(member, options) {
|
|
|
133
132
|
serialized.products = json.products;
|
|
134
133
|
}
|
|
135
134
|
|
|
135
|
+
if (json.newsletters && labsService.isSet('multipleNewsletters')) {
|
|
136
|
+
serialized.newsletters = json.newsletters;
|
|
137
|
+
}
|
|
138
|
+
|
|
136
139
|
return serialized;
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:users');
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
|
-
const mappers = require('./mappers');
|
|
4
3
|
|
|
5
4
|
const messages = {
|
|
6
5
|
pwdChangedSuccessfully: 'Password changed successfully.'
|
|
7
6
|
};
|
|
8
7
|
|
|
9
8
|
module.exports = {
|
|
10
|
-
browse(models, apiConfig, frame) {
|
|
11
|
-
debug('browse');
|
|
12
|
-
|
|
13
|
-
frame.response = {
|
|
14
|
-
users: models.data.map(model => mappers.users(model, frame)),
|
|
15
|
-
meta: models.meta
|
|
16
|
-
};
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
read(model, apiConfig, frame) {
|
|
20
|
-
debug('read');
|
|
21
|
-
|
|
22
|
-
frame.response = {
|
|
23
|
-
users: [mappers.users(model, frame)]
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
edit() {
|
|
28
|
-
debug('edit');
|
|
29
|
-
this.read(...arguments);
|
|
30
|
-
},
|
|
31
|
-
|
|
32
9
|
destroy(filename, apiConfig, frame) {
|
|
33
10
|
debug('destroy');
|
|
34
11
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ESLint Override Notice
|
|
2
|
+
// This is a valid index.js file - it just exports a lot of stuff!
|
|
3
|
+
// Long term we would like to change the API architecture to reduce this file,
|
|
4
|
+
// but that's not the problem the index.js max - line eslint "proxy" rule is there to solve.
|
|
5
|
+
/* eslint-disable max-lines */
|
|
6
|
+
|
|
1
7
|
module.exports = {
|
|
2
8
|
get passwordreset() {
|
|
3
9
|
return require('./passwordreset');
|
|
@@ -13,7 +13,7 @@ const models = require('../../models');
|
|
|
13
13
|
* @return {Function}
|
|
14
14
|
*/
|
|
15
15
|
const http = (apiImpl) => {
|
|
16
|
-
return (req, res, next) => {
|
|
16
|
+
return async (req, res, next) => {
|
|
17
17
|
debug(`External API request to ${req.url}`);
|
|
18
18
|
let apiKey = null;
|
|
19
19
|
let integration = null;
|
|
@@ -60,69 +60,70 @@ const http = (apiImpl) => {
|
|
|
60
60
|
data: apiImpl.data
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
debug(`External API request to ${frame.docName}.${frame.method}`);
|
|
66
|
-
return shared.headers.get(result, apiImpl.headers, frame)
|
|
67
|
-
.then(headers => ({result, headers}));
|
|
68
|
-
})
|
|
69
|
-
.then(({result, headers}) => {
|
|
70
|
-
// CASE: api ctrl wants to handle the express response (e.g. streams)
|
|
71
|
-
if (typeof result === 'function') {
|
|
72
|
-
debug('ctrl function call');
|
|
73
|
-
return result(req, res, next);
|
|
74
|
-
}
|
|
63
|
+
try {
|
|
64
|
+
const result = await apiImpl(frame);
|
|
75
65
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
statusCode = apiImpl.statusCode(result);
|
|
79
|
-
} else if (apiImpl.statusCode) {
|
|
80
|
-
statusCode = apiImpl.statusCode;
|
|
81
|
-
}
|
|
66
|
+
debug(`External API request to ${frame.docName}.${frame.method}`);
|
|
67
|
+
const headers = await shared.headers.get(result, apiImpl.headers, frame) || {};
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
// CASE: api ctrl wants to handle the express response (e.g. streams)
|
|
70
|
+
if (typeof result === 'function') {
|
|
71
|
+
debug('ctrl function call');
|
|
72
|
+
return result(req, res, next);
|
|
73
|
+
}
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
let statusCode = 200;
|
|
76
|
+
if (typeof apiImpl.statusCode === 'function') {
|
|
77
|
+
statusCode = apiImpl.statusCode(result);
|
|
78
|
+
} else if (apiImpl.statusCode) {
|
|
79
|
+
statusCode = apiImpl.statusCode;
|
|
80
|
+
}
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
if (format === 'plain') {
|
|
90
|
-
debug('plain text response');
|
|
91
|
-
return res.send(result);
|
|
92
|
-
}
|
|
82
|
+
res.status(statusCode);
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
84
|
+
// CASE: generate headers based on the api ctrl configuration
|
|
85
|
+
if (req && req.headers && req.headers['accept-version'] && res.locals) {
|
|
86
|
+
headers['content-version'] = `v${res.locals.safeVersion}`;
|
|
87
|
+
}
|
|
88
|
+
res.set(headers);
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
const send = (format) => {
|
|
91
|
+
if (format === 'plain') {
|
|
92
|
+
debug('plain text response');
|
|
93
|
+
return res.send(result);
|
|
94
|
+
}
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
debug('json response');
|
|
97
|
+
res.json(result || {});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let responseFormat;
|
|
101
|
+
|
|
102
|
+
if (apiImpl.response){
|
|
103
|
+
if (typeof apiImpl.response.format === 'function') {
|
|
104
|
+
const apiResponseFormat = apiImpl.response.format();
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} else {
|
|
109
|
-
responseFormat = apiResponseFormat;
|
|
110
|
-
}
|
|
106
|
+
if (apiResponseFormat.then) { // is promise
|
|
107
|
+
return apiResponseFormat.then((formatName) => {
|
|
108
|
+
send(formatName);
|
|
109
|
+
});
|
|
111
110
|
} else {
|
|
112
|
-
responseFormat =
|
|
111
|
+
responseFormat = apiResponseFormat;
|
|
113
112
|
}
|
|
113
|
+
} else {
|
|
114
|
+
responseFormat = apiImpl.response.format;
|
|
114
115
|
}
|
|
116
|
+
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
118
|
+
send(responseFormat);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
req.frameOptions = {
|
|
121
|
+
docName: frame.docName,
|
|
122
|
+
method: frame.method
|
|
123
|
+
};
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
next(err);
|
|
126
|
+
}
|
|
126
127
|
};
|
|
127
128
|
};
|
|
128
129
|
|
|
@@ -67,6 +67,19 @@ module.exports.input = (apiConfig, apiSerializers, frame) => {
|
|
|
67
67
|
return sequence(tasks);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
const getBestMatchSerializer = function (apiSerializers, docName, method) {
|
|
71
|
+
if (apiSerializers[docName] && apiSerializers[docName][method]) {
|
|
72
|
+
debug(`Calling ${docName}.${method}`);
|
|
73
|
+
return apiSerializers[docName][method].bind(apiSerializers[docName]);
|
|
74
|
+
} else if (apiSerializers[docName] && apiSerializers[docName].all) {
|
|
75
|
+
debug(`Calling ${docName}.all`);
|
|
76
|
+
return apiSerializers[docName].all.bind(apiSerializers[docName]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
debug(`Returning as-is`);
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
|
|
70
83
|
/**
|
|
71
84
|
* @description Shared output serialization handler.
|
|
72
85
|
*
|
|
@@ -101,33 +114,19 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, frame) => {
|
|
|
101
114
|
});
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (apiSerializers[apiConfig.docName].all) {
|
|
107
|
-
tasks.push(function serialiseCustomAll() {
|
|
108
|
-
return apiSerializers[apiConfig.docName].all(response, apiConfig, frame);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
117
|
+
const customSerializer = getBestMatchSerializer(apiSerializers, apiConfig.docName, apiConfig.method);
|
|
118
|
+
const defaultSerializer = getBestMatchSerializer(apiSerializers, 'default', apiConfig.method);
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return apiSerializers.default.all(response, apiConfig, frame);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (apiSerializers.default[apiConfig.method]) {
|
|
127
|
-
tasks.push(function serializeDefaultMethod() {
|
|
128
|
-
return apiSerializers.default[apiConfig.method](response, apiConfig, frame);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
120
|
+
if (customSerializer) {
|
|
121
|
+
// CASE: custom serializer exists
|
|
122
|
+
tasks.push(function doCustomSerializer() {
|
|
123
|
+
return customSerializer(response, apiConfig, frame);
|
|
124
|
+
});
|
|
125
|
+
} else if (defaultSerializer) {
|
|
126
|
+
// CASE: Fall back to default serializer
|
|
127
|
+
tasks.push(function doDefaultSerializer() {
|
|
128
|
+
return defaultSerializer(response, apiConfig, frame);
|
|
129
|
+
});
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
if (apiSerializers.all && apiSerializers.all.after) {
|
|
@@ -15,6 +15,7 @@ const BACKUP_TABLES = [
|
|
|
15
15
|
'members_stripe_customers_subscriptions',
|
|
16
16
|
'migrations',
|
|
17
17
|
'migrations_lock',
|
|
18
|
+
'newsletters',
|
|
18
19
|
'oauth',
|
|
19
20
|
'permissions',
|
|
20
21
|
'permissions_roles',
|
|
@@ -39,6 +40,7 @@ const BACKUP_TABLES = [
|
|
|
39
40
|
'members_paid_subscription_events',
|
|
40
41
|
'members_subscribe_events',
|
|
41
42
|
'members_product_events',
|
|
43
|
+
'members_newsletters',
|
|
42
44
|
'offers',
|
|
43
45
|
'offer_redemptions'
|
|
44
46
|
];
|
|
@@ -65,6 +65,36 @@ function dropTables(names) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Creates a migration which will drop an existing table and then re-add a new table based on provided spec
|
|
70
|
+
* @param {string} name - table name
|
|
71
|
+
* @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
|
|
72
|
+
* this parameter MUST be present, otherwise @daniellockyer will hunt you down
|
|
73
|
+
*
|
|
74
|
+
* @returns {Object} migration object returning config/up/down properties
|
|
75
|
+
*/
|
|
76
|
+
function recreateTable(name, tableSpec) {
|
|
77
|
+
return createNonTransactionalMigration(
|
|
78
|
+
async function up(connection) {
|
|
79
|
+
const exists = await connection.schema.hasTable(name);
|
|
80
|
+
|
|
81
|
+
if (!exists) {
|
|
82
|
+
logging.warn(`Failed to drop table: ${name} - table does not exist`);
|
|
83
|
+
} else {
|
|
84
|
+
logging.info(`Dropping table: ${name}`);
|
|
85
|
+
await commands.deleteTable(name, connection);
|
|
86
|
+
logging.info(`Re-adding table: ${name}`);
|
|
87
|
+
await commands.createTable(name, connection, tableSpec);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async function down() {
|
|
91
|
+
// noop: we cannot go back to old table schema
|
|
92
|
+
logging.warn(`Ignoring rollback for table recreate: ${name}`);
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
68
98
|
/**
|
|
69
99
|
* Creates a migration which will add a permission to the database
|
|
70
100
|
*
|
|
@@ -178,7 +208,7 @@ function addPermissionToRole(config) {
|
|
|
178
208
|
return;
|
|
179
209
|
}
|
|
180
210
|
|
|
181
|
-
logging.
|
|
211
|
+
logging.info(`Adding permission(${config.permission}) to role(${config.role})`);
|
|
182
212
|
await connection('permissions_roles').insert({
|
|
183
213
|
id: ObjectId().toHexString(),
|
|
184
214
|
permission_id: permission.id,
|
|
@@ -375,7 +405,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
|
|
|
375
405
|
column,
|
|
376
406
|
dbIsInCorrectState: hasColumn => hasColumn === false,
|
|
377
407
|
operation: commands.dropColumn,
|
|
378
|
-
operationVerb: 'Removing'
|
|
408
|
+
operationVerb: 'Removing',
|
|
409
|
+
columnDefinition
|
|
379
410
|
})
|
|
380
411
|
);
|
|
381
412
|
}
|
|
@@ -462,6 +493,7 @@ function addSetting({key, value, type, group}) {
|
|
|
462
493
|
module.exports = {
|
|
463
494
|
addTable,
|
|
464
495
|
dropTables,
|
|
496
|
+
recreateTable,
|
|
465
497
|
addPermission,
|
|
466
498
|
addPermissionToRole,
|
|
467
499
|
addPermissionWithRoles,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ESLint Override Notice
|
|
2
|
+
// This file was named incorrectly and it didn't flag up in our eslint rules.
|
|
3
|
+
// The ESLint match-regex rule has now been updated to catch this, but this file has to be excluded.
|
|
4
|
+
/* eslint-disable ghost/filenames/match-regex */
|
|
5
|
+
|
|
6
|
+
const {addTable} = require('../../utils');
|
|
7
|
+
|
|
8
|
+
module.exports = addTable('newsletters', {
|
|
9
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
10
|
+
name: {type: 'string', maxlength: 191, nullable: false},
|
|
11
|
+
description: {type: 'string', maxlength: 2000, nullable: true},
|
|
12
|
+
sender_name: {type: 'string', maxlength: 191, nullable: false},
|
|
13
|
+
sender_email: {type: 'string', maxlength: 191, nullable: false, validations: {isEmail: true}},
|
|
14
|
+
sender_reply_to: {type: 'string', maxlength: 191, nullable: false, validations: {isEmail: true}},
|
|
15
|
+
default: {type: 'bool', nullable: false, defaultTo: false},
|
|
16
|
+
status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'active'},
|
|
17
|
+
recipient_filter: {
|
|
18
|
+
type: 'text',
|
|
19
|
+
maxlength: 1000000000,
|
|
20
|
+
nullable: false,
|
|
21
|
+
defaultTo: ''
|
|
22
|
+
},
|
|
23
|
+
subscribe_on_signup: {type: 'bool', nullable: false, defaultTo: false},
|
|
24
|
+
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
|
|
25
|
+
});
|
package/core/server/data/migrations/versions/4.42/2022-03-30-15-44-add-newsletter-permissions.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const {
|
|
2
|
+
addPermissionWithRoles,
|
|
3
|
+
combineTransactionalMigrations
|
|
4
|
+
} = require('../../utils');
|
|
5
|
+
|
|
6
|
+
module.exports = combineTransactionalMigrations(
|
|
7
|
+
addPermissionWithRoles({
|
|
8
|
+
name: 'Browse newsletters',
|
|
9
|
+
action: 'browse',
|
|
10
|
+
object: 'newsletter'
|
|
11
|
+
}, [
|
|
12
|
+
'Administrator'
|
|
13
|
+
]),
|
|
14
|
+
addPermissionWithRoles({
|
|
15
|
+
name: 'Add newsletters',
|
|
16
|
+
action: 'add',
|
|
17
|
+
object: 'newsletter'
|
|
18
|
+
}, [
|
|
19
|
+
'Administrator'
|
|
20
|
+
]),
|
|
21
|
+
addPermissionWithRoles({
|
|
22
|
+
name: 'Edit newsletters',
|
|
23
|
+
action: 'edit',
|
|
24
|
+
object: 'newsletter'
|
|
25
|
+
}, [
|
|
26
|
+
'Administrator'
|
|
27
|
+
])
|
|
28
|
+
);
|
package/core/server/data/migrations/versions/4.43/2022-03-28-19-26-recreate-newsletter-table.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const {recreateTable} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = recreateTable('newsletters', {
|
|
4
|
+
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
5
|
+
name: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
6
|
+
description: {type: 'string', maxlength: 2000, nullable: true},
|
|
7
|
+
slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
8
|
+
sender_name: {type: 'string', maxlength: 191, nullable: false},
|
|
9
|
+
sender_email: {type: 'string', maxlength: 191, nullable: true},
|
|
10
|
+
sender_reply_to: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'newsletter', validations: {isIn: ['newsletter', 'support']}},
|
|
11
|
+
status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'active'},
|
|
12
|
+
visibility: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
maxlength: 50,
|
|
15
|
+
nullable: false,
|
|
16
|
+
defaultTo: 'members'
|
|
17
|
+
},
|
|
18
|
+
subscribe_on_signup: {type: 'bool', nullable: false, defaultTo: true},
|
|
19
|
+
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0},
|
|
20
|
+
header_image: {type: 'string', maxlength: 2000, nullable: true},
|
|
21
|
+
show_header_icon: {type: 'bool', nullable: false, defaultTo: true},
|
|
22
|
+
show_header_title: {type: 'bool', nullable: false, defaultTo: true},
|
|
23
|
+
title_font_category: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'sans_serif', validations: {isIn: ['serif', 'sans_serif']}},
|
|
24
|
+
title_alignment: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'center', validations: {isIn: ['center', 'left']}},
|
|
25
|
+
show_feature_image: {type: 'bool', nullable: false, defaultTo: true},
|
|
26
|
+
body_font_category: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'sans_serif', validations: {isIn: ['serif', 'sans_serif']}},
|
|
27
|
+
footer_content: {type: 'text', maxlength: 1000000000, nullable: true},
|
|
28
|
+
show_badge: {type: 'bool', nullable: false, defaultTo: true}
|
|
29
|
+
});
|