ghost 5.8.1 → 5.9.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/components/tryghost-adapter-manager-0.0.0.tgz +0 -0
- package/components/tryghost-api-framework-0.0.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-0.0.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-job-manager-0.0.0.tgz +0 -0
- package/components/tryghost-mailgun-client-0.0.0.tgz +0 -0
- package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
- package/components/tryghost-members-api-0.0.0.tgz +0 -0
- package/components/tryghost-members-importer-0.0.0.tgz +0 -0
- package/components/tryghost-members-offers-0.0.0.tgz +0 -0
- package/components/tryghost-members-payments-0.0.0.tgz +0 -0
- package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
- package/components/tryghost-minifier-0.0.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-0.0.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-0.0.0.tgz +0 -0
- package/components/tryghost-package-json-0.0.0.tgz +0 -0
- package/components/tryghost-session-service-0.0.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-0.0.0.tgz +0 -0
- package/components/tryghost-update-check-service-0.0.0.tgz +0 -0
- package/content/themes/casper/README.md +1 -1
- package/content/themes/casper/assets/built/portal.min.js +3 -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 +119 -37
- package/content/themes/casper/package.json +3 -3
- package/content/themes/casper/partials/post-card.hbs +3 -1
- package/content/themes/casper/post.hbs +7 -7
- package/content/themes/casper/yarn.lock +69 -68
- package/core/boot.js +2 -0
- package/core/built/admin/assets/{chunk.143.904e017ad397bb681e9d.js → chunk.143.e35fdb482bc822313f0c.js} +5 -5
- package/core/built/admin/assets/{chunk.178.9541bdb92bded29cd60d.js → chunk.178.1d381d687652f2597fe2.js} +4 -4
- package/core/built/admin/assets/{chunk.351.cbc224ca65c14ef5322d.js → chunk.351.73f27952f867334a8228.js} +3 -3
- package/core/built/admin/assets/{chunk.351.cbc224ca65c14ef5322d.js.LICENSE.txt → chunk.351.73f27952f867334a8228.js.LICENSE.txt} +0 -0
- package/core/built/admin/assets/{ghost-b469423d0fbe5e40af17b560f7e3cead.css → ghost-686c383caa6a3469cefb939ab10e21b6.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-bcb6f4517a2dfe23a0a280632bfca00c.css → ghost-dark-6814c399ff5b3d9c8efe2d92bc7ec779.css} +1 -1
- package/core/built/admin/assets/{ghost-a66a04418efe85083a3adca0fb16bb52.js → ghost-eca1a709a74b1af277e48aad4e16c9db.js} +94 -92
- package/core/built/admin/assets/{vendor-46baf13852f545c6c89756c8e0ccbff2.js → vendor-516c9e43b4aeb92079dc1ab92c9ce492.js} +3 -3
- package/core/built/admin/index.html +6 -6
- package/core/frontend/helpers/comment_count.js +7 -15
- package/core/frontend/helpers/comments.js +4 -16
- package/core/frontend/helpers/ghost_head.js +1 -1
- package/core/frontend/public/ghost.min.css +1 -1
- package/core/frontend/src/comment-counts/js/comment-counts.js +12 -1
- package/core/server/api/endpoints/comments-members.js +23 -1
- package/core/server/api/endpoints/index.js +52 -52
- package/core/server/api/endpoints/utils/serializers/input/comments.js +18 -0
- package/core/server/api/endpoints/utils/serializers/input/db.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/index.js +4 -0
- package/core/server/api/endpoints/utils/serializers/input/integrations.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/tiers.js +1 -1
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-filter-type-group-mapper.js +0 -0
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-key-group-mapper.js +0 -0
- package/core/server/api/{shared → endpoints/utils}/serializers/input/utils/settings-key-type-mapper.js +0 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +12 -12
- package/core/server/api/endpoints/utils/serializers/output/tiers.js +1 -1
- package/core/server/api/index.js +0 -2
- package/core/server/data/db/connection.js +0 -4
- package/core/server/data/importer/importers/data/settings.js +2 -2
- package/core/server/data/migrations/versions/5.9/2022-08-09-08-32-added-new-integration-type.js +24 -0
- package/core/server/data/schema/clients/mysql.js +0 -15
- package/core/server/data/schema/commands.js +0 -9
- package/core/server/data/schema/fixtures/fixtures.json +1 -1
- package/core/server/data/schema/schema.js +3 -3
- package/core/server/models/base/plugins/user-type.js +1 -9
- package/core/server/models/comment.js +96 -15
- package/core/server/models/label.js +14 -0
- package/core/server/models/newsletter.js +21 -0
- package/core/server/models/stripe-customer-subscription.js +6 -0
- package/core/server/models/tag.js +20 -0
- package/core/server/models/user.js +20 -0
- package/core/server/run-update-check.js +1 -1
- package/core/server/services/auth/api-key/admin.js +1 -1
- package/core/server/services/auth/api-key/content.js +1 -1
- package/core/server/services/bulk-email/bulk-email-processor.js +18 -11
- package/core/server/services/bulk-email/index.js +1 -17
- package/core/server/services/comments/controller.js +9 -0
- package/core/server/services/comments/email-templates/new-comment-reply.hbs +2 -2
- package/core/server/services/comments/email-templates/new-comment.hbs +2 -2
- package/core/server/services/comments/email-templates/report.hbs +2 -2
- package/core/server/services/comments/service.js +16 -3
- package/core/server/services/comments/stats.js +2 -0
- package/core/server/services/email-analytics/jobs/fetch-latest.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/permissions/can-this.js +154 -161
- package/core/server/services/permissions/parse-context.js +1 -8
- package/core/server/services/webhooks/serialize.js +3 -3
- package/core/server/web/api/endpoints/admin/routes.js +1 -1
- package/core/server/web/api/endpoints/content/routes.js +1 -1
- package/core/server/web/api/testmode/jobs/graceful-job.js +1 -1
- package/core/server/web/comments/routes.js +2 -1
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/labs.js +0 -1
- package/package.json +31 -29
- package/yarn.lock +330 -276
- package/core/server/api/README.md +0 -130
- package/core/server/api/shared/frame.js +0 -95
- package/core/server/api/shared/headers.js +0 -152
- package/core/server/api/shared/http.js +0 -127
- package/core/server/api/shared/index.js +0 -25
- package/core/server/api/shared/pipeline.js +0 -259
- package/core/server/api/shared/serializers/handle.js +0 -140
- package/core/server/api/shared/serializers/index.js +0 -13
- package/core/server/api/shared/serializers/input/all.js +0 -41
- package/core/server/api/shared/serializers/input/index.js +0 -5
- package/core/server/api/shared/serializers/output/index.js +0 -1
- package/core/server/api/shared/utils/index.js +0 -5
- package/core/server/api/shared/utils/options.js +0 -23
- package/core/server/api/shared/validators/handle.js +0 -68
- package/core/server/api/shared/validators/index.js +0 -9
- package/core/server/api/shared/validators/input/all.js +0 -213
- package/core/server/api/shared/validators/input/index.js +0 -5
- package/core/server/services/bulk-email/mailgun.js +0 -122
- package/core/server/web/shared/middleware/cache-control.js +0 -43
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# API
|
|
2
|
-
|
|
3
|
-
## Stages
|
|
4
|
-
|
|
5
|
-
Each request goes through the following stages:
|
|
6
|
-
|
|
7
|
-
- input validation
|
|
8
|
-
- input serialisation
|
|
9
|
-
- permissions
|
|
10
|
-
- query
|
|
11
|
-
- output serialisation
|
|
12
|
-
|
|
13
|
-
The framework we are building pipes a request through these stages in respect of the API controller configuration.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## Frame
|
|
17
|
-
|
|
18
|
-
Is a class, which holds all the information for request processing. We pass this instance by reference.
|
|
19
|
-
Each function can modify the original instance. No need to return the class instance.
|
|
20
|
-
|
|
21
|
-
### Structure
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
{
|
|
25
|
-
original: Object,
|
|
26
|
-
options: Object,
|
|
27
|
-
data: Object,
|
|
28
|
-
user: Object,
|
|
29
|
-
file: Object,
|
|
30
|
-
files: Array
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Example
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
{
|
|
38
|
-
original: {
|
|
39
|
-
include: 'tags'
|
|
40
|
-
},
|
|
41
|
-
options: {
|
|
42
|
-
withRelated: ['tags']
|
|
43
|
-
},
|
|
44
|
-
data: {
|
|
45
|
-
posts: []
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## API Controller
|
|
51
|
-
|
|
52
|
-
A controller is no longer just a function, it's a set of configurations.
|
|
53
|
-
|
|
54
|
-
### Structure
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
edit: function || object
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
edit: {
|
|
62
|
-
headers: object,
|
|
63
|
-
options: Array,
|
|
64
|
-
data: Array,
|
|
65
|
-
validation: object | function,
|
|
66
|
-
permissions: boolean | object | function,
|
|
67
|
-
query: function
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Examples
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
edit: {
|
|
76
|
-
headers: {
|
|
77
|
-
cacheInvalidate: true
|
|
78
|
-
},
|
|
79
|
-
// Allowed url/query params
|
|
80
|
-
options: ['include']
|
|
81
|
-
// Url/query param validation configuration
|
|
82
|
-
validation: {
|
|
83
|
-
options: {
|
|
84
|
-
include: {
|
|
85
|
-
required: true,
|
|
86
|
-
values: ['tags']
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
permissions: true,
|
|
91
|
-
// Returns a model response!
|
|
92
|
-
query(frame) {
|
|
93
|
-
return models.Post.edit(frame.data, frame.options);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
read: {
|
|
100
|
-
// Allowed url/query params, which will be remembered inside `frame.data`
|
|
101
|
-
// This is helpful for READ requests e.g. `model.findOne(frame.data, frame.options)`.
|
|
102
|
-
// Our model layer requires sending the where clauses as first parameter.
|
|
103
|
-
data: ['slug']
|
|
104
|
-
validation: {
|
|
105
|
-
data: {
|
|
106
|
-
slug: {
|
|
107
|
-
values: ['eins']
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
permissions: true,
|
|
112
|
-
query(frame) {
|
|
113
|
-
return models.Post.findOne(frame.data, frame.options);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
edit: {
|
|
120
|
-
validation() {
|
|
121
|
-
// custom validation, skip framework
|
|
122
|
-
},
|
|
123
|
-
permissions: {
|
|
124
|
-
unsafeAttrs: ['author']
|
|
125
|
-
},
|
|
126
|
-
query(frame) {
|
|
127
|
-
return models.Post.edit(frame.data, frame.options);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
const debug = require('@tryghost/debug')('api:shared:frame');
|
|
2
|
-
const _ = require('lodash');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @description The "frame" holds all information of a request.
|
|
6
|
-
*
|
|
7
|
-
* Each party can modify the frame by reference.
|
|
8
|
-
* A request hits a lot of stages in the API implementation and that's why modification by reference was the
|
|
9
|
-
* easiest to use. We always have access to the original input, we never loose track of it.
|
|
10
|
-
*/
|
|
11
|
-
class Frame {
|
|
12
|
-
constructor(obj = {}) {
|
|
13
|
-
this.original = obj;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* options: Query params, url params, context and custom options
|
|
17
|
-
* data: Body or if the ctrl wants query/url params inside body
|
|
18
|
-
* user: Logged in user
|
|
19
|
-
* file: Uploaded file
|
|
20
|
-
* files: Uploaded files
|
|
21
|
-
* apiType: Content or admin api access
|
|
22
|
-
*/
|
|
23
|
-
this.options = {};
|
|
24
|
-
this.data = {};
|
|
25
|
-
this.user = {};
|
|
26
|
-
this.file = {};
|
|
27
|
-
this.files = [];
|
|
28
|
-
this.apiType = null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @description Configure the frame.
|
|
33
|
-
*
|
|
34
|
-
* If you instantiate a new frame, all the data you pass in, land in `this.original`. This is helpful
|
|
35
|
-
* for debugging to see what the original input was.
|
|
36
|
-
*
|
|
37
|
-
* This function will prepare the incoming data for further processing.
|
|
38
|
-
* Based on the API ctrl implemented, this fn will pick allowed properties to either options or data.
|
|
39
|
-
*/
|
|
40
|
-
configure(apiConfig) {
|
|
41
|
-
debug('configure');
|
|
42
|
-
|
|
43
|
-
if (apiConfig.options) {
|
|
44
|
-
if (typeof apiConfig.options === 'function') {
|
|
45
|
-
apiConfig.options = apiConfig.options(this);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'query')) {
|
|
49
|
-
Object.assign(this.options, _.pick(this.original.query, apiConfig.options));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'params')) {
|
|
53
|
-
Object.assign(this.options, _.pick(this.original.params, apiConfig.options));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'options')) {
|
|
57
|
-
Object.assign(this.options, _.pick(this.original.options, apiConfig.options));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.options.context = this.original.context;
|
|
62
|
-
|
|
63
|
-
if (this.original.body && Object.keys(this.original.body).length) {
|
|
64
|
-
this.data = _.cloneDeep(this.original.body);
|
|
65
|
-
} else {
|
|
66
|
-
if (apiConfig.data) {
|
|
67
|
-
if (typeof apiConfig.data === 'function') {
|
|
68
|
-
apiConfig.data = apiConfig.data(this);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'query')) {
|
|
72
|
-
Object.assign(this.data, _.pick(this.original.query, apiConfig.data));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'params')) {
|
|
76
|
-
Object.assign(this.data, _.pick(this.original.params, apiConfig.data));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (Object.prototype.hasOwnProperty.call(this.original, 'options')) {
|
|
80
|
-
Object.assign(this.data, _.pick(this.original.options, apiConfig.data));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.user = this.original.user;
|
|
86
|
-
this.file = this.original.file;
|
|
87
|
-
this.files = this.original.files;
|
|
88
|
-
|
|
89
|
-
debug('original', this.original);
|
|
90
|
-
debug('options', this.options);
|
|
91
|
-
debug('data', this.data);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
module.exports = Frame;
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
const url = require('url');
|
|
2
|
-
const debug = require('@tryghost/debug')('api:shared:headers');
|
|
3
|
-
const Promise = require('bluebird');
|
|
4
|
-
const INVALIDATE_ALL = '/*';
|
|
5
|
-
|
|
6
|
-
const cacheInvalidate = (result, options = {}) => {
|
|
7
|
-
let value = options.value;
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
'X-Cache-Invalidate': value || INVALIDATE_ALL
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const disposition = {
|
|
15
|
-
/**
|
|
16
|
-
* @description Generate CSV header.
|
|
17
|
-
*
|
|
18
|
-
* @param {Object} result - API response
|
|
19
|
-
* @param {Object} options
|
|
20
|
-
* @return {Object}
|
|
21
|
-
*/
|
|
22
|
-
csv(result, options = {}) {
|
|
23
|
-
let value = options.value;
|
|
24
|
-
|
|
25
|
-
if (typeof options.value === 'function') {
|
|
26
|
-
value = options.value();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
'Content-Disposition': `Attachment; filename="${value}"`,
|
|
31
|
-
'Content-Type': 'text/csv'
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @description Generate JSON header.
|
|
37
|
-
*
|
|
38
|
-
* @param {Object} result - API response
|
|
39
|
-
* @param {Object} options
|
|
40
|
-
* @return {Object}
|
|
41
|
-
*/
|
|
42
|
-
json(result, options = {}) {
|
|
43
|
-
return {
|
|
44
|
-
'Content-Disposition': `Attachment; filename="${options.value}"`,
|
|
45
|
-
'Content-Type': 'application/json',
|
|
46
|
-
'Content-Length': Buffer.byteLength(JSON.stringify(result))
|
|
47
|
-
};
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @description Generate YAML header.
|
|
52
|
-
*
|
|
53
|
-
* @param {Object} result - API response
|
|
54
|
-
* @param {Object} options
|
|
55
|
-
* @return {Object}
|
|
56
|
-
*/
|
|
57
|
-
yaml(result, options = {}) {
|
|
58
|
-
return {
|
|
59
|
-
'Content-Disposition': `Attachment; filename="${options.value}"`,
|
|
60
|
-
'Content-Type': 'application/yaml',
|
|
61
|
-
'Content-Length': Buffer.byteLength(JSON.stringify(result))
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @description Content Disposition Header
|
|
67
|
-
*
|
|
68
|
-
* Create a header that invokes the 'Save As' dialog in the browser when exporting the database to file. The 'filename'
|
|
69
|
-
* parameter is governed by [RFC6266](http://tools.ietf.org/html/rfc6266#section-4.3).
|
|
70
|
-
*
|
|
71
|
-
* For encoding whitespace and non-ISO-8859-1 characters, you MUST use the "filename*=" attribute, NOT "filename=".
|
|
72
|
-
* Ideally, both. Examples: http://tools.ietf.org/html/rfc6266#section-5
|
|
73
|
-
*
|
|
74
|
-
* We'll use ISO-8859-1 characters here to keep it simple.
|
|
75
|
-
*
|
|
76
|
-
* @see http://tools.ietf.org/html/rfc598
|
|
77
|
-
*/
|
|
78
|
-
file(result, options = {}) {
|
|
79
|
-
return Promise.resolve()
|
|
80
|
-
.then(() => {
|
|
81
|
-
let value = options.value;
|
|
82
|
-
|
|
83
|
-
if (typeof options.value === 'function') {
|
|
84
|
-
value = options.value();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return value;
|
|
88
|
-
})
|
|
89
|
-
.then((filename) => {
|
|
90
|
-
return {
|
|
91
|
-
'Content-Disposition': `Attachment; filename="${filename}"`
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
module.exports = {
|
|
98
|
-
/**
|
|
99
|
-
* @description Get header based on ctrl configuration.
|
|
100
|
-
*
|
|
101
|
-
* @param {Object} result - API response
|
|
102
|
-
* @param {Object} apiConfigHeaders
|
|
103
|
-
* @param {Object} frame
|
|
104
|
-
* @return {Promise}
|
|
105
|
-
*/
|
|
106
|
-
async get(result, apiConfigHeaders = {}, frame) {
|
|
107
|
-
let headers = {};
|
|
108
|
-
|
|
109
|
-
if (apiConfigHeaders.disposition) {
|
|
110
|
-
const dispositionHeader = await disposition[apiConfigHeaders.disposition.type](result, apiConfigHeaders.disposition);
|
|
111
|
-
|
|
112
|
-
if (dispositionHeader) {
|
|
113
|
-
Object.assign(headers, dispositionHeader);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (apiConfigHeaders.cacheInvalidate) {
|
|
118
|
-
const cacheInvalidationHeader = cacheInvalidate(result, apiConfigHeaders.cacheInvalidate);
|
|
119
|
-
|
|
120
|
-
if (cacheInvalidationHeader) {
|
|
121
|
-
Object.assign(headers, cacheInvalidationHeader);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const locationHeaderDisabled = apiConfigHeaders && apiConfigHeaders.location === false;
|
|
126
|
-
const hasFrameData = frame
|
|
127
|
-
&& (frame.method === 'add')
|
|
128
|
-
&& result[frame.docName]
|
|
129
|
-
&& result[frame.docName][0]
|
|
130
|
-
&& result[frame.docName][0].id;
|
|
131
|
-
|
|
132
|
-
if (!locationHeaderDisabled && hasFrameData) {
|
|
133
|
-
const protocol = (frame.original.url.secure === false) ? 'http://' : 'https://';
|
|
134
|
-
const resourceId = result[frame.docName][0].id;
|
|
135
|
-
|
|
136
|
-
let locationURL = url.resolve(`${protocol}${frame.original.url.host}`,frame.original.url.pathname);
|
|
137
|
-
if (!locationURL.endsWith('/')) {
|
|
138
|
-
locationURL += '/';
|
|
139
|
-
}
|
|
140
|
-
locationURL += `${resourceId}/`;
|
|
141
|
-
|
|
142
|
-
const locationHeader = {
|
|
143
|
-
Location: locationURL
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
Object.assign(headers, locationHeader);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
debug(headers);
|
|
150
|
-
return headers;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
const url = require('url');
|
|
2
|
-
const debug = require('@tryghost/debug')('api:shared:http');
|
|
3
|
-
const shared = require('../shared');
|
|
4
|
-
const models = require('../../models');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @description HTTP wrapper.
|
|
8
|
-
*
|
|
9
|
-
* This wrapper is used in the routes definition (see web/).
|
|
10
|
-
* The wrapper receives the express request, prepares the frame and forwards the request to the pipeline.
|
|
11
|
-
*
|
|
12
|
-
* @param {Function} apiImpl - Pipeline wrapper, which executes the target ctrl function.
|
|
13
|
-
* @return {Function}
|
|
14
|
-
*/
|
|
15
|
-
const http = (apiImpl) => {
|
|
16
|
-
return async (req, res, next) => {
|
|
17
|
-
debug(`External API request to ${req.url}`);
|
|
18
|
-
let apiKey = null;
|
|
19
|
-
let integration = null;
|
|
20
|
-
let user = null;
|
|
21
|
-
|
|
22
|
-
if (req.api_key) {
|
|
23
|
-
apiKey = {
|
|
24
|
-
id: req.api_key.get('id'),
|
|
25
|
-
type: req.api_key.get('type')
|
|
26
|
-
};
|
|
27
|
-
integration = {
|
|
28
|
-
id: req.api_key.get('integration_id')
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// NOTE: "external user" is only used in the subscriber app. External user is ID "0".
|
|
33
|
-
if ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) {
|
|
34
|
-
user = req.user.id;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const frame = new shared.Frame({
|
|
38
|
-
body: req.body,
|
|
39
|
-
file: req.file,
|
|
40
|
-
files: req.files,
|
|
41
|
-
query: req.query,
|
|
42
|
-
params: req.params,
|
|
43
|
-
user: req.user,
|
|
44
|
-
session: req.session,
|
|
45
|
-
url: {
|
|
46
|
-
host: req.vhost ? req.vhost.host : req.get('host'),
|
|
47
|
-
pathname: url.parse(req.originalUrl || req.url).pathname,
|
|
48
|
-
secure: req.secure
|
|
49
|
-
},
|
|
50
|
-
context: {
|
|
51
|
-
api_key: apiKey,
|
|
52
|
-
user: user,
|
|
53
|
-
integration: integration,
|
|
54
|
-
member: (req.member || null)
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
frame.configure({
|
|
59
|
-
options: apiImpl.options,
|
|
60
|
-
data: apiImpl.data
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const result = await apiImpl(frame);
|
|
65
|
-
|
|
66
|
-
debug(`External API request to ${frame.docName}.${frame.method}`);
|
|
67
|
-
const headers = await shared.headers.get(result, apiImpl.headers, frame) || {};
|
|
68
|
-
|
|
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
|
-
}
|
|
74
|
-
|
|
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
|
-
}
|
|
81
|
-
|
|
82
|
-
res.status(statusCode);
|
|
83
|
-
|
|
84
|
-
// CASE: generate headers based on the api ctrl configuration
|
|
85
|
-
res.set(headers);
|
|
86
|
-
|
|
87
|
-
const send = (format) => {
|
|
88
|
-
if (format === 'plain') {
|
|
89
|
-
debug('plain text response');
|
|
90
|
-
return res.send(result);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
debug('json response');
|
|
94
|
-
res.json(result || {});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
let responseFormat;
|
|
98
|
-
|
|
99
|
-
if (apiImpl.response){
|
|
100
|
-
if (typeof apiImpl.response.format === 'function') {
|
|
101
|
-
const apiResponseFormat = apiImpl.response.format();
|
|
102
|
-
|
|
103
|
-
if (apiResponseFormat.then) { // is promise
|
|
104
|
-
return apiResponseFormat.then((formatName) => {
|
|
105
|
-
send(formatName);
|
|
106
|
-
});
|
|
107
|
-
} else {
|
|
108
|
-
responseFormat = apiResponseFormat;
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
responseFormat = apiImpl.response.format;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
send(responseFormat);
|
|
116
|
-
} catch (err) {
|
|
117
|
-
req.frameOptions = {
|
|
118
|
-
docName: frame.docName,
|
|
119
|
-
method: frame.method
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
next(err);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
module.exports = http;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
get headers() {
|
|
3
|
-
return require('./headers');
|
|
4
|
-
},
|
|
5
|
-
|
|
6
|
-
get http() {
|
|
7
|
-
return require('./http');
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
get Frame() {
|
|
11
|
-
return require('./frame');
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
get pipeline() {
|
|
15
|
-
return require('./pipeline');
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
get validators() {
|
|
19
|
-
return require('./validators');
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
get serializers() {
|
|
23
|
-
return require('./serializers');
|
|
24
|
-
}
|
|
25
|
-
};
|