Files
iistwin/server/utils/audit.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

47 lines
1.4 KiB
TypeScript

import { db } from '../db';
import { systemAuditLog } from '@shared/schema';
export interface AuditEntry {
action: string;
userId?: number | null;
organizationId?: number | null;
taskId?: number | null;
details?: Record<string, unknown> | null;
ip?: string | null;
userAgent?: string | null;
}
/**
* Write a system audit log entry asynchronously (fire-and-forget).
* A write failure is logged to console but never throws — it must not
* interrupt the primary action.
*/
export function logAudit(entry: AuditEntry): void {
db.insert(systemAuditLog)
.values({
action: entry.action,
userId: entry.userId ?? null,
organizationId: entry.organizationId ?? null,
taskId: entry.taskId ?? null,
details: entry.details ?? null,
ip: entry.ip ?? null,
userAgent: entry.userAgent ?? null,
})
.catch(err => {
console.error('[audit] Failed to write system audit log:', err);
});
}
/**
* Extract the client IP address from an Express request,
* honouring the X-Forwarded-For header (trust proxy must be enabled).
*/
export function getClientIp(req: { ip?: string; headers: Record<string, string | string[] | undefined> }): string | null {
const forwarded = req.headers['x-forwarded-for'];
if (forwarded) {
const first = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
return first.trim() || null;
}
return req.ip ?? null;
}