Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using Deal.Modules.Tenants.Application;
|
||||
using Deal.Modules.Tenants.Application.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user