feat(admin): журнал аудита с ротацией и апгрейд таблицы пользователей
Build and Push Auth Portal Docker Image / build-and-push (push) Successful in 1m37s
Build and Push Auth Portal Docker Image / create-release (push) Skipped

Добавлен audit log (solution-users-6) с retention N дней и hourly purge; таблица пользователей приведена к DNA solution-users-1 (Filters, avatar, sorting).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-21 04:20:47 +07:00
co-authored by Cursor
parent e430abe98b
commit 26fc8253c3
30 changed files with 2959 additions and 59 deletions
+75
View File
@@ -0,0 +1,75 @@
import { z } from 'zod'
export const AUDIT_SEVERITIES = ['info', 'warning', 'critical'] as const
export type AuditSeverity = (typeof AUDIT_SEVERITIES)[number]
export const auditSeveritySchema = z.enum(AUDIT_SEVERITIES)
export const AUDIT_TARGET_TYPES = [
'user',
'settings',
'session',
'system',
] as const
export type AuditTargetType = (typeof AUDIT_TARGET_TYPES)[number]
export const auditTargetTypeSchema = z.enum(AUDIT_TARGET_TYPES)
/** Well-known action keys written by the API. */
export const AUDIT_ACTIONS = [
'auth.login',
'auth.login_failed',
'auth.logout',
'user.create',
'user.update',
'user.delete',
'user.access_update',
'app_switcher.update',
'audit.settings_update',
'audit.purge',
] as const
export type AuditAction = (typeof AUDIT_ACTIONS)[number]
export const auditActionSchema = z.enum(AUDIT_ACTIONS)
export const auditLogEntrySchema = z.object({
id: z.string(),
action: z.string(),
severity: auditSeveritySchema,
actor_user_id: z.string().nullable(),
actor_email: z.string().nullable(),
actor_name: z.string().nullable(),
target_type: auditTargetTypeSchema.nullable(),
target_id: z.string().nullable(),
summary: z.string(),
details: z.record(z.string(), z.unknown()).nullable(),
ip: z.string().nullable(),
created_at: z.string(),
})
export type AuditLogEntry = z.infer<typeof auditLogEntrySchema>
export const auditListQuerySchema = z.object({
action: z.string().optional(),
severity: auditSeveritySchema.optional(),
limit: z.coerce.number().int().min(1).max(500).default(200),
})
export type AuditListQuery = z.infer<typeof auditListQuerySchema>
export const DEFAULT_AUDIT_RETENTION_DAYS = 90
export const MIN_AUDIT_RETENTION_DAYS = 7
export const MAX_AUDIT_RETENTION_DAYS = 3650
export const auditSettingsSchema = z.object({
retention_days: z
.number()
.int()
.min(MIN_AUDIT_RETENTION_DAYS)
.max(MAX_AUDIT_RETENTION_DAYS),
})
export type AuditSettings = z.infer<typeof auditSettingsSchema>
export const putAuditSettingsSchema = auditSettingsSchema
export type PutAuditSettings = z.infer<typeof putAuditSettingsSchema>
export const auditPurgeResponseSchema = z.object({
deleted: z.number().int().nonnegative(),
retention_days: z.number().int(),
})
export type AuditPurgeResponse = z.infer<typeof auditPurgeResponseSchema>