0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Implement user and related content deletion

Closes #3100

* Introduces `destroyByAuhor`, given a context and an id, it will check if context has permission to delete the user by the id, and then deletes all the content where `author_id` is id, and then deletes the user
* Does multiple checks to make sure user exists
* Added a fixture `posts:mu` that creates 4 users belonging to 4 roles, 50 posts that have authors evenly distributed, 5 tags and all 50 have one tag attached to it, evenly distributed.

Caveats / questions

* Started testing
This commit is contained in:
Gabor Javorszky 2014-07-23 09:32:27 +01:00
parent 9f21730648
commit 8487dada0b
6 changed files with 535 additions and 400 deletions

View file

@ -255,9 +255,22 @@ users = {
destroy: function destroy(options) {
return canThis(options.context).destroy.user(options.id).then(function () {
return users.read(options).then(function (result) {
return dataProvider.User.destroy(options).then(function () {
return result;
return dataProvider.Base.transaction(function (t) {
options.transacting = t;
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 when.reject(new errors.InternalServerError(error));
});
}, function (error) {
return errors.handleAPIError(error);
});
}).catch(function (error) {
return errors.handleAPIError(error);

View file

@ -521,6 +521,31 @@ Post = ghostBookshelf.Model.extend({
});
},
/**
* ### destroyByAuthor
* @param {[type]} options has context and id. Context is the user doing the destroy, id is the user to destroy
*/
destroyByAuthor: function (options) {
var postCollection = Posts.forge(),
authorId = options.id;
options = this.filterOptions(options, 'destroyByAuthor');
if (authorId) {
return postCollection.query('where', 'author_id', '=', authorId).fetch(options).then(function (results) {
return when.map(results.models, function (post) {
return post.related('tags').detach(null, options).then(function () {
return post.destroy(options);
});
});
}, function (error) {
return when.reject(new errors.InternalServerError(error.message || error));
});
}
return when.reject(new errors.NotFoundError('No user found'));
},
permissible: function (postModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) {
var self = this,
postModel = postModelOrId,

View file

@ -12,6 +12,9 @@ var testUtils = require('../../utils'),
describe('Post Model', function () {
// Keep the DB clean
describe('Single author posts', function () {
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(testUtils.setup('owner', 'posts', 'apps'));
@ -381,6 +384,7 @@ describe('Post Model', function () {
}).catch(done);
});
it('can findPage, with various options', function (done) {
testUtils.fixtures.insertMorePosts().then(function () {
@ -475,6 +479,36 @@ describe('Post Model', function () {
done();
}).catch(done);
});
});
describe('Multiauthor Posts', function () {
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(testUtils.setup('posts:mu'));
should.exist(PostModel);
it('can destroy multiple posts by author', function (done) {
// We're going to delete all posts by user 1
var authorData = {id: 1};
PostModel.findAll().then(function (found) {
// There are 50 posts to begin with
found.length.should.equal(50);
return PostModel.destroyByAuthor(authorData);
}).then(function (results) {
// User 1 has 13 posts in the database
results.length.should.equal(13);
return PostModel.findAll();
}).then(function (found) {
// Only 37 should remain
found.length.should.equal(37);
done();
}).catch(done);
});
});
// disabling sanitization until we can implement a better version
// it('should sanitize the title', function (done) {

File diff suppressed because one or more lines are too long

View file

@ -31,12 +31,71 @@ var when = require('when'),
fixtures = {
insertPosts: function insertPosts() {
var knex = config.database.knex;
// ToDo: Get rid of pyramid of doom
return when(knex('posts').insert(DataGenerator.forKnex.posts).then(function () {
return knex('tags').insert(DataGenerator.forKnex.tags).then(function () {
return when(knex('posts').insert(DataGenerator.forKnex.posts)).then(function () {
return knex('tags').insert(DataGenerator.forKnex.tags);
}).then(function () {
return knex('posts_tags').insert(DataGenerator.forKnex.posts_tags);
});
},
insertMultiAuthorPosts: function insertMultiAuthorPosts(max) {
var knex = config.database.knex,
tags,
author,
authors,
i, j, k = postsInserted,
posts = [];
max = max || 50;
// insert users of different roles
return when(fixtures.createUsersWithRoles()).then(function (results) {
// create the tags
return knex('tags').insert(DataGenerator.forKnex.tags);
}).then(function (results) {
return knex('users').select('id');
}).then(function (results) {
authors = _.pluck(results, 'id');
// Let's insert posts with random authors
for (i = 0; i < max; i += 1) {
author = authors[i % authors.length];
posts.push(DataGenerator.forKnex.createGenericPost(k++, null, null, author));
}
// Keep track so we can run this function again safely
postsInserted = k;
return sequence(_.times(posts.length, function (index) {
return function () {
return knex('posts').insert(posts[index]);
};
}));
}).then(function () {
return when.all([
// PostgreSQL can return results in any order
knex('posts').orderBy('id', 'asc').select('id'),
knex('tags').select('id')
]);
}).then(function (results) {
var posts = _.pluck(results[0], 'id'),
tags = _.pluck(results[1], 'id'),
promises = [],
i;
if (max > posts.length) {
throw new Error('Trying to add more posts_tags than the number of posts.');
}
for (i = 0; i < max; i += 1) {
promises.push(DataGenerator.forKnex.createPostsTags(posts[i], tags[i % tags.length]));
}
return sequence(_.times(promises.length, function (index) {
return function () {
return knex('posts_tags').insert(promises[index]);
};
}));
});
},
insertMorePosts: function insertMorePosts(max) {
@ -263,8 +322,10 @@ toDoList = {
'permission': function insertPermission() { return fixtures.insertOne('permissions', 'createPermission'); },
'role': function insertRole() { return fixtures.insertOne('roles', 'createRole'); },
'roles': function insertRoles() { return fixtures.insertRoles(); },
'tag': function insertRole() { return fixtures.insertOne('tags', 'createTag'); },
'tag': function insertTag() { return fixtures.insertOne('tags', 'createTag'); },
'posts': function insertPosts() { return fixtures.insertPosts(); },
'posts:mu': function insertMultiAuthorPosts() { return fixtures.insertMultiAuthorPosts(); },
'apps': function insertApps() { return fixtures.insertApps(); },
'settings': function populate() {
return settings.populateDefaults().then(function () { return SettingsAPI.updateSettingsCache(); });