diff --git a/packages/cli/eslint.config.mjs b/packages/cli/eslint.config.mjs index a7e17ed9ac9..2aec476afeb 100644 --- a/packages/cli/eslint.config.mjs +++ b/packages/cli/eslint.config.mjs @@ -163,7 +163,6 @@ export default defineConfig( './src/public-api/v1/handlers/security-policy/security-policy.handler.ts', './src/public-api/v1/handlers/sso-oidc/sso-oidc.handler.ts', './src/public-api/v1/handlers/sso-saml/sso-saml.handler.ts', - './src/public-api/v1/handlers/tags/tags.handler.ts', './src/public-api/v1/handlers/users/users.handler.ee.ts', './src/public-api/v1/handlers/workflows/workflows.handler.ts', ], @@ -276,7 +275,6 @@ export default defineConfig( './src/eventbus/message-event-bus/message-event-bus.ts', './src/evaluation.ee/evaluation-collection.service.ts', './src/evaluation.ee/test-runner/test-runner.service.ee.ts', - './src/public-api/v1/handlers/tags/tags.handler.ts', // modules/** non-persistence services surfaced by narrowing the exemption './src/modules/agents/agent-knowledge.service.ts', './src/modules/agents/agent-publish.service.ts', diff --git a/packages/cli/src/public-api/types.ts b/packages/cli/src/public-api/types.ts index 475aaadfc56..09501dc8cdc 100644 --- a/packages/cli/src/public-api/types.ts +++ b/packages/cli/src/public-api/types.ts @@ -63,10 +63,6 @@ export declare namespace TestRunRequest { type Cancel = AuthenticatedRequest<{ id: string; runId: string }>; } -export declare namespace TagRequest { - type Delete = AuthenticatedRequest<{ id: string }>; -} - export declare namespace CredentialTypeRequest { type Get = AuthenticatedRequest<{ credentialTypeName: string }, {}, {}, {}>; } diff --git a/packages/cli/src/public-api/v1/controllers/tags.public.controller.ts b/packages/cli/src/public-api/v1/controllers/tags.public.controller.ts index 4687b24bd68..6d39c47e311 100644 --- a/packages/cli/src/public-api/v1/controllers/tags.public.controller.ts +++ b/packages/cli/src/public-api/v1/controllers/tags.public.controller.ts @@ -16,6 +16,7 @@ import { ApiSummary, ApiTags, Body, + Delete, Get, Param, Post, @@ -151,4 +152,29 @@ export class TagsPublicController { ...(updatedTag.updatedAt ? { updatedAt: updatedTag.updatedAt.toISOString() } : {}), }; } + + @Delete('/:tagId') + @ApiKeyScope('tag:delete') + @ApiSummary('Delete a tag') + @ApiDescription('Deletes a tag.') + @ApiTags(tags) + @ApiResponse(200, TagPublicDto) + @ApiErrorResponse(404) + async deleteTag( + _req: AuthenticatedRequest, + _res: Response, + @Param('tagId', tagIdParamSchema) tagId: string, + ): Promise { + let tag: TagEntity; + + try { + tag = await this.tagService.getById(tagId); + } catch { + throw new NotFoundError('Not Found'); + } + + await this.tagService.delete(tagId); + + return toTagPublicDto(tag); + } } diff --git a/packages/cli/src/public-api/v1/handlers/tags/spec/paths/deleteTag.generated.yml b/packages/cli/src/public-api/v1/handlers/tags/spec/paths/deleteTag.generated.yml new file mode 100644 index 00000000000..148332bb6a7 --- /dev/null +++ b/packages/cli/src/public-api/v1/handlers/tags/spec/paths/deleteTag.generated.yml @@ -0,0 +1,29 @@ +operationId: deleteTag +tags: + - Tags +summary: Delete a tag +description: Deletes a tag. +x-required-scope: tag:delete +x-eov-operation-id: unreachable +x-eov-operation-handler: v1/handlers/decorator-routed.handler +x-decorator-routed: true +parameters: + - schema: + type: string + required: true + description: The ID of the tag. + name: tagId + in: path +responses: + '200': + description: Operation successful. + content: + application/json: + schema: + $ref: ../../../../shared/spec/schemas/tagPublicDto.generated.yml + '401': + $ref: ../../../../shared/spec/responses/unauthorized.yml + '403': + $ref: ../../../../shared/spec/responses/forbidden.yml + '404': + $ref: ../../../../shared/spec/responses/notFound.yml diff --git a/packages/cli/src/public-api/v1/handlers/tags/spec/paths/tags.id.yml b/packages/cli/src/public-api/v1/handlers/tags/spec/paths/tags.id.yml deleted file mode 100644 index 9842322d03f..00000000000 --- a/packages/cli/src/public-api/v1/handlers/tags/spec/paths/tags.id.yml +++ /dev/null @@ -1,23 +0,0 @@ -delete: - x-eov-operation-id: deleteTag - x-required-scope: tag:delete - x-eov-operation-handler: v1/handlers/tags/tags.handler - tags: - - Tags - summary: Delete a tag - description: Deletes a tag. - parameters: - - $ref: '../schemas/parameters/tagId.yml' - responses: - '200': - description: Operation successful. - content: - application/json: - schema: - $ref: '../schemas/tag.yml' - '401': - $ref: '../../../../shared/spec/responses/unauthorized.yml' - '403': - $ref: '../../../../shared/spec/responses/forbidden.yml' - '404': - $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/public-api/v1/handlers/tags/spec/schemas/parameters/tagId.yml b/packages/cli/src/public-api/v1/handlers/tags/spec/schemas/parameters/tagId.yml deleted file mode 100644 index 9a218184213..00000000000 --- a/packages/cli/src/public-api/v1/handlers/tags/spec/schemas/parameters/tagId.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: id -in: path -description: The ID of the tag. -required: true -schema: - type: string diff --git a/packages/cli/src/public-api/v1/handlers/tags/tags.handler.ts b/packages/cli/src/public-api/v1/handlers/tags/tags.handler.ts deleted file mode 100644 index aa05aecf7d9..00000000000 --- a/packages/cli/src/public-api/v1/handlers/tags/tags.handler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Container } from '@n8n/di'; - -import type { TagRequest } from '../../../types'; -import type { PublicAPIEndpoint } from '../../shared/handler.types'; -import { apiKeyHasScopeWithGlobalScopeFallback } from '../../shared/middlewares/global.middleware'; - -import { NotFoundError } from '@/errors/response-errors/not-found.error'; -import { TagService } from '@/services/tag.service'; - -type TagHandlers = { - deleteTag: PublicAPIEndpoint; -}; - -const tagHandlers: TagHandlers = { - deleteTag: [ - apiKeyHasScopeWithGlobalScopeFallback({ scope: 'tag:delete' }), - async (req, res) => { - const { id } = req.params; - - let tag; - try { - tag = await Container.get(TagService).getById(id); - } catch (error) { - throw new NotFoundError('Not Found'); - } - - await Container.get(TagService).delete(id); - return res.json(tag); - }, - ], -}; - -export = tagHandlers; diff --git a/packages/cli/src/public-api/v1/openapi.decorator-routes.generated.yml b/packages/cli/src/public-api/v1/openapi.decorator-routes.generated.yml index 14976bed48e..e513b0e8b12 100644 --- a/packages/cli/src/public-api/v1/openapi.decorator-routes.generated.yml +++ b/packages/cli/src/public-api/v1/openapi.decorator-routes.generated.yml @@ -188,6 +188,8 @@ paths: $ref: ./handlers/tags/spec/paths/getTag.generated.yml put: $ref: ./handlers/tags/spec/paths/updateTag.generated.yml + delete: + $ref: ./handlers/tags/spec/paths/deleteTag.generated.yml /variables: get: $ref: ./handlers/variables/spec/paths/getVariables.generated.yml diff --git a/packages/cli/src/public-api/v1/openapi.yml b/packages/cli/src/public-api/v1/openapi.yml index 844c2fdb934..fcff6b6bf8f 100644 --- a/packages/cli/src/public-api/v1/openapi.yml +++ b/packages/cli/src/public-api/v1/openapi.yml @@ -97,8 +97,6 @@ paths: $ref: './handlers/credentials/spec/paths/credentials.id.test.yml' /credentials/schema/{credentialTypeName}: $ref: './handlers/credentials/spec/paths/credentials.schema.id.yml' - /tags/{id}: - $ref: './handlers/tags/spec/paths/tags.id.yml' /workflows/{id}/{versionId}: $ref: './handlers/workflows/spec/paths/workflows.id.versionId.yml' /workflows/{id}/test-runs: diff --git a/packages/cli/src/public-api/v1/shared/spec/parameters/_index.yml b/packages/cli/src/public-api/v1/shared/spec/parameters/_index.yml index 67bd9316c17..b84832bb163 100644 --- a/packages/cli/src/public-api/v1/shared/spec/parameters/_index.yml +++ b/packages/cli/src/public-api/v1/shared/spec/parameters/_index.yml @@ -10,8 +10,6 @@ TestRunWorkflowId: $ref: '../../../handlers/evaluations/spec/schemas/parameters/testRunWorkflowId.yml' TestRunId: $ref: '../../../handlers/evaluations/spec/schemas/parameters/testRunId.yml' -TagId: - $ref: '../../../handlers/tags/spec/schemas/parameters/tagId.yml' UserIdentifier: $ref: '../../../handlers/users/spec/schemas/parameters/userIdentifier.yml' IncludeRole: diff --git a/packages/cli/test/integration/public-api/tags.test.ts b/packages/cli/test/integration/public-api/tags.test.ts index 99254388575..b7fe8ac8c7d 100644 --- a/packages/cli/test/integration/public-api/tags.test.ts +++ b/packages/cli/test/integration/public-api/tags.test.ts @@ -161,6 +161,7 @@ describe('DELETE /tags/:id', () => { const response = await authOwnerAgent.delete('/tags/gZqmqiGAuo1dHT7q'); expect(response.statusCode).toBe(404); + expect(response.body.message).toBe('Not Found'); }); test('owner should delete the tag', async () => { @@ -171,12 +172,12 @@ describe('DELETE /tags/:id', () => { expect(response.statusCode).toBe(200); - const { id, name, createdAt, updatedAt } = response.body; - - expect(id).toEqual(tag.id); - expect(name).toEqual(tag.name); - expect(createdAt).toEqual(tag.createdAt.toISOString()); - expect(updatedAt).toEqual(tag.updatedAt.toISOString()); + expect(response.body).toStrictEqual({ + id: tag.id, + name: tag.name, + createdAt: tag.createdAt.toISOString(), + updatedAt: tag.updatedAt.toISOString(), + }); // make sure the tag actually deleted from the db const deletedTag = await Container.get(TagRepository).findOneBy({ @@ -205,6 +206,23 @@ describe('DELETE /tags/:id', () => { expect(notDeletedTag).not.toBeNull(); }); + + test('should fail due to missing "tag:delete" scope', async () => { + const tag = await createTag({}); + + const memberWithoutScope = await createMemberWithApiKey({ scopes: ['tag:list'] }); + const agent = testServer.publicApiAgentFor(memberWithoutScope); + + const response = await agent.delete(`/tags/${tag.id}`); + + expect(response.statusCode).toBe(403); + expect(response.body.message).toBe('Forbidden'); + + // make sure the tag was not deleted from the db + const notDeletedTag = await Container.get(TagRepository).findOneBy({ id: tag.id }); + + expect(notDeletedTag).not.toBeNull(); + }); }); describe('POST /tags', () => { diff --git a/packages/nodes-base/nodes/N8n/n8n-api-coverage.json b/packages/nodes-base/nodes/N8n/n8n-api-coverage.json index f4c543809f8..4c514de0cd5 100644 --- a/packages/nodes-base/nodes/N8n/n8n-api-coverage.json +++ b/packages/nodes-base/nodes/N8n/n8n-api-coverage.json @@ -125,7 +125,7 @@ "GET /tags/{tagId}": { "status": "gap" }, - "DELETE /tags/{id}": { + "DELETE /tags/{tagId}": { "status": "gap" }, "PUT /tags/{tagId}": {