Skip to content

Commit e7a717e

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 b91fbbd commit e7a717e

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 { CacheService } from '../../common/cache/cache.service';
@@ -106,4 +106,32 @@ export class AuditsService {
106106
private auditCachePrefix(userId: string): string {
107107
return `audits:${userId}:`;
108108
}
109+
110+
async updateFindingStatus(findingId: string, status: string, userId: string) {
111+
const validStatuses = ['OPEN', 'ACKNOWLEDGED', 'FALSE_POSITIVE', 'RESOLVED'];
112+
if (!validStatuses.includes(status)) {
113+
throw new BadRequestException(
114+
`Invalid status. Must be one of: ${validStatuses.join(', ')}`,
115+
);
116+
}
117+
118+
const finding = await this.prisma.db.auditFinding.findUnique({
119+
where: { id: findingId },
120+
include: { audit: { include: { project: { select: { userId: true } } } } },
121+
});
122+
123+
if (!finding) throw new NotFoundException('Finding not found');
124+
if (finding.audit.project.userId !== userId) {
125+
throw new ForbiddenException('Access denied');
126+
}
127+
128+
const updated = await this.prisma.db.auditFinding.update({
129+
where: { id: findingId },
130+
data: { status },
131+
});
132+
133+
logger.info({ findingId, status, userId }, 'Finding status updated');
134+
135+
return updated;
136+
}
109137
}

0 commit comments

Comments
 (0)