Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

Commit ede8304

Browse files
committed
Add v2.5.0
1 parent 7418317 commit ede8304

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+556
-163
lines changed

core/built/assets/ghost.min-e3bf097e9324c3108e4d726878a169e0.js core/built/assets/ghost.min-a9c5dcce8b8513b4e711b19a2b5fd06d.js

+10-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/built/assets/vendor.min-77bca21f1e9cb03acfb0bd707f936352.js core/built/assets/vendor.min-3d45f4fcdb055b37c95a7c146cc49d44.js

+10-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/server/api/v2/authors.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const Promise = require('bluebird');
2+
const common = require('../../lib/common');
3+
const models = require('../../models');
4+
const ALLOWED_INCLUDES = ['count.posts'];
5+
6+
module.exports = {
7+
docName: 'authors',
8+
9+
browse: {
10+
options: [
11+
'include',
12+
'filter',
13+
'fields',
14+
'limit',
15+
'status',
16+
'order',
17+
'page'
18+
],
19+
validation: {
20+
options: {
21+
include: {
22+
values: ALLOWED_INCLUDES
23+
}
24+
}
25+
},
26+
permissions: true,
27+
query(frame) {
28+
return models.User.findPage(frame.options);
29+
}
30+
},
31+
32+
read: {
33+
options: [
34+
'include',
35+
'filter',
36+
'fields'
37+
],
38+
data: [
39+
'id',
40+
'slug',
41+
'status',
42+
'email',
43+
'role'
44+
],
45+
validation: {
46+
options: {
47+
include: {
48+
values: ALLOWED_INCLUDES
49+
}
50+
}
51+
},
52+
permissions: true,
53+
query(frame) {
54+
return models.User.findOne(frame.data, frame.options)
55+
.then((model) => {
56+
if (!model) {
57+
return Promise.reject(new common.errors.NotFoundError({
58+
message: common.i18n.t('errors.api.authors.notFound')
59+
}));
60+
}
61+
62+
return model;
63+
});
64+
}
65+
}
66+
};

core/server/api/v2/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,17 @@ module.exports = {
6969

7070
get preview() {
7171
return shared.pipeline(require('./preview'), localUtils);
72+
},
73+
74+
get oembed() {
75+
return shared.pipeline(require('./oembed'), localUtils);
76+
},
77+
78+
get slack() {
79+
return shared.pipeline(require('./slack'), localUtils);
80+
},
81+
82+
get authors() {
83+
return shared.pipeline(require('./authors'), localUtils);
7284
}
7385
};

core/server/api/v2/oembed.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
const common = require('../../lib/common');
2+
const {extract, hasProvider} = require('oembed-parser');
3+
const Promise = require('bluebird');
4+
const request = require('../../lib/request');
5+
const cheerio = require('cheerio');
6+
7+
const findUrlWithProvider = (url) => {
8+
let provider;
9+
10+
// build up a list of URL variations to test against because the oembed
11+
// providers list is not always up to date with scheme or www vs non-www
12+
let baseUrl = url.replace(/^\/\/|^https?:\/\/(?:www\.)?/, '');
13+
let testUrls = [
14+
`http://${baseUrl}`,
15+
`https://${baseUrl}`,
16+
`http://www.${baseUrl}`,
17+
`https://www.${baseUrl}`
18+
];
19+
20+
for (let testUrl of testUrls) {
21+
provider = hasProvider(testUrl);
22+
if (provider) {
23+
url = testUrl;
24+
break;
25+
}
26+
}
27+
28+
return {url, provider};
29+
};
30+
31+
const getOembedUrlFromHTML = (html) => {
32+
return cheerio('link[type="application/json+oembed"]', html).attr('href');
33+
};
34+
35+
module.exports = {
36+
docName: 'oembed',
37+
38+
read: {
39+
permissions: false,
40+
data: [
41+
'url'
42+
],
43+
options: [],
44+
query({data}) {
45+
let {url} = data;
46+
47+
if (!url || !url.trim()) {
48+
return Promise.reject(new common.errors.BadRequestError({
49+
message: common.i18n.t('errors.api.oembed.noUrlProvided')
50+
}));
51+
}
52+
53+
function unknownProvider() {
54+
return Promise.reject(new common.errors.ValidationError({
55+
message: common.i18n.t('errors.api.oembed.unknownProvider')
56+
}));
57+
}
58+
59+
function knownProvider(url) {
60+
return extract(url).catch((err) => {
61+
return Promise.reject(new common.errors.InternalServerError({
62+
message: err.message
63+
}));
64+
});
65+
}
66+
67+
let provider;
68+
({url, provider} = findUrlWithProvider(url));
69+
70+
if (provider) {
71+
return knownProvider(url);
72+
}
73+
74+
// see if the URL is a redirect to cater for shortened urls
75+
return request(url, {
76+
method: 'GET',
77+
timeout: 2 * 1000,
78+
followRedirect: true
79+
}).then((response) => {
80+
if (response.url !== url) {
81+
({url, provider} = findUrlWithProvider(response.url));
82+
return provider ? knownProvider(url) : unknownProvider();
83+
}
84+
85+
const oembedUrl = getOembedUrlFromHTML(response.body);
86+
87+
if (!oembedUrl) {
88+
return unknownProvider();
89+
}
90+
91+
return request(oembedUrl, {
92+
method: 'GET',
93+
json: true
94+
}).then((response) => {
95+
return response.body;
96+
});
97+
}).catch(() => {
98+
return unknownProvider();
99+
});
100+
}
101+
}
102+
};

