From 0973f04fc679734e625c63ddfd06e46cbf1572eb Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 15 Sep 2025 16:39:15 +0200 Subject: [PATCH 1/4] feat: get workflow run items --- src/api/ai-workflow/ai-workflow.controller.ts | 27 +++++ src/api/ai-workflow/ai-workflow.service.ts | 101 ++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/src/api/ai-workflow/ai-workflow.controller.ts b/src/api/ai-workflow/ai-workflow.controller.ts index 000417e..a00dace 100644 --- a/src/api/ai-workflow/ai-workflow.controller.ts +++ b/src/api/ai-workflow/ai-workflow.controller.ts @@ -38,6 +38,33 @@ import { User } from 'src/shared/decorators/user.decorator'; export class AiWorkflowController { constructor(private readonly aiWorkflowService: AiWorkflowService) {} + @Get('/:workflowId/runs/:runId/items') + @Roles( + UserRole.Admin, + UserRole.Copilot, + UserRole.ProjectManager, + UserRole.User, + ) + @Scopes(Scope.ReadWorkflowRun) + @ApiOperation({ + summary: 'Get AIWorkflowRunItems for a given workflow run ID', + }) + @ApiResponse({ + status: 200, + description: 'The AIWorkflowRunItems for the given run ID.', + }) + @ApiResponse({ status: 400, description: 'Bad Request.' }) + @ApiResponse({ status: 401, description: 'Unauthorized.' }) + @ApiResponse({ status: 403, description: 'Forbidden.' }) + @ApiResponse({ status: 404, description: 'Run not found.' }) + async getRunItems( + @Param('workflowId') workflowId: string, + @Param('runId') runId: string, + @User() user: JwtUser, + ) { + return this.aiWorkflowService.getRunItems(workflowId, runId, user); + } + @Post() @Roles(UserRole.Admin) @Scopes(Scope.CreateWorkflow) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index 3a2ff09..204c1cb 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -364,4 +364,105 @@ export class AiWorkflowService { throw error; } } + + async getRunItems(workflowId: string, runId: string, user: JwtUser) { + // Validate workflow exists + const workflow = await this.prisma.aiWorkflow.findUnique({ + where: { id: workflowId }, + }); + if (!workflow) { + this.logger.error(`Workflow with id ${workflowId} not found.`); + throw new NotFoundException(`Workflow with id ${workflowId} not found.`); + } + + // Validate run exists and belongs to workflow + const run = await this.prisma.aiWorkflowRun.findUnique({ + where: { id: runId }, + include: { workflow: true }, + }); + if (!run || run.workflowId !== workflowId) { + this.logger.error( + `Run with id ${runId} not found or does not belong to workflow ${workflowId}.`, + ); + throw new NotFoundException( + `Run with id ${runId} not found or does not belong to workflow ${workflowId}.`, + ); + } + + // Permission check similar to getWorkflowRuns + const submission = run.submissionId + ? await this.prisma.submission.findUnique({ + where: { id: run.submissionId }, + }) + : null; + const challengeId = submission?.challengeId; + + if (!challengeId) { + this.logger.error(`Challenge ID not found for submission ${run.submissionId}`); + throw new InternalServerErrorException( + `Challenge ID not found for submission ${run.submissionId}`, + ); + } + + const challenge: ChallengeData = + await this.challengeApiService.getChallengeDetail(challengeId); + + if (!challenge) { + throw new InternalServerErrorException( + `Challenge with id ${challengeId} was not found!`, + ); + } + + const isM2mOrAdmin = user.isMachine || user.roles?.includes(UserRole.Admin); + if (!isM2mOrAdmin) { + const requiredRoles = [ + UserRole.Reviewer, + UserRole.ProjectManager, + UserRole.Copilot, + UserRole.Submitter, + ].map((r) => r.toLowerCase()); + + const memberRoles = ( + await this.resourceApiService.getMemberResourcesRoles( + challengeId, + user.userId, + ) + ).filter((resource) => + requiredRoles.some( + (role) => + resource.roleName!.toLowerCase().indexOf(role.toLowerCase()) >= 0, + ), + ); + + if (!memberRoles.length) { + throw new ForbiddenException('Insufficient permissions'); + } + + if ( + challenge.status !== ChallengeStatus.COMPLETED && + memberRoles.some( + (r) => r.roleName?.toLowerCase() === UserRole.Submitter.toLowerCase(), + ) && + user.userId !== submission?.memberId + ) { + this.logger.log( + `Submitter ${user.userId} trying to access AI workflow run for other submitters.`, + ); + throw new ForbiddenException('Insufficient permissions'); + } + } + + // Fetch run items with comments + const items = await this.prisma.aiWorkflowRunItem.findMany({ + where: { workflowRunId: runId }, + include: { + comments: true, + }, + orderBy: { + createdAt: 'asc', + }, + }); + + return items; + } } From 58437dba9e20b0331f2934c6107bc6aa5625254c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 15 Sep 2025 17:58:26 +0200 Subject: [PATCH 2/4] feat: get workflow run items --- src/api/ai-workflow/ai-workflow.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index 204c1cb..b0621fd 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -366,7 +366,6 @@ export class AiWorkflowService { } async getRunItems(workflowId: string, runId: string, user: JwtUser) { - // Validate workflow exists const workflow = await this.prisma.aiWorkflow.findUnique({ where: { id: workflowId }, }); @@ -375,7 +374,6 @@ export class AiWorkflowService { throw new NotFoundException(`Workflow with id ${workflowId} not found.`); } - // Validate run exists and belongs to workflow const run = await this.prisma.aiWorkflowRun.findUnique({ where: { id: runId }, include: { workflow: true }, @@ -389,7 +387,6 @@ export class AiWorkflowService { ); } - // Permission check similar to getWorkflowRuns const submission = run.submissionId ? await this.prisma.submission.findUnique({ where: { id: run.submissionId }, @@ -452,7 +449,6 @@ export class AiWorkflowService { } } - // Fetch run items with comments const items = await this.prisma.aiWorkflowRunItem.findMany({ where: { workflowRunId: runId }, include: { From ea9e6a2d1ed59ac27c97748b0f7b19126d5f464c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 15 Sep 2025 17:59:36 +0200 Subject: [PATCH 3/4] fix: lint --- src/api/ai-workflow/ai-workflow.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index b0621fd..62fe289 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -395,7 +395,9 @@ export class AiWorkflowService { const challengeId = submission?.challengeId; if (!challengeId) { - this.logger.error(`Challenge ID not found for submission ${run.submissionId}`); + this.logger.error( + `Challenge ID not found for submission ${run.submissionId}`, + ); throw new InternalServerErrorException( `Challenge ID not found for submission ${run.submissionId}`, ); From 2a052b666bb6c14600e632cadbbfeee5eab118de Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Mon, 15 Sep 2025 19:32:17 +0200 Subject: [PATCH 4/4] deploy to dev --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4c07744..3737228 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,6 +75,7 @@ workflows: only: - develop - feat/ai-workflows + - pm-1782 - 'build-prod': context: org-global