mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat(core): Transfer folder structure when deleting user (no-changelog) (#13845)
This commit is contained in:
@@ -25,6 +25,7 @@ describe('UsersController', () => {
|
|||||||
mock(),
|
mock(),
|
||||||
projectService,
|
projectService,
|
||||||
eventService,
|
eventService,
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { ExternalHooks } from '@/external-hooks';
|
|||||||
import type { PublicUser } from '@/interfaces';
|
import type { PublicUser } from '@/interfaces';
|
||||||
import { listQueryMiddleware } from '@/middlewares';
|
import { listQueryMiddleware } from '@/middlewares';
|
||||||
import { AuthenticatedRequest, ListQuery, UserRequest } from '@/requests';
|
import { AuthenticatedRequest, ListQuery, UserRequest } from '@/requests';
|
||||||
|
import { FolderService } from '@/services/folder.service';
|
||||||
import { ProjectService } from '@/services/project.service.ee';
|
import { ProjectService } from '@/services/project.service.ee';
|
||||||
import { UserService } from '@/services/user.service';
|
import { UserService } from '@/services/user.service';
|
||||||
import { WorkflowService } from '@/workflows/workflow.service';
|
import { WorkflowService } from '@/workflows/workflow.service';
|
||||||
@@ -48,6 +49,7 @@ export class UsersController {
|
|||||||
private readonly credentialsService: CredentialsService,
|
private readonly credentialsService: CredentialsService,
|
||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
|
private readonly folderService: FolderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static ERROR_MESSAGES = {
|
static ERROR_MESSAGES = {
|
||||||
@@ -215,6 +217,12 @@ export class UsersController {
|
|||||||
transfereePersonalProject.id,
|
transfereePersonalProject.id,
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.folderService.transferAllFoldersToProject(
|
||||||
|
personalProjectToDelete.id,
|
||||||
|
transfereePersonalProject.id,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.projectService.clearCredentialCanUseExternalSecretsCache(
|
await this.projectService.clearCredentialCanUseExternalSecretsCache(
|
||||||
|
|||||||
@@ -276,4 +276,21 @@ export class FolderRepository extends Repository<FolderWithWorkflowAndSubFolderC
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async transferAllFoldersToProject(
|
||||||
|
fromProjectId: string,
|
||||||
|
toProjectId: string,
|
||||||
|
tx?: EntityManager,
|
||||||
|
) {
|
||||||
|
const manager = tx ?? this.manager;
|
||||||
|
return await manager.update(
|
||||||
|
Folder,
|
||||||
|
{
|
||||||
|
homeProject: { id: fromProjectId },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
homeProject: { id: toProjectId },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,15 +148,12 @@ export class FolderService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async transferFoldersToProject(fromProjectId: string, toProjectId: string) {
|
async transferAllFoldersToProject(
|
||||||
return await this.folderRepository.update(
|
fromProjectId: string,
|
||||||
{
|
toProjectId: string,
|
||||||
homeProject: { id: fromProjectId },
|
tx?: EntityManager,
|
||||||
},
|
) {
|
||||||
{
|
return await this.folderRepository.transferAllFoldersToProject(fromProjectId, toProjectId, tx);
|
||||||
homeProject: { id: toProjectId },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private transformFolderPathToTree(flatPath: FolderPathRow[]): SimpleFolderNode[] {
|
private transformFolderPathToTree(flatPath: FolderPathRow[]): SimpleFolderNode[] {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export class ProjectService {
|
|||||||
// 3. Move folders over to the target project, before deleting the project else cascading will delete workflows
|
// 3. Move folders over to the target project, before deleting the project else cascading will delete workflows
|
||||||
if (targetProject) {
|
if (targetProject) {
|
||||||
const folderService = await this.folderService;
|
const folderService = await this.folderService;
|
||||||
await folderService.transferFoldersToProject(project.id, targetProject.id);
|
await folderService.transferAllFoldersToProject(project.id, targetProject.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. delete shared credentials into this project
|
// 4. delete shared credentials into this project
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import { UsersController } from '@/controllers/users.controller';
|
import { UsersController } from '@/controllers/users.controller';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
|
import { FolderRepository } from '@/databases/repositories/folder.repository';
|
||||||
import { ProjectRelationRepository } from '@/databases/repositories/project-relation.repository';
|
import { ProjectRelationRepository } from '@/databases/repositories/project-relation.repository';
|
||||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
||||||
@@ -12,6 +13,7 @@ import { UserRepository } from '@/databases/repositories/user.repository';
|
|||||||
import { ExecutionService } from '@/executions/execution.service';
|
import { ExecutionService } from '@/executions/execution.service';
|
||||||
import { CacheService } from '@/services/cache/cache.service';
|
import { CacheService } from '@/services/cache/cache.service';
|
||||||
import { Telemetry } from '@/telemetry';
|
import { Telemetry } from '@/telemetry';
|
||||||
|
import { createFolder } from '@test-integration/db/folders';
|
||||||
|
|
||||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||||
import {
|
import {
|
||||||
@@ -458,6 +460,13 @@ describe('DELETE /users/:id', () => {
|
|||||||
getPersonalProject(transferee),
|
getPersonalProject(transferee),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
createFolder(memberPersonalProject, { name: 'folder1' }),
|
||||||
|
createFolder(memberPersonalProject, { name: 'folder2' }),
|
||||||
|
createFolder(transfereePersonalProject, { name: 'folder3' }),
|
||||||
|
createFolder(transfereePersonalProject, { name: 'folder1' }),
|
||||||
|
]);
|
||||||
|
|
||||||
const deleteSpy = jest.spyOn(Container.get(CacheService), 'deleteMany');
|
const deleteSpy = jest.spyOn(Container.get(CacheService), 'deleteMany');
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -485,6 +494,7 @@ describe('DELETE /users/:id', () => {
|
|||||||
const projectRelationRepository = Container.get(ProjectRelationRepository);
|
const projectRelationRepository = Container.get(ProjectRelationRepository);
|
||||||
const sharedWorkflowRepository = Container.get(SharedWorkflowRepository);
|
const sharedWorkflowRepository = Container.get(SharedWorkflowRepository);
|
||||||
const sharedCredentialsRepository = Container.get(SharedCredentialsRepository);
|
const sharedCredentialsRepository = Container.get(SharedCredentialsRepository);
|
||||||
|
const folderRepository = Container.get(FolderRepository);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// user, their personal project and their relationship to the team project is gone
|
// user, their personal project and their relationship to the team project is gone
|
||||||
@@ -574,6 +584,23 @@ describe('DELETE /users/:id', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.not.toBeNull(),
|
).resolves.not.toBeNull(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Assert that the folders have been transferred
|
||||||
|
|
||||||
|
const transfereeFolders = await folderRepository.findBy({
|
||||||
|
homeProject: { id: transfereePersonalProject.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 () => {
|
test('should fail to delete self', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user