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:
parent
9f21730648
commit
8487dada0b
6 changed files with 535 additions and 400 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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(); });
|
||||
|
|
Loading…
Add table
Reference in a new issue