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 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..62fe289 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -364,4 +364,103 @@ export class AiWorkflowService { throw error; } } + + async getRunItems(workflowId: string, runId: string, user: JwtUser) { + 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.`); + } + + 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}.`, + ); + } + + 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'); + } + } + + const items = await this.prisma.aiWorkflowRunItem.findMany({ + where: { workflowRunId: runId }, + include: { + comments: true, + }, + orderBy: { + createdAt: 'asc', + }, + }); + + return items; + } }