fix(backend): delete follow relations for deleted users (#8805)

* delete follows

* migration-deleted-user-follows

---------

Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
This commit is contained in:
Ulf Gebhardt 2025-08-07 11:20:32 +01:00 committed by GitHub
parent 1612d03b52
commit 04dec08d04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 88 additions and 0 deletions

View File

@ -0,0 +1,51 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getDriver } from '@db/neo4j'
export const description = 'Delete follow relation for deleted users'
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// Implement your migration here.
await transaction.run(`
MATCH (:User {deleted: true})-[follow:FOLLOWS]-(:User)
DELETE follow;
`)
await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}
export async function down(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
try {
// cannot be rolled back
// Implement your migration here.
// await transaction.run(``)
// await transaction.commit()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
await transaction.rollback()
// eslint-disable-next-line no-console
console.log('rolled back')
throw new Error(error)
} finally {
await session.close()
}
}

View File

@ -554,6 +554,40 @@ describe('Delete a User as admin', () => {
await expect(database.neode.all('SocialMedia')).resolves.toHaveLength(0)
})
})
describe('connected follow relations', () => {
beforeEach(async () => {
const userIFollow = await Factory.build('user', {
name: 'User I Follow',
about: 'I follow this user',
id: 'uifollow',
})
const userFollowingMe = await Factory.build('user', {
name: 'User Following Me',
about: 'This user follows me',
id: 'ufollowsme',
})
await user.relateTo(userIFollow, 'following')
await userFollowingMe.relateTo(user, 'following')
})
it('will be removed completely', async () => {
const relation = await database.neode.cypher(
'MATCH (user:User {id: $id})-[relationship:FOLLOWS]-(:User) RETURN relationship',
{ id: (await user.toJson()).id },
)
const relations = relation.records.map((record) => record.get('relationship'))
expect(relations).toHaveLength(2)
await mutate({ mutation: deleteUserMutation, variables })
const relation2 = await database.neode.cypher(
'MATCH (user:User {id: $id})-[relationship:FOLLOWS]-(:User) RETURN relationship',
{ id: (await user.toJson()).id },
)
const relations2 = relation2.records.map((record) => record.get('relationship'))
expect(relations2).toHaveLength(0)
})
})
})
})
})

View File

@ -280,6 +280,9 @@ export default {
WITH user
OPTIONAL MATCH (user)<-[:OWNED_BY]-(socialMedia:SocialMedia)
DETACH DELETE socialMedia
WITH user
OPTIONAL MATCH (user)-[follow:FOLLOWS]-(:User)
DELETE follow
RETURN user {.*}
`,
{ userId },