mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Merge pull request #6615 from jaswilli/delete-204
Use HTTP status 204 on deletes
This commit is contained in:
commit
192086bd98
14 changed files with 109 additions and 124 deletions
|
@ -41,20 +41,6 @@ export default RESTAdapter.extend(DataAdapterMixin, {
|
|||
return url;
|
||||
},
|
||||
|
||||
// Override deleteRecord to disregard the response body on 2xx responses.
|
||||
// This is currently needed because the API is returning status 200 along
|
||||
// with the JSON object for the deleted entity and Ember expects an empty
|
||||
// response body for successful DELETEs.
|
||||
// Non-2xx (failure) responses will still work correctly as Ember will turn
|
||||
// them into rejected promises.
|
||||
deleteRecord() {
|
||||
let response = this._super(...arguments);
|
||||
|
||||
return response.then(() => {
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
handleResponse(status) {
|
||||
if (status === 401) {
|
||||
if (this.get('session.isAuthenticated')) {
|
||||
|
|
|
@ -125,6 +125,12 @@ export default function () {
|
|||
return response;
|
||||
});
|
||||
|
||||
this.del('/posts/:id/', function (db, request) {
|
||||
db.posts.remove(request.params.id);
|
||||
|
||||
return new Mirage.Response(204, {}, {});
|
||||
});
|
||||
|
||||
/* Roles ---------------------------------------------------------------- */
|
||||
|
||||
this.get('/roles/', function (db, request) {
|
||||
|
@ -267,7 +273,11 @@ export default function () {
|
|||
};
|
||||
});
|
||||
|
||||
this.del('/tags/:id/', 'tag');
|
||||
this.del('/tags/:id/', function (db, request) {
|
||||
db.tags.remove(request.params.id);
|
||||
|
||||
return new Mirage.Response(204, {}, {});
|
||||
});
|
||||
|
||||
/* Users ---------------------------------------------------------------- */
|
||||
|
||||
|
@ -304,7 +314,11 @@ export default function () {
|
|||
};
|
||||
});
|
||||
|
||||
this.del('/users/:id/', 'user');
|
||||
this.del('/users/:id/', function (db, request) {
|
||||
db.users.remove(request.params.id);
|
||||
|
||||
return new Mirage.Response(204, {}, {});
|
||||
});
|
||||
|
||||
this.get('/users/:id', function (db, request) {
|
||||
return {
|
||||
|
|
|
@ -56,20 +56,22 @@ cacheInvalidationHeader = function cacheInvalidationHeader(req, result) {
|
|||
var parsedUrl = req._parsedUrl.pathname.replace(/^\/|\/$/g, '').split('/'),
|
||||
method = req.method,
|
||||
endpoint = parsedUrl[0],
|
||||
cacheInvalidate,
|
||||
jsonResult = result.toJSON ? result.toJSON() : result,
|
||||
INVALIDATE_ALL = '/*',
|
||||
post,
|
||||
hasStatusChanged,
|
||||
wasDeleted,
|
||||
wasPublishedUpdated;
|
||||
|
||||
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
|
||||
if (endpoint === 'settings' || endpoint === 'users' || endpoint === 'db' || endpoint === 'tags') {
|
||||
cacheInvalidate = '/*';
|
||||
if (['POST', 'PUT', 'DELETE'].indexOf(method) > -1) {
|
||||
if (['settings', 'users', 'db', 'tags'].indexOf(endpoint) > -1) {
|
||||
return INVALIDATE_ALL;
|
||||
} else if (endpoint === 'posts') {
|
||||
if (method === 'DELETE') {
|
||||
return INVALIDATE_ALL;
|
||||
}
|
||||
|
||||
post = jsonResult.posts[0];
|
||||
hasStatusChanged = post.statusChanged;
|
||||
wasDeleted = method === 'DELETE';
|
||||
// Invalidate cache when post was updated but not when post is draft
|
||||
wasPublishedUpdated = method === 'PUT' && post.status === 'published';
|
||||
|
||||
|
@ -77,15 +79,13 @@ cacheInvalidationHeader = function cacheInvalidationHeader(req, result) {
|
|||
delete post.statusChanged;
|
||||
|
||||
// Don't set x-cache-invalidate header for drafts
|
||||
if (hasStatusChanged || wasDeleted || wasPublishedUpdated) {
|
||||
cacheInvalidate = '/*';
|
||||
if (hasStatusChanged || wasPublishedUpdated) {
|
||||
return INVALIDATE_ALL;
|
||||
} else {
|
||||
cacheInvalidate = '/' + config.routeKeywords.preview + '/' + post.uuid + '/';
|
||||
return '/' + config.routeKeywords.preview + '/' + post.uuid + '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cacheInvalidate;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -209,6 +209,10 @@ http = function http(apiMethod) {
|
|||
// Add X-Cache-Invalidate, Location, and Content-Disposition headers
|
||||
return addHeaders(apiMethod, req, res, (response || {}));
|
||||
}).then(function then(response) {
|
||||
if (req.method === 'DELETE') {
|
||||
return res.status(204).end();
|
||||
}
|
||||
|
||||
// Send a properly formatting HTTP response containing the data with correct headers
|
||||
res.json(response || {});
|
||||
}).catch(function onAPIError(error) {
|
||||
|
|
|
@ -115,7 +115,7 @@ notifications = {
|
|||
* Remove a specific notification
|
||||
*
|
||||
* @param {{id (required), context}} options
|
||||
* @returns {Promise(Notifications)}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
var tasks;
|
||||
|
@ -153,8 +153,6 @@ notifications = {
|
|||
return element.id === parseInt(options.id, 10);
|
||||
});
|
||||
notificationCounter = notificationCounter - 1;
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
tasks = [
|
||||
|
@ -163,9 +161,7 @@ notifications = {
|
|||
destroyNotification
|
||||
];
|
||||
|
||||
return pipeline(tasks, options).then(function formatResponse(result) {
|
||||
return {notifications: [result]};
|
||||
});
|
||||
return pipeline(tasks, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -206,7 +206,7 @@ posts = {
|
|||
*
|
||||
* @public
|
||||
* @param {{id (required), context,...}} options
|
||||
* @return {Promise(Post)} Deleted Post
|
||||
* @return {Promise}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
var tasks;
|
||||
|
@ -214,15 +214,14 @@ posts = {
|
|||
/**
|
||||
* @function deletePost
|
||||
* @param {Object} options
|
||||
* @return {Object} JSON representation of the deleted post
|
||||
*/
|
||||
function deletePost(options) {
|
||||
var Post = dataProvider.Post,
|
||||
data = _.defaults({status: 'all'}, options),
|
||||
fetchOpts = _.defaults({require: true}, options);
|
||||
fetchOpts = _.defaults({require: true, columns: 'id'}, options);
|
||||
|
||||
return Post.findOne(data, fetchOpts).then(function (post) {
|
||||
return post.destroy(options).return({posts: [post.toJSON()]});
|
||||
return post.destroy(options).return(null);
|
||||
}).catch(Post.NotFoundError, function () {
|
||||
throw new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound'));
|
||||
});
|
||||
|
@ -237,9 +236,7 @@ posts = {
|
|||
];
|
||||
|
||||
// Pipeline calls each task passing the result of one to be the arguments for the next
|
||||
return pipeline(tasks, options).tap(function formatResponse(response) {
|
||||
response.posts[0].statusChanged = true;
|
||||
});
|
||||
return pipeline(tasks, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -164,23 +164,18 @@ tags = {
|
|||
*
|
||||
* @public
|
||||
* @param {{id, context}} options
|
||||
* @return {Promise<Tag>} Deleted Tag
|
||||
* @return {Promise}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
var tasks;
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* ### Delete Tag
|
||||
* Make the call to the Model layer
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function doQuery(options) {
|
||||
return tags.read(options).then(function (result) {
|
||||
return dataProvider.Tag.destroy(options).then(function () {
|
||||
return result;
|
||||
});
|
||||
});
|
||||
function deleteTag(options) {
|
||||
return dataProvider.Tag.destroy(options).return(null);
|
||||
}
|
||||
|
||||
// Push all of our tasks into a `tasks` array in the correct order
|
||||
|
@ -188,7 +183,7 @@ tags = {
|
|||
utils.validate(docName, {opts: utils.idDefaultOptions}),
|
||||
utils.handlePermissions(docName, 'destroy'),
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
deleteTag
|
||||
];
|
||||
|
||||
// Pipeline calls each task passing the result of one to be the arguments for the next
|
||||
|
|
|
@ -361,7 +361,7 @@ users = {
|
|||
/**
|
||||
* ## Destroy
|
||||
* @param {{id, context}} options
|
||||
* @returns {Promise<User>}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
var tasks;
|
||||
|
@ -382,33 +382,22 @@ users = {
|
|||
}
|
||||
|
||||
/**
|
||||
* ### Model Query
|
||||
* ### Delete User
|
||||
* Make the call to the Model layer
|
||||
* @param {Object} options
|
||||
* @returns {Object} options
|
||||
*/
|
||||
function doQuery(options) {
|
||||
return users.read(options).then(function (result) {
|
||||
return dataProvider.Base.transaction(function (t) {
|
||||
options.transacting = t;
|
||||
function deleteUser(options) {
|
||||
return dataProvider.Base.transaction(function (t) {
|
||||
options.transacting = t;
|
||||
|
||||
Promise.all([
|
||||
dataProvider.Accesstoken.destroyByUser(options),
|
||||
dataProvider.Refreshtoken.destroyByUser(options),
|
||||
dataProvider.Post.destroyByAuthor(options)
|
||||
]).then(function () {
|
||||
return dataProvider.User.destroy(options);
|
||||
}).then(function () {
|
||||
t.commit();
|
||||
}).catch(function (error) {
|
||||
t.rollback(error);
|
||||
});
|
||||
}).then(function () {
|
||||
return result;
|
||||
}, function (error) {
|
||||
return Promise.reject(new errors.InternalServerError(error));
|
||||
});
|
||||
}, function (error) {
|
||||
return Promise.all([
|
||||
dataProvider.Accesstoken.destroyByUser(options),
|
||||
dataProvider.Refreshtoken.destroyByUser(options),
|
||||
dataProvider.Post.destroyByAuthor(options)
|
||||
]).then(function () {
|
||||
return dataProvider.User.destroy(options);
|
||||
}).return(null);
|
||||
}).catch(function (error) {
|
||||
return errors.formatAndRejectAPIError(error);
|
||||
});
|
||||
}
|
||||
|
@ -418,7 +407,7 @@ users = {
|
|||
utils.validate(docName, {opts: utils.idDefaultOptions}),
|
||||
handlePermissions,
|
||||
utils.convertOptions(allowedIncludes),
|
||||
doQuery
|
||||
deleteUser
|
||||
];
|
||||
|
||||
// Pipeline calls each task passing the result of one to be the arguments for the next
|
||||
|
|
|
@ -375,7 +375,7 @@ Post = ghostBookshelf.Model.extend({
|
|||
// whitelists for the `options` hash argument on methods, by method name.
|
||||
// these are the only options that can be passed to Bookshelf / Knex.
|
||||
validOptions = {
|
||||
findOne: ['importing', 'withRelated', 'require'],
|
||||
findOne: ['columns', 'importing', 'withRelated', 'require'],
|
||||
findPage: ['page', 'limit', 'columns', 'filter', 'order', 'status', 'staticPages'],
|
||||
findAll: ['columns'],
|
||||
add: ['importing']
|
||||
|
|
|
@ -94,20 +94,13 @@ describe('Notifications API', function () {
|
|||
// begin delete test
|
||||
request.del(location)
|
||||
.set('Authorization', 'Bearer ' + accesstoken)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(204)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// a delete returns a JSON object containing the notification
|
||||
// we just deleted.
|
||||
var deleteResponse = res.body;
|
||||
should.exist(deleteResponse.notifications);
|
||||
deleteResponse.notifications[0].type.should.equal(newNotification.type);
|
||||
deleteResponse.notifications[0].message.should.equal(newNotification.message);
|
||||
deleteResponse.notifications[0].status.should.equal(newNotification.status);
|
||||
res.body.should.be.empty();
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -844,20 +844,16 @@ describe('Post API', function () {
|
|||
var deletePostId = 1;
|
||||
request.del(testUtils.API.getApiQuery('posts/' + deletePostId + '/'))
|
||||
.set('Authorization', 'Bearer ' + accesstoken)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.expect(204)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
res.body.should.be.empty();
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
jsonResponse.posts[0].id.should.eql(deletePostId);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -907,18 +903,15 @@ describe('Post API', function () {
|
|||
|
||||
request.del(testUtils.API.getApiQuery('posts/' + draftPost.posts[0].id + '/'))
|
||||
.set('Authorization', 'Bearer ' + accesstoken)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.expect(204)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
res.body.should.be.empty();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -123,9 +123,7 @@ describe('Notifications API', function () {
|
|||
NotificationsAPI.destroy(
|
||||
_.extend({}, testUtils.context.internal, {id: notification.id})
|
||||
).then(function (result) {
|
||||
should.exist(result);
|
||||
should.exist(result.notifications);
|
||||
result.notifications[0].id.should.equal(notification.id);
|
||||
should.not.exist(result);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
@ -144,9 +142,7 @@ describe('Notifications API', function () {
|
|||
NotificationsAPI.destroy(
|
||||
_.extend({}, testUtils.context.owner, {id: notification.id})
|
||||
).then(function (result) {
|
||||
should.exist(result);
|
||||
should.exist(result.notifications);
|
||||
result.notifications[0].id.should.equal(notification.id);
|
||||
should.not.exist(result);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
|
|
@ -552,4 +552,36 @@ describe('Post API', function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Destroy', function () {
|
||||
it('can delete a post', function (done) {
|
||||
var options = {context: {user: 1}, id: 1};
|
||||
|
||||
PostAPI.read(options).then(function (results) {
|
||||
should.exist(results.posts[0]);
|
||||
|
||||
return PostAPI.destroy(options);
|
||||
}).then(function (results) {
|
||||
should.not.exist(results);
|
||||
|
||||
return PostAPI.read(options);
|
||||
}).then(function () {
|
||||
done(new Error('Post still exists when it should have been deleted'));
|
||||
}).catch(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when attempting to delete a non-existent post', function (done) {
|
||||
var options = {context: {user: 1}, id: 123456788};
|
||||
|
||||
PostAPI.destroy(options).then(function () {
|
||||
done(new Error('No error was thrown'));
|
||||
}).catch(function (error) {
|
||||
error.errorType.should.eql('NotFoundError');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -126,9 +126,8 @@ describe('Tags API', function () {
|
|||
it('can destroy Tag', function (done) {
|
||||
TagAPI.destroy(_.extend({}, testUtils.context.admin, {id: firstTag}))
|
||||
.then(function (results) {
|
||||
should.exist(results);
|
||||
should.exist(results.tags);
|
||||
results.tags.length.should.be.above(0);
|
||||
should.not.exist(results);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
|
|
@ -693,15 +693,6 @@ describe('Users API', function () {
|
|||
});
|
||||
|
||||
describe('Destroy', function () {
|
||||
function checkDestroyResponse(response) {
|
||||
should.exist(response);
|
||||
should.exist(response.users);
|
||||
should.not.exist(response.meta);
|
||||
response.users.should.have.length(1);
|
||||
testUtils.API.checkResponse(response.users[0], 'user');
|
||||
response.users[0].created_at.should.be.an.instanceof(Date);
|
||||
}
|
||||
|
||||
describe('Owner', function () {
|
||||
it('CANNOT destroy self', function (done) {
|
||||
UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.owner}))
|
||||
|
@ -714,16 +705,16 @@ describe('Users API', function () {
|
|||
// Admin
|
||||
UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.admin}))
|
||||
.then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
// Editor
|
||||
return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.editor}));
|
||||
}).then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
|
||||
// Author
|
||||
return UserAPI.destroy(_.extend({}, context.owner, {id: userIdFor.author}));
|
||||
}).then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
@ -742,17 +733,17 @@ describe('Users API', function () {
|
|||
// Admin
|
||||
UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.admin2}))
|
||||
.then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
|
||||
// Editor
|
||||
return UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.editor2}));
|
||||
}).then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
|
||||
// Author
|
||||
return UserAPI.destroy(_.extend({}, context.admin, {id: userIdFor.author2}));
|
||||
}).then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
|
@ -784,7 +775,7 @@ describe('Users API', function () {
|
|||
it('Can destroy self', function (done) {
|
||||
UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.editor}))
|
||||
.then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
@ -792,7 +783,7 @@ describe('Users API', function () {
|
|||
it('Can destroy author', function (done) {
|
||||
UserAPI.destroy(_.extend({}, context.editor, {id: userIdFor.author}))
|
||||
.then(function (response) {
|
||||
checkDestroyResponse(response);
|
||||
should.not.exist(response);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue