-
Notifications
You must be signed in to change notification settings - Fork 276
Expand file tree
/
Copy pathaudit-log.controller.ts
More file actions
77 lines (73 loc) · 2.92 KB
/
audit-log.controller.ts
File metadata and controls
77 lines (73 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiQuery, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { db, Prisma } from '@trycompai/db';
import { AuthContext, OrganizationId } from '../auth/auth-context.decorator';
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
import { PermissionGuard } from '../auth/permission.guard';
import { RequirePermission } from '../auth/require-permission.decorator';
import type { AuthContext as AuthContextType } from '../auth/types';
@ApiTags('Audit Logs')
@Controller({ path: 'audit-logs', version: '1' })
@UseGuards(HybridAuthGuard, PermissionGuard)
@ApiSecurity('apikey')
export class AuditLogController {
@Get()
@RequirePermission('app', 'read')
@ApiOperation({ summary: 'Get audit logs filtered by entity type and ID' })
@ApiQuery({ name: 'entityType', required: false, description: 'Filter by entity type (e.g. policy, task, control)' })
@ApiQuery({ name: 'entityId', required: false, description: 'Filter by entity ID' })
@ApiQuery({ name: 'pathContains', required: false, description: 'Filter by path substring (e.g. automation ID)' })
@ApiQuery({ name: 'take', required: false, description: 'Number of logs to return (max 100, default 50)' })
async getAuditLogs(
@OrganizationId() organizationId: string,
@AuthContext() authContext: AuthContextType,
@Query('entityType') entityType?: string,
@Query('entityId') entityId?: string,
@Query('pathContains') pathContains?: string,
@Query('take') take?: string,
) {
// organizationId comes from auth context (not user input) — ensures tenant isolation
const where: Record<string, unknown> = { organizationId };
if (entityType) {
// Support comma-separated entity types (e.g. "risk,task")
const types = entityType.split(',').map((t) => t.trim()).filter(Boolean);
where.entityType = types.length === 1 ? types[0] : { in: types };
}
if (entityId) {
// Support comma-separated entity IDs
const ids = entityId.split(',').map((id) => id.trim()).filter(Boolean);
where.entityId = ids.length === 1 ? ids[0] : { in: ids };
}
if (pathContains) {
where.data = {
path: ['path'],
string_contains: pathContains,
} satisfies Prisma.JsonFilter;
}
const parsedTake = take
? Math.min(100, Math.max(1, parseInt(take, 10) || 50))
: 50;
const logs = await db.auditLog.findMany({
where,
include: {
user: {
select: { id: true, name: true, email: true, image: true, isPlatformAdmin: true },
},
member: true,
organization: true,
},
orderBy: { timestamp: 'desc' },
take: parsedTake,
});
return {
data: logs,
authType: authContext.authType,
...(authContext.userId && {
authenticatedUser: {
id: authContext.userId,
email: authContext.userEmail,
},
}),
};
}
}