mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(core): Transfer folder structure when deleting a project (no-changelog) (#13865)
This commit is contained in:
@@ -148,6 +148,17 @@ export class FolderService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async transferFoldersToProject(fromProjectId: string, toProjectId: string) {
|
||||||
|
return await this.folderRepository.update(
|
||||||
|
{
|
||||||
|
homeProject: { id: fromProjectId },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
homeProject: { id: toProjectId },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private transformFolderPathToTree(flatPath: FolderPathRow[]): SimpleFolderNode[] {
|
private transformFolderPathToTree(flatPath: FolderPathRow[]): SimpleFolderNode[] {
|
||||||
if (!flatPath || flatPath.length === 0) {
|
if (!flatPath || flatPath.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ export class ProjectService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get folderService() {
|
||||||
|
return import('@/services/folder.service').then(({ FolderService }) =>
|
||||||
|
Container.get(FolderService),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async deleteProject(
|
async deleteProject(
|
||||||
user: User,
|
user: User,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@@ -134,16 +140,22 @@ export class ProjectService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. delete shared credentials into this project
|
// 3. Move folders over to the target project, before deleting the project else cascading will delete workflows
|
||||||
|
if (targetProject) {
|
||||||
|
const folderService = await this.folderService;
|
||||||
|
await folderService.transferFoldersToProject(project.id, targetProject.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. delete shared credentials into this project
|
||||||
// Cascading deletes take care of this.
|
// Cascading deletes take care of this.
|
||||||
|
|
||||||
// 4. delete shared workflows into this project
|
// 5. delete shared workflows into this project
|
||||||
// Cascading deletes take care of this.
|
// Cascading deletes take care of this.
|
||||||
|
|
||||||
// 5. delete project
|
// 6. delete project
|
||||||
await this.projectRepository.remove(project);
|
await this.projectRepository.remove(project);
|
||||||
|
|
||||||
// 6. delete project relations
|
// 7. delete project relations
|
||||||
// Cascading deletes take care of this.
|
// Cascading deletes take care of this.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { EntityNotFoundError } from '@n8n/typeorm';
|
|||||||
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
||||||
import type { Project } from '@/databases/entities/project';
|
import type { Project } from '@/databases/entities/project';
|
||||||
import type { GlobalRole } from '@/databases/entities/user';
|
import type { GlobalRole } 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';
|
||||||
@@ -13,6 +14,7 @@ import { SharedWorkflowRepository } from '@/databases/repositories/shared-workfl
|
|||||||
import { getWorkflowById } from '@/public-api/v1/handlers/workflows/workflows.service';
|
import { getWorkflowById } from '@/public-api/v1/handlers/workflows/workflows.service';
|
||||||
import { CacheService } from '@/services/cache/cache.service';
|
import { CacheService } from '@/services/cache/cache.service';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
|
import { createFolder } from '@test-integration/db/folders';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCredentialById,
|
getCredentialById,
|
||||||
@@ -1048,7 +1050,7 @@ describe('DELETE /project/:projectId', () => {
|
|||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('migrates workflows and credentials to another project if `migrateToProject` is passed', async () => {
|
test('migrates folders, workflows and credentials to another project if `migrateToProject` is passed', async () => {
|
||||||
//
|
//
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
//
|
//
|
||||||
@@ -1071,6 +1073,11 @@ describe('DELETE /project/:projectId', () => {
|
|||||||
{ project: otherProject, role: 'workflow:editor' },
|
{ project: otherProject, role: 'workflow:editor' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await createFolder(projectToBeDeleted, { name: 'folder1' });
|
||||||
|
await createFolder(projectToBeDeleted, { name: 'folder2' });
|
||||||
|
await createFolder(targetProject, { name: 'folder1' });
|
||||||
|
await createFolder(otherProject, { name: 'folder3' });
|
||||||
|
|
||||||
//
|
//
|
||||||
// ACT
|
// ACT
|
||||||
//
|
//
|
||||||
@@ -1128,6 +1135,22 @@ describe('DELETE /project/:projectId', () => {
|
|||||||
role: 'credential:user',
|
role: 'credential:user',
|
||||||
}),
|
}),
|
||||||
).resolves.toBeDefined();
|
).resolves.toBeDefined();
|
||||||
|
|
||||||
|
// folders are in the target project
|
||||||
|
const foldersInTargetProject = await Container.get(FolderRepository).findBy({
|
||||||
|
homeProject: { id: targetProject.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const foldersInDeletedProject = await Container.get(FolderRepository).findBy({
|
||||||
|
homeProject: { id: projectToBeDeleted.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(foldersInDeletedProject).toHaveLength(0);
|
||||||
|
|
||||||
|
expect(foldersInTargetProject).toHaveLength(3);
|
||||||
|
expect(foldersInTargetProject.map((f) => f.name)).toEqual(
|
||||||
|
expect.arrayContaining(['folder1', 'folder1', 'folder2']),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// This test is testing behavior that is explicitly not enabled right now,
|
// This test is testing behavior that is explicitly not enabled right now,
|
||||||
|
|||||||
Reference in New Issue
Block a user