Add audit log UI and backend API seeded E2E
- Add Auth Context route role guards so doctors cannot directly enter template management, user management, or audit logs. - Add Audit Logs page, sidebar entry, frontend audit API client, and API client test. - Add backend audit log query endpoint with super/admin visibility rules and query filtering. - Extend PostgreSQL integration tests to cover audit log query permissions. - Move Playwright E2E away from localStorage seed data to real backend API login and seed helpers. - Add E2E coverage for route guards and audit log visibility. - Run Playwright backend on port 3100 and proxy Vite API requests there to avoid local port conflicts. - Make server:dev use the compiled NestJS server path, avoiding tsx parameter-property injection issues. - Update README, AGENTS, feature, testing, security, deployment, progress, API, backendization, and auth/user module docs.
This commit is contained in:
27
src/api/audit.test.ts
Normal file
27
src/api/audit.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { listAuditLogs } from './audit';
|
||||
|
||||
describe('audit api', () => {
|
||||
it('loads audit logs with query params', async () => {
|
||||
vi.mocked(fetch).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers({ 'content-type': 'application/json' }),
|
||||
json: () => Promise.resolve({
|
||||
data: {
|
||||
items: [{ id: 'log-1', action: 'report.create', targetType: 'Report', createdAt: '2026-05-02T00:00:00.000Z' }],
|
||||
total: 1,
|
||||
page: 2,
|
||||
pageSize: 20,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(''),
|
||||
} as Response);
|
||||
|
||||
await expect(listAuditLogs({ page: 2, pageSize: 20, action: 'report.create' })).resolves.toMatchObject({
|
||||
total: 1,
|
||||
page: 2,
|
||||
});
|
||||
expect(fetch).toHaveBeenCalledWith('/api/audit-logs?page=2&pageSize=20&action=report.create', expect.objectContaining({ credentials: 'include' }));
|
||||
});
|
||||
});
|
||||
47
src/api/audit.ts
Normal file
47
src/api/audit.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { apiRequest } from './client';
|
||||
|
||||
export interface AuditLogItem {
|
||||
id: string;
|
||||
actorUserId?: string | null;
|
||||
actorUsername?: string | null;
|
||||
actorName?: string | null;
|
||||
actorRole?: string | null;
|
||||
action: string;
|
||||
targetType: string;
|
||||
targetId?: string | null;
|
||||
departmentId?: string | null;
|
||||
ip?: string | null;
|
||||
userAgent?: string | null;
|
||||
metadata?: unknown;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface AuditLogListResponse {
|
||||
items: AuditLogItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface AuditLogQuery {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
action?: string;
|
||||
targetType?: string;
|
||||
actor?: string;
|
||||
}
|
||||
|
||||
export const listAuditLogs = async (query: AuditLogQuery = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== '') {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
});
|
||||
const suffix = params.toString() ? `?${params}` : '';
|
||||
const response = await apiRequest<AuditLogListResponse>(`/api/audit-logs${suffix}`);
|
||||
if (!response || !Array.isArray(response.items)) {
|
||||
throw new Error('Invalid audit logs response');
|
||||
}
|
||||
return response;
|
||||
};
|
||||
Reference in New Issue
Block a user