From de4d486223e184caa4220244572c6c7f968ae9e3 Mon Sep 17 00:00:00 2001 From: Eugene Molodkin Date: Tue, 5 Nov 2024 16:10:24 +0100 Subject: [PATCH] wip: evaluation/tests endpoint CRUD --- .../databases/repositories/test.repository.ts | 47 ++++---- .../cli/src/evaluation/tests.controller.ts | 113 +++++++++--------- packages/cli/src/evaluation/tests.service.ts | 49 ++++++-- packages/cli/src/evaluation/tests.types.ts | 3 +- 4 files changed, 119 insertions(+), 93 deletions(-) diff --git a/packages/cli/src/databases/repositories/test.repository.ts b/packages/cli/src/databases/repositories/test.repository.ts index 1ab0bfaba5..aa6911847f 100644 --- a/packages/cli/src/databases/repositories/test.repository.ts +++ b/packages/cli/src/databases/repositories/test.repository.ts @@ -1,4 +1,4 @@ -import type { FindManyOptions, FindOptionsSelect, FindOptionsWhere } from '@n8n/typeorm'; +import type { FindManyOptions, FindOptionsWhere } from '@n8n/typeorm'; import { DataSource, In, Repository } from '@n8n/typeorm'; import { Service } from 'typedi'; @@ -21,32 +21,12 @@ export class TestRepository extends Repository { }, }; - type Select = FindOptionsSelect; - - const select: Select = { - id: true, - name: true, - createdAt: true, - updatedAt: true, - }; - - const relations: string[] = ['workflow', 'evaluationWorkflow', 'annotationTag']; - - const isDefaultSelect = options?.select === undefined; - const findManyOptions: FindManyOptions = { - select: { ...select, id: true }, where, + relations: ['annotationTag'], + order: { createdAt: 'DESC' }, }; - if (isDefaultSelect || options?.select?.updatedAt === true) { - findManyOptions.order = { updatedAt: 'ASC' }; - } - - if (relations.length > 0) { - findManyOptions.relations = relations; - } - if (options?.take) { findManyOptions.skip = options.skip; findManyOptions.take = options.take; @@ -56,4 +36,25 @@ export class TestRepository extends Repository { return { tests, count }; } + + async getOne(id: number, sharedWorkflowIds: string[]) { + return await this.findOne({ + where: { + id, + workflow: { + id: In(sharedWorkflowIds), + }, + }, + relations: ['annotationTag'], + }); + } + + async deleteById(id: number, sharedWorkflowIds: string[]) { + return await this.delete({ + id, + workflow: { + id: In(sharedWorkflowIds), + }, + }); + } } diff --git a/packages/cli/src/evaluation/tests.controller.ts b/packages/cli/src/evaluation/tests.controller.ts index 1615eb956f..da7249e8dc 100644 --- a/packages/cli/src/evaluation/tests.controller.ts +++ b/packages/cli/src/evaluation/tests.controller.ts @@ -1,5 +1,9 @@ -import { Get, /*Patch, Post,*/ RestController } from '@/decorators'; +import { Get, Post, Patch, RestController, Delete } from '@/decorators'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { listQueryMiddleware } from '@/middlewares'; +import { getSharedWorkflowIds } from '@/public-api/v1/handlers/workflows/workflows.service'; +import { isPositiveInteger } from '@/utils'; import { TestsService } from './tests.service'; import { TestsRequest } from './tests.types'; @@ -8,62 +12,61 @@ import { TestsRequest } from './tests.types'; export class TestsController { constructor(private readonly testsService: TestsService) {} - // private async getAccessibleWorkflowIds(user: User, scope: Scope) { - // if (this.license.isSharingEnabled()) { - // return await this.workflowSharingService.getSharedWorkflowIds(user, { scopes: [scope] }); - // } else { - // return await this.workflowSharingService.getSharedWorkflowIds(user, { - // workflowRoles: ['workflow:owner'], - // projectRoles: ['project:personalOwner'], - // }); - // } - // } - @Get('/', { middlewares: listQueryMiddleware }) async getMany(req: TestsRequest.GetMany) { - return await this.testsService.getMany(req.user, req.listQueryOptions); + const workflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + + return await this.testsService.getMany(req.user, req.listQueryOptions, workflowIds); } - // @Get('/:id') - // async getOne(req: ExecutionRequest.GetOne) { - // if (!isPositiveInteger(req.params.id)) { - // throw new BadRequestError('Execution ID is not a number'); - // } - // - // const workflowIds = await this.getAccessibleWorkflowIds(req.user, 'workflow:read'); - // - // if (workflowIds.length === 0) throw new NotFoundError('Execution not found'); - // - // return this.license.isSharingEnabled() - // ? await this.enterpriseExecutionService.findOne(req, workflowIds) - // : await this.executionService.findOne(req, workflowIds); - // } - // - // @Post('/delete') - // async delete(req: ExecutionRequest.Delete) { - // const workflowIds = await this.getAccessibleWorkflowIds(req.user, 'workflow:execute'); - // - // if (workflowIds.length === 0) throw new NotFoundError('Execution not found'); - // - // return await this.executionService.delete(req, workflowIds); - // } - // - // @Patch('/:id') - // async update(req: ExecutionRequest.Update) { - // if (!isPositiveInteger(req.params.id)) { - // throw new BadRequestError('Execution ID is not a number'); - // } - // - // const workflowIds = await this.getAccessibleWorkflowIds(req.user, 'workflow:read'); - // - // // Fail fast if no workflows are accessible - // if (workflowIds.length === 0) throw new NotFoundError('Execution not found'); - // - // const { body: payload } = req; - // const validatedPayload = validateExecutionUpdatePayload(payload); - // - // await this.executionService.annotate(req.params.id, validatedPayload, workflowIds); - // - // return await this.executionService.findOne(req, workflowIds); - // } + @Get('/:id') + async getOne(req: TestsRequest.GetOne) { + if (!isPositiveInteger(req.params.id)) { + throw new BadRequestError('Test ID is not a number'); + } + + const workflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + + return await this.testsService.findOne(Number(req.params.id), workflowIds); + } + + @Post('/') + async create(req: TestsRequest.Create) { + const workflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + + if (!workflowIds.includes(req.body.workflowId)) { + throw new BadRequestError('User does not have access to the workflow'); + } + + return await this.testsService.save(this.testsService.toEntity(req.body)); + } + + @Delete('/:id') + async delete(req: TestsRequest.Delete) { + if (!isPositiveInteger(req.params.id)) { + throw new BadRequestError('Test ID is not a number'); + } + + const workflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + + if (workflowIds.length === 0) throw new NotFoundError('Test not found'); + + return await this.testsService.delete(Number(req.params.id), workflowIds); + } + + @Patch('/:id') + async update(req: TestsRequest.Update) { + if (!isPositiveInteger(req.params.id)) { + throw new BadRequestError('Test ID is not a number'); + } + + const workflowIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + + // Fail fast if no workflows are accessible + if (workflowIds.length === 0) throw new NotFoundError('Workflow not found'); + + return await this.testsService.save( + this.testsService.toEntity({ ...req.body, id: Number(req.params.id) }), + ); + } } diff --git a/packages/cli/src/evaluation/tests.service.ts b/packages/cli/src/evaluation/tests.service.ts index f360b92b1f..fc22df983e 100644 --- a/packages/cli/src/evaluation/tests.service.ts +++ b/packages/cli/src/evaluation/tests.service.ts @@ -14,11 +14,38 @@ export class TestsService { private readonly workflowSharingService: WorkflowSharingService, ) {} - // toEntity(attrs: { name: string; id?: string }) { - // attrs.name = attrs.name.trim(); - // - // return this.testRepository.create(attrs); - // } + toEntity(attrs: { + name?: string; + workflowId?: string; + evaluationWorkflowId?: string; + id?: number; + }) { + const entity = { + name: attrs.name?.trim(), + }; + + if (attrs.id) { + entity.id = attrs.id; + } + + if (attrs.workflowId) { + entity.workflow = { + id: attrs.workflowId, + }; + } + + if (attrs.evaluationWorkflowId) { + entity.evaluationWorkflow = { + id: attrs.evaluationWorkflowId, + }; + } + + return this.testRepository.create(entity); + } + + async findOne(id: number, accessibleWorkflowIds: string[]) { + return await this.testRepository.getOne(id, accessibleWorkflowIds); + } async save(test: TestEntity) { await validateEntity(test); @@ -26,15 +53,11 @@ export class TestsService { return await this.testRepository.save(test); } - async delete(id: string) { - return await this.testRepository.delete(id); + async delete(id: number, accessibleWorkflowIds: string[]) { + return await this.testRepository.deleteById(id, accessibleWorkflowIds); } - async getMany(user: User, options: ListQuery.Options) { - const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, { - scopes: ['workflow:read'], - }); - - return await this.testRepository.getMany(sharedWorkflowIds, options); + async getMany(user: User, options: ListQuery.Options, accessibleWorkflowIds: string[] = []) { + return await this.testRepository.getMany(accessibleWorkflowIds, options); } } diff --git a/packages/cli/src/evaluation/tests.types.ts b/packages/cli/src/evaluation/tests.types.ts index 9d0c18824f..e1c9b4e9ef 100644 --- a/packages/cli/src/evaluation/tests.types.ts +++ b/packages/cli/src/evaluation/tests.types.ts @@ -1,4 +1,3 @@ -import type { TestEntity } from '@/databases/entities/test-entity'; import type { AuthenticatedRequest, ListQuery } from '@/requests'; // ---------------------------------- @@ -8,7 +7,7 @@ import type { AuthenticatedRequest, ListQuery } from '@/requests'; export declare namespace TestsRequest { namespace RouteParams { type TestId = { - id: TestEntity['id']; + id: string; }; }