Больше двух параметров — каждый на отдельной строке (закрывающая скобка в конце последнего); два и меньше — в одну строку. Правило добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам в 253 файлах.
97 lines
4.5 KiB
C#
97 lines
4.5 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// Хелпер записи аудита действий пользователей тенанта и операторов (единая точка — <see cref="AuditService"/>).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Актор берётся из разрешённой сессии (<c>HttpContext.Items</c>, наполняют SessionMiddleware/
|
||
/// OperatorSessionMiddleware): для тенанта — <see cref="AuditActorTypes.Tenant"/> с userId/tenantId, для
|
||
/// оператора — <see cref="AuditActorTypes.Operator"/> без tenantId. IP — адрес клиента без порта.
|
||
/// Детали — минимальные, без секретов (пароли/токены/api-ключи). Нет сессии — no-op (вызывать после
|
||
/// проверки HasUser, но безопасно и без неё). Append-only, как весь аудит.
|
||
/// </remarks>
|
||
public static class AuditAppender
|
||
{
|
||
/// <summary>
|
||
/// Пишет событие действия пользователя тенанта (актор tenant).
|
||
/// </summary>
|
||
/// <param name="context">Контекст запроса (источник актора и IP).</param>
|
||
/// <param name="eventType">Тип события — константа <see cref="AuditEvents"/>.</param>
|
||
/// <param name="details">Минимальные детали события (обычно анонимный объект) или null.</param>
|
||
/// <param name="ct">Токен отмены.</param>
|
||
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<AuditService>();
|
||
await audit.AppendAsync(
|
||
new AuditRecordDto(
|
||
eventType,
|
||
AuditActorTypes.Tenant,
|
||
ActorId: user.UserId,
|
||
TenantId: user.TenantId,
|
||
Ip: ClientIp(context),
|
||
DetailJson: DetailJson(details)),
|
||
ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Пишет событие действия оператора (актор operator, без tenantId).
|
||
/// </summary>
|
||
/// <param name="context">Контекст запроса (источник актора и IP).</param>
|
||
/// <param name="eventType">Тип события — константа <see cref="AuditEvents"/>.</param>
|
||
/// <param name="details">Минимальные детали события (обычно анонимный объект) или null.</param>
|
||
/// <param name="ct">Токен отмены.</param>
|
||
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<AuditService>();
|
||
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();
|
||
}
|