Skip to content

Commit c48c623

Browse files
feat: implement audit detail page with findings view (#32)
- API: Add PATCH /audits/findings/:id endpoint for finding status updates - Validates status against allowed values (OPEN, ACKNOWLEDGED, etc.) - Enforces authorization (user must own the audit) - Web: Create reusable audit components - FindingCard: expandable card with status selector, code display, AI summary - FindingList: findings with search, severity/status/plugin filters - SeverityChart: pie chart showing severity distribution via recharts - Web: Rewrite audit detail page with real API integration - Fetches audit + findings from API with loading/error/empty states - Stats grid showing security score, status, findings count, duration - Optimistic status updates with revert on failure - Active 'Verify on-chain' button with placeholder - Link to AI Chat for contextual questions - Web: Improve audit list page - Real API integration with status filtering and search - Links to detail pages - Proper loading, empty, and error states - Web: Extract shared API helpers to lib/api-helpers.ts
1 parent ee13e63 commit c48c623

8 files changed

Lines changed: 1021 additions & 324 deletions

File tree

apps/api/src/modules/audits/audits.controller.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
1+
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
22
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
33

44
import { CurrentUser } from '../../common/decorators/current-user.decorator';
@@ -30,4 +30,14 @@ export class AuditsController {
3030
findOne(@Param('id') id: string, @CurrentUser('id') userId: string) {
3131
return this.auditsService.findOne(id, userId);
3232
}
33+
34+
@Patch('findings/:id')
35+
@ApiOperation({ summary: 'Update finding status' })
36+
updateFindingStatus(
37+
@Param('id') id: string,
38+
@Body() body: { status: string },
39+
@CurrentUser('id') userId: string,
40+
) {
41+
return this.auditsService.updateFindingStatus(id, body.status, userId);
42+
}
3343
}

apps/api/src/modules/audits/audits.service.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
1+
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
22
import { logger } from '@veridion/logger';
33

44
import { PrismaService } from '../../common/prisma/prisma.service';
@@ -72,4 +72,32 @@ export class AuditsService {
7272

7373
return audit;
7474
}
75+
76+
async updateFindingStatus(findingId: string, status: string, userId: string) {
77+
const validStatuses = ['OPEN', 'ACKNOWLEDGED', 'FALSE_POSITIVE', 'RESOLVED'];
78+
if (!validStatuses.includes(status)) {
79+
throw new BadRequestException(
80+
`Invalid status. Must be one of: ${validStatuses.join(', ')}`,
81+
);
82+
}
83+
84+
const finding = await this.prisma.db.auditFinding.findUnique({
85+
where: { id: findingId },
86+
include: { audit: { include: { project: { select: { userId: true } } } } },
87+
});
88+
89+
if (!finding) throw new NotFoundException('Finding not found');
90+
if (finding.audit.project.userId !== userId) {
91+
throw new ForbiddenException('Access denied');
92+
}
93+
94+
const updated = await this.prisma.db.auditFinding.update({
95+
where: { id: findingId },
96+
data: { status },
97+
});
98+
99+
logger.info({ findingId, status, userId }, 'Finding status updated');
100+
101+
return updated;
102+
}
75103
}

0 commit comments

Comments
 (0)