mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Removed x_by fields from API v2 response
refs #10286 - v2 no longer exposes x_by fields (published_by, updated_by, created_by) - we will add a brand new concept called activity stream/actions soon
This commit is contained in:
parent
254a460462
commit
789a3c0715
12 changed files with 210 additions and 83 deletions
|
@ -76,6 +76,12 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, options) => {
|
|||
|
||||
// ##### API VERSION RESOURCE SERIALIZATION
|
||||
|
||||
if (apiSerializers.all && apiSerializers.all.before) {
|
||||
tasks.push(function allSerializeBefore() {
|
||||
return apiSerializers.all.before(response, apiConfig, options);
|
||||
});
|
||||
}
|
||||
|
||||
if (apiSerializers[apiConfig.docName]) {
|
||||
if (apiSerializers[apiConfig.docName].all) {
|
||||
tasks.push(function serializeOptionsShared() {
|
||||
|
@ -90,6 +96,12 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, options) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (apiSerializers.all && apiSerializers.all.after) {
|
||||
tasks.push(function allSerializeAfter() {
|
||||
return apiSerializers.all.after(apiConfig, options);
|
||||
});
|
||||
}
|
||||
|
||||
debug(tasks);
|
||||
return sequence(tasks);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ const urlService = require('../../services/url');
|
|||
const settingsCache = require('../../services/settings/cache');
|
||||
const models = require('../../models');
|
||||
const api = require('./index');
|
||||
const ALLOWED_INCLUDES = ['created_by', 'updated_by'];
|
||||
const ALLOWED_INCLUDES = [];
|
||||
const UNSAFE_ATTRS = ['role_id'];
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const common = require('../../lib/common');
|
||||
const models = require('../../models');
|
||||
const ALLOWED_INCLUDES = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'authors', 'authors.roles'];
|
||||
const ALLOWED_INCLUDES = ['author', 'tags', 'authors', 'authors.roles'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'pages',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const models = require('../../models');
|
||||
const common = require('../../lib/common');
|
||||
const urlService = require('../../services/url');
|
||||
const allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'authors', 'authors.roles'];
|
||||
const allowedIncludes = ['author', 'tags', 'authors', 'authors.roles'];
|
||||
const unsafeAttrs = ['author_id', 'status', 'authors'];
|
||||
|
||||
module.exports = {
|
||||
|
|
25
core/server/api/v2/utils/serializers/output/all.js
Normal file
25
core/server/api/v2/utils/serializers/output/all.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:all');
|
||||
const _ = require('lodash');
|
||||
|
||||
const removeXBY = (object) => {
|
||||
_.each(object, (value, key) => {
|
||||
// CASE: go deeper
|
||||
if (_.isObject(value) || _.isArray(value)) {
|
||||
removeXBY(value);
|
||||
} else if (['updated_by', 'created_by', 'published_by'].includes(key)) {
|
||||
delete object[key];
|
||||
}
|
||||
});
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
after(apiConfig, frame) {
|
||||
debug('all after');
|
||||
|
||||
if (frame.response) {
|
||||
frame.response = removeXBY(frame.response);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,4 +1,8 @@
|
|||
module.exports = {
|
||||
get all() {
|
||||
return require('./all');
|
||||
},
|
||||
|
||||
get db() {
|
||||
return require('./db');
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@ const moment = require('moment-timezone');
|
|||
const testUtils = require('../../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../../../core/server/config');
|
||||
const models = require('../../../../../../core/server/models');
|
||||
const ghost = testUtils.startGhost;
|
||||
let request;
|
||||
|
||||
|
@ -248,7 +249,6 @@ describe('Posts API V2', function () {
|
|||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
jsonResponse.posts[0].author.should.be.a.String();
|
||||
testUtils.API.isISO8601(jsonResponse.posts[0].created_at).should.be.true();
|
||||
jsonResponse.posts[0].created_by.should.be.a.String();
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
done();
|
||||
|
@ -300,7 +300,6 @@ describe('Posts API V2', function () {
|
|||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
jsonResponse.posts[0].author.should.be.a.String();
|
||||
jsonResponse.posts[0].created_by.should.be.a.String();
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
done();
|
||||
|
@ -309,7 +308,7 @@ describe('Posts API V2', function () {
|
|||
|
||||
it('with includes', function (done) {
|
||||
request
|
||||
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=authors,tags,created_by'))
|
||||
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=authors,tags'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -384,13 +383,19 @@ describe('Posts API V2', function () {
|
|||
localUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
res.body.posts[0].title.should.eql(post.title);
|
||||
res.body.posts[0].status.should.eql(post.status);
|
||||
res.body.posts[0].published_at.should.eql('2016-05-30T07:00:00.000Z');
|
||||
res.body.posts[0].created_at.should.not.eql(post.created_at.toISOString());
|
||||
res.body.posts[0].updated_at.should.not.eql(post.updated_at.toISOString());
|
||||
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
|
||||
res.body.posts[0].created_by.should.not.eql(post.created_by);
|
||||
return models.Post.findOne({
|
||||
id: res.body.posts[0].id,
|
||||
status: 'draft'
|
||||
}, testUtils.context.internal);
|
||||
})
|
||||
.then((model) => {
|
||||
model.get('title').should.eql(post.title);
|
||||
model.get('status').should.eql(post.status);
|
||||
model.get('published_at').toISOString().should.eql('2016-05-30T07:00:00.000Z');
|
||||
model.get('created_at').toISOString().should.not.eql(post.created_at.toISOString());
|
||||
model.get('updated_at').toISOString().should.not.eql(post.updated_at.toISOString());
|
||||
model.get('updated_by').should.not.eql(post.updated_by);
|
||||
model.get('created_by').should.not.eql(post.created_by);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -468,13 +473,18 @@ describe('Posts API V2', function () {
|
|||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
localUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
|
||||
return models.Post.findOne({
|
||||
id: res.body.posts[0].id
|
||||
}, testUtils.context.internal);
|
||||
})
|
||||
.then((model) => {
|
||||
// We expect that the changed properties aren't changed, they are still the same than before.
|
||||
res.body.posts[0].created_by.should.not.eql(post.created_by);
|
||||
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
|
||||
res.body.posts[0].created_at.should.not.eql(post.created_at);
|
||||
model.get('created_at').toISOString().should.not.eql(post.created_at);
|
||||
model.get('updated_by').should.not.eql(post.updated_by);
|
||||
model.get('created_by').should.not.eql(post.created_by);
|
||||
|
||||
// `updated_at` is automatically set, but it's not the date we send to override.
|
||||
res.body.posts[0].updated_at.should.not.eql(post.updated_at);
|
||||
model.get('updated_at').toISOString().should.not.eql(post.updated_at);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('Settings API V2', function () {
|
|||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.settings);
|
||||
|
||||
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'updated_by']);
|
||||
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'key', 'value', 'type', 'created_at', 'updated_at']);
|
||||
jsonResponse.settings[0].key.should.eql('title');
|
||||
testUtils.API.isISO8601(jsonResponse.settings[0].created_at).should.be.true();
|
||||
done();
|
||||
|
|
|
@ -328,55 +328,6 @@ describe('User API V2', function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('check which fields can be modified', function (done) {
|
||||
var existingUserData, modifiedUserData;
|
||||
|
||||
request.get(localUtils.API.getApiQuery('users/me/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse.users[0]);
|
||||
existingUserData = _.cloneDeep(jsonResponse.users[0]);
|
||||
modifiedUserData = _.cloneDeep(jsonResponse);
|
||||
|
||||
existingUserData.created_by.should.eql('1');
|
||||
existingUserData.updated_by.should.eql('1');
|
||||
|
||||
modifiedUserData.users[0].created_at = moment().add(2, 'days').format();
|
||||
modifiedUserData.users[0].updated_at = moment().add(2, 'days').format();
|
||||
modifiedUserData.users[0].created_by = ObjectId.generate();
|
||||
modifiedUserData.users[0].updated_by = ObjectId.generate();
|
||||
|
||||
delete modifiedUserData.users[0].id;
|
||||
|
||||
request.put(localUtils.API.getApiQuery('users/me/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send(modifiedUserData)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse.users[0]);
|
||||
|
||||
jsonResponse.users[0].created_by.should.eql(existingUserData.created_by);
|
||||
jsonResponse.users[0].updated_by.should.eql(existingUserData.updated_by);
|
||||
jsonResponse.users[0].updated_at.should.not.eql(modifiedUserData.updated_at);
|
||||
jsonResponse.users[0].created_at.should.eql(existingUserData.created_at);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Destroy', function () {
|
||||
|
|
|
@ -15,26 +15,47 @@ const expectedProperties = {
|
|||
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
|
||||
slugs: ['slugs'],
|
||||
slug: ['slug'],
|
||||
invites: ['invites', 'meta'],
|
||||
themes: ['themes'],
|
||||
|
||||
post: _(schema.posts)
|
||||
.keys()
|
||||
// by default we only return html
|
||||
.without('mobiledoc', 'plaintext')
|
||||
// swaps author_id to author, and always returns computed properties: url, comment_id, primary_tag, primary_author
|
||||
.without('author_id').concat('author', 'url', 'primary_tag', 'primary_author')
|
||||
.value(),
|
||||
user: _(schema.users).keys().without('password').without('ghost_auth_access_token').value(),
|
||||
// Tag API swaps parent_id to parent
|
||||
tag: _(schema.tags).keys().without('parent_id').concat('parent').value(),
|
||||
setting: _.keys(schema.settings),
|
||||
subscriber: _.keys(schema.subscribers),
|
||||
accesstoken: _.keys(schema.accesstokens),
|
||||
role: _.keys(schema.roles),
|
||||
permission: _.keys(schema.permissions),
|
||||
,
|
||||
user: _(schema.users)
|
||||
.keys()
|
||||
.without('password')
|
||||
.without('ghost_auth_access_token')
|
||||
,
|
||||
tag: _(schema.tags)
|
||||
.keys()
|
||||
// Tag API swaps parent_id to parent
|
||||
.without('parent_id').concat('parent')
|
||||
,
|
||||
setting: _(schema.settings)
|
||||
.keys()
|
||||
,
|
||||
subscriber: _(schema.subscribers)
|
||||
.keys()
|
||||
,
|
||||
accesstoken: _(schema.accesstokens)
|
||||
.keys()
|
||||
,
|
||||
role: _(schema.roles)
|
||||
.keys()
|
||||
,
|
||||
permission: _(schema.permissions)
|
||||
.keys()
|
||||
,
|
||||
notification: ['type', 'message', 'status', 'id', 'dismissible', 'location', 'custom'],
|
||||
theme: ['name', 'package', 'active'],
|
||||
themes: ['themes'],
|
||||
invites: ['invites', 'meta'],
|
||||
invite: _(schema.invites).keys().without('token').value(),
|
||||
invite: _(schema.invites)
|
||||
.keys()
|
||||
.without('token')
|
||||
,
|
||||
webhook: _(schema.webhooks)
|
||||
.keys()
|
||||
.without(
|
||||
|
@ -45,9 +66,25 @@ const expectedProperties = {
|
|||
'secret',
|
||||
'integration_id'
|
||||
)
|
||||
.value()
|
||||
};
|
||||
|
||||
_.each(expectedProperties, (value, key) => {
|
||||
if (!value.__wrapped__) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated: x_by
|
||||
*/
|
||||
expectedProperties[key] = value
|
||||
.without(
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'published_by'
|
||||
)
|
||||
.value();
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
API: {
|
||||
getApiQuery(route) {
|
||||
|
|
|
@ -10,13 +10,14 @@ const expectedProperties = {
|
|||
tags: ['tags', 'meta'],
|
||||
authors: ['authors', 'meta'],
|
||||
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
|
||||
|
||||
post: _(schema.posts)
|
||||
.keys()
|
||||
// by default we only return html
|
||||
.without('mobiledoc', 'plaintext')
|
||||
// swaps author_id to author, and always returns computed properties: url, comment_id, primary_tag, primary_author
|
||||
.without('author_id').concat('author', 'url', 'primary_tag', 'primary_author')
|
||||
.value(),
|
||||
,
|
||||
author: _(schema.users)
|
||||
.keys()
|
||||
.without(
|
||||
|
@ -31,12 +32,30 @@ const expectedProperties = {
|
|||
'last_seen',
|
||||
'status'
|
||||
)
|
||||
.value()
|
||||
,
|
||||
// Tag API swaps parent_id to parent
|
||||
tag: _(schema.tags).keys().without('parent_id').concat('parent').value()
|
||||
tag: _(schema.tags)
|
||||
.keys()
|
||||
.without('parent_id').concat('parent')
|
||||
};
|
||||
|
||||
_.each(expectedProperties, (value, key) => {
|
||||
if (!value.__wrapped__) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated: x_by
|
||||
*/
|
||||
expectedProperties[key] = value
|
||||
.without(
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'published_by'
|
||||
)
|
||||
.value();
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
API: {
|
||||
getApiQuery(route) {
|
||||
|
|
69
core/test/unit/api/v2/utils/serializers/output/all_spec.js
Normal file
69
core/test/unit/api/v2/utils/serializers/output/all_spec.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
const should = require('should');
|
||||
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
|
||||
|
||||
describe('Unit: v2/utils/serializers/output/all', () => {
|
||||
describe('after', function () {
|
||||
it('x_by', function () {
|
||||
const apiConfig = {};
|
||||
let response = {
|
||||
posts: [
|
||||
{
|
||||
created_by: 'xxx',
|
||||
title: 'xxx'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
serializers.output.all.after(apiConfig, {
|
||||
response: response
|
||||
});
|
||||
|
||||
should.not.exist(response.posts[0].created_by);
|
||||
should.exist(response.posts[0].title);
|
||||
|
||||
response = {
|
||||
post:
|
||||
{
|
||||
created_by: 'xxx',
|
||||
updated_by: 'yyy',
|
||||
title: 'xxx'
|
||||
}
|
||||
};
|
||||
|
||||
serializers.output.all.after(apiConfig, {
|
||||
response: response
|
||||
});
|
||||
|
||||
should.not.exist(response.post.created_by);
|
||||
should.not.exist(response.post.updated_by);
|
||||
should.exist(response.post.title);
|
||||
|
||||
response = {
|
||||
pages: [
|
||||
{
|
||||
created_by: 'xxx',
|
||||
authors: [
|
||||
{
|
||||
updated_by: 'yyy',
|
||||
slug: 'ghost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
published_by: 'yyy'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
serializers.output.all.after(apiConfig, {
|
||||
response: response
|
||||
});
|
||||
|
||||
should.not.exist(response.pages[0].created_by);
|
||||
should.not.exist(response.pages[1].published_by);
|
||||
should.exist(response.pages[0].authors);
|
||||
should.exist(response.pages[0].authors[0].slug);
|
||||
should.not.exist(response.pages[0].authors[0].updated_by);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue