mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Added post tag assignment on user deletion
closes https://github.com/TryGhost/Ghost/issues/15008 - To improve searchability of the posts written by a removed user we are adding an internal tag to all the posts the user was an author or a co-author of - This improvement should make managing and disovering deleted user's post way easier
This commit is contained in:
parent
2956c1e88a
commit
db125ec0b9
2 changed files with 116 additions and 2 deletions
|
@ -46,6 +46,8 @@ class Users {
|
|||
this.auth = auth;
|
||||
this.apiMail = apiMail;
|
||||
this.apiSettings = apiSettings;
|
||||
|
||||
this.assignTagToUserPosts = this.assignTagToUserPosts.bind(this);
|
||||
}
|
||||
|
||||
async resetAllPasswords(frameOptions) {
|
||||
|
@ -68,6 +70,77 @@ class Users {
|
|||
});
|
||||
}
|
||||
|
||||
async assignTagToUserPosts({id, context, transacting}) {
|
||||
// create an internal tag to assign to reassigned posts
|
||||
// in following format: `#{author_slug}`
|
||||
const author = await this.models.User.findOne({
|
||||
id
|
||||
}, {
|
||||
id,
|
||||
context,
|
||||
transacting
|
||||
});
|
||||
let tag = await this.models.Tag.findOne({
|
||||
slug: `hash-${author.get('slug')}`
|
||||
}, {
|
||||
context,
|
||||
transacting
|
||||
});
|
||||
|
||||
if (!tag) {
|
||||
tag = await this.models.Tag.add({
|
||||
slug: `#${author.get('slug')}`
|
||||
}, {
|
||||
context,
|
||||
transacting
|
||||
});
|
||||
}
|
||||
|
||||
const userPosts = await this.models.Base.knex('posts_authors')
|
||||
.transacting(transacting)
|
||||
.where('author_id', id)
|
||||
.select('post_id');
|
||||
const usersPostIds = userPosts.map(p => p.post_id);
|
||||
|
||||
// Add a tag to all posts that do not have the author tag yet
|
||||
// NOTE: the method is implemented in an iterative way to avoid
|
||||
// memory consumption in case the user has thousands of posts
|
||||
// assigned to them. Also, didn't have any "bulk" way to add
|
||||
// a tag to multiple posts as the "sort_order" needs custom
|
||||
// logic to be run for each post.
|
||||
// Rewrite this bit if/when we hit a performance bottleneck here
|
||||
for (const postId of usersPostIds) {
|
||||
const post = await this.models.Post.findOne({
|
||||
id: postId
|
||||
}, {
|
||||
id: postId,
|
||||
withRelated: ['tags'],
|
||||
context,
|
||||
transacting
|
||||
});
|
||||
|
||||
// check if tag already assigned to the post
|
||||
const existingTagSlugs = post.relations.tags.models.map(t => t.get('slug'));
|
||||
|
||||
if (!existingTagSlugs.includes(tag.get('slug'))) {
|
||||
await this.models.Post.edit({
|
||||
tags: [...post.relations.tags.models, tag]
|
||||
}, {
|
||||
id: postId,
|
||||
context,
|
||||
transacting
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} frameOptions
|
||||
* @param {string} frameOptions.id - user ID to destroy
|
||||
* @param {Object} frameOptions.context - frame context to perform the action
|
||||
* @returns
|
||||
*/
|
||||
async destroyUser(frameOptions) {
|
||||
const backupPath = await this.dbBackup.backup();
|
||||
const parsedFileName = path.parse(backupPath);
|
||||
|
@ -76,7 +149,17 @@ class Users {
|
|||
return this.models.Base.transaction(async (t) => {
|
||||
frameOptions.transacting = t;
|
||||
|
||||
await this.models.Post.reassignByAuthor(frameOptions);
|
||||
await this.assignTagToUserPosts({
|
||||
id: frameOptions.id,
|
||||
context: frameOptions.context,
|
||||
transacting: frameOptions.transacting
|
||||
});
|
||||
|
||||
await this.models.Post.reassignByAuthor({
|
||||
id: frameOptions.id,
|
||||
context: frameOptions.context,
|
||||
transacting: frameOptions.transacting
|
||||
});
|
||||
|
||||
try {
|
||||
await this.models.ApiKey.destroy({
|
||||
|
|
|
@ -15,9 +15,10 @@ describe('User API', function () {
|
|||
request = supertest.agent(config.get('url'));
|
||||
|
||||
// create inactive user
|
||||
const authorRole = testUtils.DataGenerator.Content.roles[2].name;
|
||||
otherAuthor = await testUtils.createUser({
|
||||
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+3@ghost.org'}),
|
||||
role: testUtils.DataGenerator.Content.roles[2].name
|
||||
role: authorRole
|
||||
});
|
||||
|
||||
// by default we login with the owner
|
||||
|
@ -113,12 +114,42 @@ describe('User API', function () {
|
|||
});
|
||||
|
||||
describe('Destroy', function () {
|
||||
before(async function () {
|
||||
// login with the owner
|
||||
request = supertest.agent(config.get('url'));
|
||||
await localUtils.doAuth(request);
|
||||
});
|
||||
|
||||
it('[failure] Destroy unknown user id', function () {
|
||||
return request
|
||||
.delete(localUtils.API.getApiQuery('users/' + ObjectId().toHexString()))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('Destroy known user and reassign post tags', async function () {
|
||||
const otherAuthorPost = await testUtils.createPost({
|
||||
post: {
|
||||
tags: [{
|
||||
slug: 'existing-tag'
|
||||
}, {
|
||||
slug: 'second-one'
|
||||
}],
|
||||
authors: [otherAuthor]
|
||||
}
|
||||
});
|
||||
|
||||
await request
|
||||
.delete(localUtils.API.getApiQuery(`users/${otherAuthor.id}`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(200);
|
||||
|
||||
const tags = await otherAuthorPost.related('tags').fetch();
|
||||
|
||||
should.equal(tags.length, 3);
|
||||
should.equal(tags.models[2].get('slug'), `hash-${otherAuthor.slug}`);
|
||||
should.equal(tags.models[2].get('name'), `#${otherAuthor.slug}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue