using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.DependencyInjection; using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Http; /// /// Хелпер записи аудита действий пользователей тенанта и операторов (единая точка — ). /// /// /// Актор берётся из разрешённой сессии (HttpContext.Items, наполняют SessionMiddleware/ /// OperatorSessionMiddleware): для тенанта — с userId/tenantId, для /// оператора — без tenantId. IP — адрес клиента без порта. /// Детали — минимальные, без секретов (пароли/токены/api-ключи). Нет сессии — no-op (вызывать после /// проверки HasUser, но безопасно и без неё). Append-only, как весь аудит. /// public static class AuditAppender { /// /// Пишет событие действия пользователя тенанта (актор tenant). /// /// Контекст запроса (источник актора и IP). /// Тип события — константа . /// Минимальные детали события (обычно анонимный объект) или null. /// Токен отмены. public static async Task AppendTenantAsync( HttpContext context, string eventType, object? details, CancellationToken ct) { CurrentUser? user = context.GetCurrentUser(); if (user is null) { return; } AuditService audit = context.RequestServices.GetRequiredService(); await audit.AppendAsync( new AuditRecordDto( eventType, AuditActorTypes.Tenant, ActorId: user.UserId, TenantId: user.TenantId, Ip: ClientIp(context), DetailJson: DetailJson(details)), ct); } /// /// Пишет событие действия оператора (актор operator, без tenantId). /// /// Контекст запроса (источник актора и IP). /// Тип события — константа . /// Минимальные детали события (обычно анонимный объект) или null. /// Токен отмены. public static async Task AppendOperatorAsync( HttpContext context, string eventType, object? details, CancellationToken ct) { CurrentOperator? operatorIdentity = context.GetCurrentOperator(); if (operatorIdentity is null) { return; } AuditService audit = context.RequestServices.GetRequiredService(); await audit.AppendAsync( new AuditRecordDto( eventType, AuditActorTypes.Operator, ActorId: operatorIdentity.OperatorId, TenantId: null, Ip: ClientIp(context), DetailJson: DetailJson(details)), ct); } // Сериализует детали события (null — деталей нет). // details: Объект деталей или null. // Возвращает: JSON деталей (camelCase) или null. private static string? DetailJson(object? details) => details is null ? null : AuditService.ToDetailJson(details); // IP-адрес клиента для аудита (без порта; null, если недоступен). // context: Контекст запроса. // Возвращает: Строковое представление IP или null. private static string? ClientIp(HttpContext context) => context.Connection.RemoteIpAddress?.ToString(); }