mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 05:50:35 +03:00
Optimized usage of urls in API v2
refs #9866 - Extracted url decoration logic to utility in output serializers in posts, pages, users, and tags - Added test cases for url usage by child object (tags of posts)
This commit is contained in:
parent
2fbc5aa257
commit
d582c06eee
@ -1,76 +1,5 @@
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:pages');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
// @TODO: refactor if we add users+tags controllers
|
||||
const urlsForUser = (user) => {
|
||||
user.url = urlService.getUrlByResourceId(user.id, {absolute: true});
|
||||
|
||||
if (user.profile_image) {
|
||||
user.profile_image = urlService.utils.urlFor('image', {image: user.profile_image}, true);
|
||||
}
|
||||
|
||||
if (user.cover_image) {
|
||||
user.cover_image = urlService.utils.urlFor('image', {image: user.cover_image}, true);
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
const urlsForTag = (tag) => {
|
||||
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
|
||||
|
||||
if (tag.feature_image) {
|
||||
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
|
||||
// @TODO: Update the url decoration in https://github.com/TryGhost/Ghost/pull/9969.
|
||||
const absoluteUrls = (attrs, options) => {
|
||||
attrs.url = urlService.getUrlByResourceId(attrs.id, {absolute: true});
|
||||
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.og_image) {
|
||||
attrs.og_image = urlService.utils.urlFor('image', {image: attrs.og_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.twitter_image) {
|
||||
attrs.twitter_image = urlService.utils.urlFor('image', {image: attrs.twitter_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.html) {
|
||||
attrs.html = urlService.utils.makeAbsoluteUrls(attrs.html, urlService.utils.urlFor('home', true), attrs.url).html();
|
||||
}
|
||||
|
||||
if (options.columns && !options.columns.includes('url')) {
|
||||
delete attrs.url;
|
||||
}
|
||||
|
||||
if (options && options.withRelated) {
|
||||
options.withRelated.forEach((relation) => {
|
||||
// @NOTE: this block also decorates primary_tag/primary_author objects as they
|
||||
// are being passed by reference in tags/authors. Might be refactored into more explicit call
|
||||
// in the future, but is good enough for current use-case
|
||||
if (relation === 'tags' && attrs.tags) {
|
||||
attrs.tags = attrs.tags.map(tag => urlsForTag(tag));
|
||||
}
|
||||
|
||||
if (relation === 'author' && attrs.author) {
|
||||
attrs.author = urlsForUser(attrs.author);
|
||||
}
|
||||
|
||||
if (relation === 'authors' && attrs.authors) {
|
||||
attrs.authors = attrs.authors.map(author => urlsForUser(author));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
@ -78,7 +7,7 @@ module.exports = {
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
pages: models.data.map(model => absoluteUrls(model.toJSON(frame.options), frame.options)),
|
||||
pages: models.data.map(model => url.forPost(model.id, model.toJSON(frame.options), frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
@ -86,7 +15,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
pages: [absoluteUrls(models.toJSON(frame.options), frame.options)]
|
||||
pages: [url.forPost(models.id, models.toJSON(frame.options), frame.options)]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
|
@ -1,76 +1,5 @@
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:posts');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
// @TODO: refactor if we add users+tags controllers
|
||||
const urlsForUser = (user) => {
|
||||
user.url = urlService.getUrlByResourceId(user.id, {absolute: true});
|
||||
|
||||
if (user.profile_image) {
|
||||
user.profile_image = urlService.utils.urlFor('image', {image: user.profile_image}, true);
|
||||
}
|
||||
|
||||
if (user.cover_image) {
|
||||
user.cover_image = urlService.utils.urlFor('image', {image: user.cover_image}, true);
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
const urlsForTag = (tag) => {
|
||||
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
|
||||
|
||||
if (tag.feature_image) {
|
||||
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
|
||||
// @TODO: Update the url decoration in https://github.com/TryGhost/Ghost/pull/9969.
|
||||
const absoluteUrls = (attrs, options) => {
|
||||
attrs.url = urlService.getUrlByResourceId(attrs.id, {absolute: true});
|
||||
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.og_image) {
|
||||
attrs.og_image = urlService.utils.urlFor('image', {image: attrs.og_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.twitter_image) {
|
||||
attrs.twitter_image = urlService.utils.urlFor('image', {image: attrs.twitter_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.html) {
|
||||
attrs.html = urlService.utils.makeAbsoluteUrls(attrs.html, urlService.utils.urlFor('home', true), attrs.url).html();
|
||||
}
|
||||
|
||||
if (options.columns && !options.columns.includes('url')) {
|
||||
delete attrs.url;
|
||||
}
|
||||
|
||||
if (options && options.withRelated) {
|
||||
options.withRelated.forEach((relation) => {
|
||||
// @NOTE: this block also decorates primary_tag/primary_author objects as they
|
||||
// are being passed by reference in tags/authors. Might be refactored into more explicit call
|
||||
// in the future, but is good enough for current use-case
|
||||
if (relation === 'tags' && attrs.tags) {
|
||||
attrs.tags = attrs.tags.map(tag => urlsForTag(tag));
|
||||
}
|
||||
|
||||
if (relation === 'author' && attrs.author) {
|
||||
attrs.author = urlsForUser(attrs.author);
|
||||
}
|
||||
|
||||
if (relation === 'authors' && attrs.authors) {
|
||||
attrs.authors = attrs.authors.map(author => urlsForUser(author));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
@ -83,7 +12,7 @@ module.exports = {
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
posts: models.data.map(model => absoluteUrls(model.toJSON(frame.options), frame.options)),
|
||||
posts: models.data.map(model => url.forPost(model.id, model.toJSON(frame.options), frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
@ -92,7 +21,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
posts: [absoluteUrls(models.toJSON(frame.options), frame.options)]
|
||||
posts: [url.forPost(models.id, models.toJSON(frame.options), frame.options)]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
|
@ -1,15 +1,5 @@
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:tags');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
const absoluteUrls = (tag) => {
|
||||
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
|
||||
|
||||
if (tag.feature_image) {
|
||||
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
@ -21,7 +11,7 @@ module.exports = {
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
tags: models.data.map(model => absoluteUrls(model.toJSON(frame.options))),
|
||||
tags: models.data.map(model => url.forTag(model.id, model.toJSON(frame.options), frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
@ -29,7 +19,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
tags: [absoluteUrls(models.toJSON(frame.options))]
|
||||
tags: [url.forTag(models.id, models.toJSON(frame.options), frame.options)]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
|
@ -1,27 +1,13 @@
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:users');
|
||||
const common = require('../../../../../lib/common');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
const absoluteUrls = (user) => {
|
||||
user.url = urlService.getUrlByResourceId(user.id, {absolute: true});
|
||||
|
||||
if (user.profile_image) {
|
||||
user.profile_image = urlService.utils.urlFor('image', {image: user.profile_image}, true);
|
||||
}
|
||||
|
||||
if (user.cover_image) {
|
||||
user.cover_image = urlService.utils.urlFor('image', {image: user.cover_image}, true);
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
browse(models, apiConfig, frame) {
|
||||
debug('browse');
|
||||
|
||||
frame.response = {
|
||||
users: models.data.map(model => absoluteUrls(model.toJSON(frame.options))),
|
||||
users: models.data.map(model => url.forUser(model.id, model.toJSON(frame.options), frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
@ -32,7 +18,7 @@ module.exports = {
|
||||
debug('read');
|
||||
|
||||
frame.response = {
|
||||
users: [absoluteUrls(model.toJSON(frame.options))]
|
||||
users: [url.forUser(model.id, model.toJSON(frame.options), frame.options)]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
|
75
core/server/api/v2/utils/serializers/output/utils/url.js
Normal file
75
core/server/api/v2/utils/serializers/output/utils/url.js
Normal file
@ -0,0 +1,75 @@
|
||||
const urlService = require('../../../../../../services/url');
|
||||
const {urlFor, makeAbsoluteUrls} = require('../../../../../../services/url/utils');
|
||||
|
||||
const forPost = (id, attrs, options) => {
|
||||
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
|
||||
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = urlFor('image', {image: attrs.feature_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.og_image) {
|
||||
attrs.og_image = urlFor('image', {image: attrs.og_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.twitter_image) {
|
||||
attrs.twitter_image = urlFor('image', {image: attrs.twitter_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.html) {
|
||||
attrs.html = makeAbsoluteUrls(attrs.html, urlFor('home', true), attrs.url).html();
|
||||
}
|
||||
|
||||
if (options.columns && !options.columns.includes('url')) {
|
||||
delete attrs.url;
|
||||
}
|
||||
|
||||
if (options && options.withRelated) {
|
||||
options.withRelated.forEach((relation) => {
|
||||
// @NOTE: this block also decorates primary_tag/primary_author objects as they
|
||||
// are being passed by reference in tags/authors. Might be refactored into more explicit call
|
||||
// in the future, but is good enough for current use-case
|
||||
if (relation === 'tags' && attrs.tags) {
|
||||
attrs.tags = attrs.tags.map(tag => forTag(tag.id, tag));
|
||||
}
|
||||
|
||||
if (relation === 'author' && attrs.author) {
|
||||
attrs.author = forUser(attrs.author.id, attrs.author, options);
|
||||
}
|
||||
|
||||
if (relation === 'authors' && attrs.authors) {
|
||||
attrs.authors = attrs.authors.map(author => forUser(author.id, author, options));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const forUser = (id, attrs) => {
|
||||
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
|
||||
|
||||
if (attrs.profile_image) {
|
||||
attrs.profile_image = urlFor('image', {image: attrs.profile_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.cover_image) {
|
||||
attrs.cover_image = urlFor('image', {image: attrs.cover_image}, true);
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const forTag = (id, attrs) => {
|
||||
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
|
||||
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = urlFor('image', {image: attrs.feature_image}, true);
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
module.exports.forPost = forPost;
|
||||
module.exports.forUser = forUser;
|
||||
module.exports.forTag = forTag;
|
@ -250,6 +250,47 @@ describe('Posts', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('browse posts: request only url fields', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&fields=url`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse.posts);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', false, false, ['url']);
|
||||
res.body.posts[0].url.should.eql('http://127.0.0.1:2369/welcome/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('browse posts: request only url fields with include', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&fields=url&include=tags`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse.posts);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', false, false, ['url','tags']);
|
||||
jsonResponse.posts[0].url.should.eql('http://127.0.0.1:2369/welcome/');
|
||||
jsonResponse.posts[0].tags[0].url.should.eql('http://127.0.0.1:2369/tag/getting-started/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('browse posts: request to include tags and authors should always contain absolute urls', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&include=tags,authors`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
|
@ -10,9 +10,7 @@ describe('Unit: v2/utils/serializers/output/posts', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
postModel = (data) => {
|
||||
return {
|
||||
toJSON: sandbox.stub().returns(data)
|
||||
};
|
||||
return Object.assign(data, {toJSON: sandbox.stub().returns(data)});
|
||||
};
|
||||
|
||||
sandbox.stub(urlService, 'getUrlByResourceId').returns('getUrlByResourceId');
|
||||
|
Loading…
Reference in New Issue
Block a user