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();
}