feat(core): Allow transferring user's data to team project when deleting them (no-changelog) (#13941)

This commit is contained in:
Ricardo Espinoza
2025-03-18 15:25:40 -04:00
committed by GitHub
parent 24d8eac85d
commit 5633502c63
2 changed files with 85 additions and 11 deletions

View File

@@ -189,9 +189,9 @@ export class UsersController {
let transfereeId;
if (transferId) {
const transfereePersonalProject = await this.projectRepository.findOneBy({ id: transferId });
const transfereeProject = await this.projectRepository.findOneBy({ id: transferId });
if (!transfereePersonalProject) {
if (!transfereeProject) {
throw new NotFoundError(
'Request to delete a user failed because the transferee project was not found in DB',
);
@@ -199,8 +199,7 @@ export class UsersController {
const transferee = await this.userRepository.findOneByOrFail({
projectRelations: {
projectId: transfereePersonalProject.id,
role: 'project:personalOwner',
projectId: transfereeProject.id,
},
});
@@ -209,25 +208,23 @@ export class UsersController {
await this.userService.getManager().transaction(async (trx) => {
await this.workflowService.transferAll(
personalProjectToDelete.id,
transfereePersonalProject.id,
transfereeProject.id,
trx,
);
await this.credentialsService.transferAll(
personalProjectToDelete.id,
transfereePersonalProject.id,
transfereeProject.id,
trx,
);
await this.folderService.transferAllFoldersToProject(
personalProjectToDelete.id,
transfereePersonalProject.id,
transfereeProject.id,
trx,
);
});
await this.projectService.clearCredentialCanUseExternalSecretsCache(
transfereePersonalProject.id,
);
await this.projectService.clearCredentialCanUseExternalSecretsCache(transfereeProject.id);
}
const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([

View File

@@ -384,7 +384,7 @@ describe('DELETE /users/:id', () => {
expect(credential).toBeNull();
});
test('should delete user and team relations and transfer their personal resources', async () => {
test('should delete user and team relations and transfer their personal resources to user', async () => {
//
// ARRANGE
//
@@ -603,6 +603,83 @@ describe('DELETE /users/:id', () => {
expect(deletedUserFolders).toHaveLength(0);
});
test('should delete user and transfer their personal resources to team project', async () => {
//
// ARRANGE
//
const memberToDelete = await createMember();
const teamProject = await createTeamProject('test project', owner);
const memberPersonalProject = await getPersonalProject(memberToDelete);
const memberToDeleteWorkflow = await createWorkflow({ name: 'workflow1' }, memberToDelete);
const memberToDeleteCredential = await saveCredential(randomCredentialPayload(), {
user: memberToDelete,
role: 'credential:owner',
});
await Promise.all([
createFolder(memberPersonalProject, { name: 'folder1' }),
createFolder(memberPersonalProject, { name: 'folder2' }),
createFolder(teamProject, { name: 'folder3' }),
createFolder(teamProject, { name: 'folder1' }),
]);
const deleteSpy = jest.spyOn(Container.get(CacheService), 'deleteMany');
//
// ACT
//
await ownerAgent
.delete(`/users/${memberToDelete.id}`)
.query({ transferId: teamProject.id })
.expect(200);
//
// ASSERT
//
deleteSpy.mockClear();
const sharedWorkflowRepository = Container.get(SharedWorkflowRepository);
const sharedCredentialRepository = Container.get(SharedCredentialsRepository);
const folderRepository = Container.get(FolderRepository);
const userRepository = Container.get(UserRepository);
// assert member has been deleted
const user = await userRepository.findOneBy({ id: memberToDelete.id });
expect(user).toBeNull();
// assert the workflow has been transferred
const memberToDeleteWorkflowProjectOwner =
await sharedWorkflowRepository.getWorkflowOwningProject(memberToDeleteWorkflow.id);
expect(memberToDeleteWorkflowProjectOwner?.id).toBe(teamProject.id);
// assert the credential has been transferred
const memberToDeleteCredentialProjectOwner =
await sharedCredentialRepository.findCredentialOwningProject(memberToDeleteCredential.id);
expect(memberToDeleteCredentialProjectOwner?.id).toBe(teamProject.id);
// assert that the folders have been transferred
const transfereeFolders = await folderRepository.findBy({
homeProject: { id: teamProject.id },
});
const deletedUserFolders = await folderRepository.findBy({
homeProject: { id: memberPersonalProject.id },
});
expect(transfereeFolders).toHaveLength(4);
expect(transfereeFolders.map((folder) => folder.name)).toEqual(
expect.arrayContaining(['folder1', 'folder2', 'folder3', 'folder1']),
);
expect(deletedUserFolders).toHaveLength(0);
});
test('should fail to delete self', async () => {
await ownerAgent.delete(`/users/${owner.id}`).expect(400);