mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-29 01:39:24 +08:00
feat(API): Allow creating a folder in the calling user's personal project (#34091)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { FolderNotFoundError } from '@/errors/folder-not-found.error';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { FolderService } from '@/services/folder.service';
|
||||
import { ProjectService } from '@/services/project.service.ee';
|
||||
|
||||
import type { PublicAPIEndpoint } from '../../shared/handler.types';
|
||||
import {
|
||||
@@ -20,6 +21,8 @@ import {
|
||||
} from '../../shared/middlewares/global.middleware';
|
||||
import { assertProjectScope } from '../../shared/services/utils.service';
|
||||
|
||||
const PERSONAL_PROJECT_ALIAS = 'personal';
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
if (error instanceof FolderNotFoundError) {
|
||||
throw new NotFoundError(error.message);
|
||||
@@ -44,7 +47,15 @@ const folderHandlers: FolderHandlers = {
|
||||
isLicensed('feat:folders'),
|
||||
apiKeyHasScopeWithGlobalScopeFallback({ scope: 'folder:create' }),
|
||||
async (req, res) => {
|
||||
const { projectId } = req.params;
|
||||
let { projectId } = req.params;
|
||||
if (projectId === PERSONAL_PROJECT_ALIAS) {
|
||||
const personalProject = await Container.get(ProjectService).getPersonalProject(req.user);
|
||||
if (!personalProject) {
|
||||
throw new NotFoundError('Could not find a personal project for this user');
|
||||
}
|
||||
projectId = personalProject.id;
|
||||
}
|
||||
|
||||
await assertProjectScope(req.user, projectId, ['folder:create']);
|
||||
|
||||
const payload = CreateFolderDto.safeParse(req.body);
|
||||
|
||||
@@ -9,7 +9,9 @@ post:
|
||||
parameters:
|
||||
- name: projectId
|
||||
in: path
|
||||
description: The ID of the project.
|
||||
description: >-
|
||||
The ID of the project, or `personal` to create the folder in the
|
||||
calling user's own personal project.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Container } from '@n8n/di';
|
||||
import type { ApiKeyScope } from '@n8n/permissions';
|
||||
|
||||
import { FolderService } from '@/services/folder.service';
|
||||
import { ProjectService } from '@/services/project.service.ee';
|
||||
|
||||
import { createFolder } from '../shared/db/folders';
|
||||
import { createOwnerWithApiKey, createMemberWithApiKey } from '../shared/db/users';
|
||||
@@ -185,6 +186,36 @@ describe('POST /projects/:projectId/folders', () => {
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
});
|
||||
|
||||
test('should create a folder in the calling users personal project when projectId is "personal"', async () => {
|
||||
testServer.license.enable('feat:folders');
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.post('/projects/personal/folders')
|
||||
.send({ name: 'Personal Shortcut Folder' });
|
||||
|
||||
expect(response.statusCode).toBe(201);
|
||||
expect(response.body).toHaveProperty('name', 'Personal Shortcut Folder');
|
||||
|
||||
const listResponse = await authOwnerAgent
|
||||
.get(`/projects/${ownerPersonalProject.id}/folders`)
|
||||
.query({ filter: JSON.stringify({ name: 'Personal Shortcut Folder' }) });
|
||||
|
||||
expect(listResponse.body.count).toBe(1);
|
||||
expect(listResponse.body.data[0].id).toBe(response.body.id);
|
||||
});
|
||||
|
||||
test('should return 404 when the calling user has no personal project', async () => {
|
||||
testServer.license.enable('feat:folders');
|
||||
vi.spyOn(Container.get(ProjectService), 'getPersonalProject').mockResolvedValueOnce(null);
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.post('/projects/personal/folders')
|
||||
.send({ name: 'Folder' });
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
expect(response.body.message).toBe('Could not find a personal project for this user');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /projects/:projectId/folders', () => {
|
||||
|
||||
Reference in New Issue
Block a user