0
Fork 0
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:
Naz 2022-07-12 13:25:39 +01:00 committed by naz
parent 2956c1e88a
commit db125ec0b9
2 changed files with 116 additions and 2 deletions

View file

@ -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({

View file

@ -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}`);
});
});
});