Разбить модули Deal.Modules.* по назначению

Application проектов Discovery, Kanban, Pipeline, Settings, Tenants
разделён на Abstractions/Exceptions/Extensions/Models/Registrars/Services;
namespace приведён к путям, using потребителей мигрированы и
дедуплицированы (169 файлов), cref/FQN обновлены.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:18:14 +03:00
parent 31c434ed93
commit cd0b3b606b
356 changed files with 14436 additions and 12906 deletions
+86 -83
View File
@@ -1,83 +1,86 @@
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();
}
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;
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();
}
+5 -1
View File
@@ -1,5 +1,9 @@
using Deal.Api.Configuration;
using Deal.Modules.Tenants.Application;
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;
namespace Deal.Api.Http;
@@ -1,5 +1,9 @@
using System.Threading.RateLimiting;
using Deal.Modules.Tenants.Application;
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;
namespace Deal.Api.Http;