feat(audit): implement audit logging for search requests and results
All checks were successful
Publish FaceAI Container / publish (push) Successful in 13m22s

- Added configuration options for audit database path and retention days in backend and processor.
- Integrated audit logging in server and worker processes to track search requests, completions, and failures.
- Created utility functions for reading and parsing audit logs in end-to-end tests.
- Updated Docker Compose files to include audit database configuration.
- Added new tests to verify audit log entries for successful and no-results searches.
This commit is contained in:
MaddoScientisto 2026-05-19 23:29:38 +02:00
commit 32db61c381
14 changed files with 1067 additions and 16 deletions

View file

@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { Worker } from 'bullmq';
import { config } from './config.js';
import { createAuditStore } from '../../backend/src/audit-store.js';
import {
createRedisConnection,
getSearchRecord,
@ -16,6 +17,7 @@ import {
import { parseMatcherCsv, resolvePklPath, runFaceMatcher } from './worker-utils.js';
const connection = createRedisConnection(config.redisUrl);
const auditStore = createAuditStore({ dbPath: config.auditDbPath, retentionDays: config.auditRetentionDays });
async function ensureMatcherBinaryAvailable() {
try {
@ -93,9 +95,23 @@ async function completeSearch(search, searchId, searchLogPath, matchCount, match
completionCode
});
await markSearchCompleted(connection, searchId, result.id, matchCount, config.searchTtlSeconds, {
const completedSearch = await markSearchCompleted(connection, searchId, result.id, matchCount, config.searchTtlSeconds, {
completionCode
});
auditStore.markSearchCompleted({
searchId,
user: { id: search.userId },
race: {
id: search.raceId,
name: search.raceName,
storage: search.raceStorage
},
resultId: result.id,
matchCount,
matches,
completionCode,
completedAt: completedSearch?.completedAt || Date.now()
});
await releaseActiveSearchLock(connection, search.userId, searchId);
}
@ -178,7 +194,22 @@ async function processJob(job) {
message: error.message,
stack: error.stack || null
});
await markSearchFailed(connection, searchId, 'PROCESSOR_ERROR', error.message, config.searchTtlSeconds);
const failedSearch = await markSearchFailed(connection, searchId, 'PROCESSOR_ERROR', error.message, config.searchTtlSeconds);
auditStore.markSearchFailed({
searchId,
user: { id: search.userId },
race: {
id: search.raceId,
name: search.raceName,
storage: search.raceStorage
},
errorCode: 'PROCESSOR_ERROR',
errorMessage: error.message,
completedAt: failedSearch?.completedAt || Date.now(),
payload: {
stack: error.stack || null
}
});
await releaseActiveSearchLock(connection, search.userId, searchId);
throw error;
}