feat(admin): журнал аудита с ротацией и апгрейд таблицы пользователей
Добавлен 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:
@@ -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>
|
||||
Reference in New Issue
Block a user