Deal — единая кодовая база
ci / build-test (push) Canceled after 0s

SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
This commit is contained in:
Rustam Khalimov
2026-09-11 23:56:47 +03:00
commit 27c7831910
1383 changed files with 158436 additions and 0 deletions
@@ -0,0 +1,159 @@
using Deal.Contracts.Integrations.Abstractions;
using Deal.Infrastructure.Integrations.Abstractions;
using Deal.Infrastructure.Integrations.Models;
using Deal.Infrastructure.Integrations.Options;
using Deal.Infrastructure.Integrations.Services;
using Deal.Infrastructure.Integrations.Sources;
using Deal.Infrastructure.Persistence;
using Deal.Infrastructure.Persistence.Repositories;
using Deal.Infrastructure.Security;
using Deal.Infrastructure.Services;
using Deal.Infrastructure.Tenancy;
using Deal.Modules.Cards.Application.Abstractions;
using Deal.Modules.Cards.Application.Sources;
using Deal.Modules.Discovery.Application.Abstractions;
using Deal.Modules.Kanban.Application.Abstractions;
using Deal.Modules.Pipeline.Application.Abstractions;
using Deal.Modules.Settings.Application.Abstractions;
using Deal.Modules.Telegram.Application;
using Deal.Modules.Tenants.Application.Abstractions;
using Deal.Modules.Tenants.Application.Models;
using Deal.SharedKernel.Tenants.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Deal.Infrastructure;
/// <summary>
/// DI-регистрация адаптеров персистентности Deal.Infrastructure.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Регистрирует EF-адаптеры портов модулей
/// </summary>
/// <param name="tenantLimitDefaults">Дефолт-бюджет лениво создаваемых строк tenant_limits. Передаётся из конфигурации/env DEAL_DEFAULT_AI_BUDGET в Program.cs.</param>
public static IServiceCollection AddDealPersistence(this IServiceCollection services, TokenLimitDefaults? tenantLimitDefaults = null)
{
services.AddScoped<IAuthStore, AuthStore>();
services.AddScoped<IOperatorAuthStore, OperatorAuthStore>();
services.AddScoped<ITenantRepository, TenantRepository>();
services.AddScoped<IAuditLogStore, AuditLogStore>();
services.AddScoped<ITokenUsageEventStore, TokenUsageEventStore>();
services.AddScoped<IInviteStore, InviteStore>();
services.AddScoped<ITenantLimitStore>(provider => new TenantLimitStore(
provider.GetRequiredService<DealDbContext>(),
tenantLimitDefaults ?? TokenBudgetDefaults.Default));
services.AddScoped<IRateLimitCounterStore, RateLimitCounterStore>();
services.AddScoped<ISettingsStore, SettingsStore>();
services.AddScoped<IGlobalSettingsStore, GlobalSettingsStore>();
services.AddScoped<ICardStore, KanbanStore>();
services.AddScoped<ISourceContentProvider, TelegramSourceContentProvider>();
services.AddScoped<IPipelineStore, PipelineStore>();
services.AddScoped<IMlLearningStore, MlLearningStore>();
services.AddScoped<ITelegramStore, TelegramStore>();
services.AddScoped<IDiscoveryStore, DiscoveryStore>();
services.AddScoped<ITenantProvisioner, TenantProvisioningService>();
services.AddScoped<TenantSchemaMigrationService>();
services.AddScoped<ICardMover, CardMover>();
return services;
}
/// <summary>
/// Регистрирует адаптеры внешних интеграций
/// </summary>
/// <param name="mlOptions">Конфигурация секции <c>Services:Ml</c> — выбор реализации IMlClient.</param>
/// <param name="aiOptions">Конфигурация секции <c>Services:Ai</c> — выбор реализации IAiClassifier/IAiTools.</param>
/// <param name="telegramOptions">Конфигурация секции <c>Services:Telegram</c> — выбор реализации ITelegramGateway.</param>
public static IServiceCollection AddDealIntegrations(
this IServiceCollection services,
MlServiceOptions mlOptions,
AiServiceOptions aiOptions,
TelegramServiceOptions telegramOptions,
MtlsCertificates? mtlsCertificates = null)
{
services.AddScoped<TokenUsageRecorder>();
if (mlOptions.UseLocal)
{
services.AddScoped<IMlClient, LocalMlClient>();
}
else
{
services.AddSingleton(new MlGrpcConnection(mlOptions, mtlsCertificates));
services.AddSingleton<MlStatusCache>();
services.AddScoped<GrpcMlClient>();
services.AddScoped<IMlClient>(provider => provider.GetRequiredService<GrpcMlClient>());
services.AddScoped<IMlTrainClient>(provider => provider.GetRequiredService<GrpcMlClient>());
}
services.AddScoped<IColumnSuggester, LocalColumnSuggester>();
if (aiOptions.UseLocal)
{
services.AddScoped<IAiClassifier, LocalAiClassifier>();
services.AddScoped<IAiTools, LocalAiTools>();
}
else
{
services.AddSingleton(new AiGrpcConnection(aiOptions, mtlsCertificates));
services.AddScoped<AiProviderConfigBuilder>();
services.AddScoped<GrpcAiClassifier>();
services.AddScoped<LocalAiClassifier>();
services.AddScoped<IAiClassifier>(provider => new BudgetedAiClassifier(
provider.GetRequiredService<GrpcAiClassifier>(),
provider.GetRequiredService<LocalAiClassifier>(),
provider.GetRequiredService<ITenantLimitStore>(),
provider.GetRequiredService<ITenantContext>(),
provider.GetRequiredService<ILogger<BudgetedAiClassifier>>()));
services.AddScoped<GrpcAiTools>();
services.AddScoped<IAiTools>(provider => new BudgetedAiTools(
provider.GetRequiredService<GrpcAiTools>(),
provider.GetRequiredService<ITenantLimitStore>(),
provider.GetRequiredService<ITenantContext>(),
provider.GetRequiredService<ILogger<BudgetedAiTools>>()));
}
if (telegramOptions.UseLocal)
{
services.AddSingleton<ITelegramGateway, LocalTelegramGateway>();
}
else
{
services.AddSingleton(new TelegramGrpcConnection(telegramOptions, mtlsCertificates));
services.AddScoped<GrpcTelegramClient>();
services.AddScoped<ITelegramGateway>(provider => provider.GetRequiredService<GrpcTelegramClient>());
}
return services;
}
/// <summary>
/// Регистрирует сервисы шифрования секретов
/// </summary>
/// <param name="contentRootPath">ContentRoot приложения — каталог по умолчанию для файла-ключа data/encryption.key.</param>
public static IServiceCollection AddDealSecurity(this IServiceCollection services, string contentRootPath)
{
EncryptionKeyProvider keyProvider = new(contentRootPath);
byte[] key = keyProvider.GetKey();
services.AddSingleton<ISecretCipher>(new AesGcmSecretCipher(key));
return services;
}
}