core/server/api/v2/pages.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ module.exports = {
3939
'fields',
4040
'status',
4141
'formats',
42-
'debug'
42+
'debug',
43+
'absolute_urls'
4344
],
4445
data: [
4546
'id',

core/server/api/v2/posts.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ module.exports = {
1616
'limit',
1717
'order',
1818
'page',
19-
'debug'
19+
'debug',
20+
'absolute_urls'
2021
],
2122
validation: {
2223
options: {
@@ -42,7 +43,8 @@ module.exports = {
4243
'fields',
4344
'status',
4445
'formats',
45-
'debug'
46+
'debug',
47+
'absolute_urls'
4648
],
4749
data: [
4850
'id',

core/server/api/v2/slack.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const common = require('../../lib/common');
2+
3+
module.exports = {
4+
docName: 'slack',
5+
sendTest: {
6+
permissions: false,
7+
query() {
8+
common.events.emit('slack.test');
9+
}
10+
}
11+
};

core/server/api/v2/utils/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,16 @@ module.exports = {
99

1010
get validators() {
1111
return require('./validators');
12+
},
13+
14+
/**
15+
* TODO: We need to check for public context as permission stage overrides
16+
* the whole context object currently: https://github.com/TryGhost/Ghost/issues/10099
17+
*/
18+
isContentAPI: (frame) => {
19+
return !!(Object.keys(frame.options.context).length === 0 ||
20+
(!frame.options.context.user && frame.options.context.api_key_id) ||
21+
frame.options.context.public
22+
);
1223
}
1324
};

core/server/api/v2/utils/serializers/input/pages.js

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:pages');
22

3+
function removeMobiledocFormat(frame) {
4+
if (frame.options.formats && frame.options.formats.includes('mobiledoc')) {
5+
frame.options.formats = frame.options.formats.filter((format) => {
6+
return (format !== 'mobiledoc');
7+
});
8+
}
9+
}
10+
311
module.exports = {
412
browse(apiConfig, frame) {
513
debug('browse');
@@ -19,6 +27,7 @@ module.exports = {
1927
} else {
2028
frame.options.filter = 'page:true';
2129
}
30+
removeMobiledocFormat(frame);
2231

2332
debug(frame.options);
2433
},
@@ -27,6 +36,7 @@ module.exports = {
2736
debug('read');
2837

2938
frame.data.page = true;
39+
removeMobiledocFormat(frame);
3040

3141
debug(frame.options);
3242
}

core/server/api/v2/utils/serializers/input/posts.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
const _ = require('lodash');
22
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:posts');
33
const url = require('./utils/url');
4+
const utils = require('../../index');
5+
6+
function removeMobiledocFormat(frame) {
7+
if (frame.options.formats && frame.options.formats.includes('mobiledoc')) {
8+
frame.options.formats = frame.options.formats.filter((format) => {
9+
return (format !== 'mobiledoc');
10+
});
11+
}
12+
}
413

514
module.exports = {
615
browse(apiConfig, frame) {
716
debug('browse');
817

9-
if (!_.get(frame, 'options.context.user') && _.get(frame, 'options.context.api_key_id')) {
18+
// @TODO: `api_key_id` does not work long term, because it can be either a content or an admin api key?
19+
/**
20+
* ## current cases:
21+
* - context object is empty (functional call, content api access)
22+
* - api_key_id exists? content api access
23+
* - user exists? admin api access
24+
*/
25+
if (utils.isContentAPI(frame)) {
1026
// CASE: the content api endpoints for posts should only return non page type resources
1127
if (frame.options.filter) {
1228
if (frame.options.filter.match(/page:\w+\+?/)) {
@@ -21,6 +37,8 @@ module.exports = {
2137
} else {
2238
frame.options.filter = 'page:false';
2339
}
40+
// CASE: the content api endpoint for posts should not return mobiledoc
41+
removeMobiledocFormat(frame);
2442
}
2543

2644
debug(frame.options);
@@ -29,8 +47,17 @@ module.exports = {
2947
read(apiConfig, frame) {
3048
debug('read');
3149

32-
if (!_.get(frame, 'options.context.user') && _.get(frame, 'options.context.api_key_id')) {
50+
// @TODO: `api_key_id` does not work long term, because it can be either a content or an admin api key?
51+
/**
52+
* ## current cases:
53+
* - context object is empty (functional call, content api access)
54+
* - api_key_id exists? content api access
55+
* - user exists? admin api access
56+
*/
57+
if (utils.isContentAPI(frame)) {
3358
frame.data.page = false;
59+
// CASE: the content api endpoint for posts should not return mobiledoc
60+
removeMobiledocFormat(frame);
3461
}
3562

3663
debug(frame.options);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:authors');
2+
const mapper = require('./utils/mapper');
3+
4+
module.exports = {
5+
browse(models, apiConfig, frame) {
6+
debug('browse');
7+
8+
frame.response = {
9+
authors: models.data.map(model => mapper.mapUser(model, frame)),
10+
meta: models.meta
11+
};
12+
13+
debug(frame.response);
14+
},
15+
16+
read(model, apiConfig, frame) {
17+
debug('read');
18+
19+
frame.response = {
20+
authors: [mapper.mapUser(model, frame)]
21+
};
22+
23+
debug(frame.response);
24+
}
25+
};

core/server/api/v2/utils/serializers/output/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,13 @@ module.exports = {
5757

5858
get preview() {
5959
return require('./preview');
60+
},
61+
62+
get oembed() {
63+
return require('./oembed');
64+
},
65+
66+
get authors() {
67+
return require('./authors');
6068
}
6169
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:oembed');
2+
3+
module.exports = {
4+
all(res, apiConfig, frame) {
5+
debug('all');
6+
frame.response = res;
7+
debug(frame.response);
8+
}
9+
};

0 commit comments

Comments
 (0)