From e3a26925073a6ae8a05188e9b4be7381ad1483d5 Mon Sep 17 00:00:00 2001 From: Rustam Khalimov Date: Fri, 11 Sep 2026 13:25:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B8=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=83=20Api,=20Contr?= =?UTF-8?q?acts,=20SharedKernel=20=D0=B8=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8?= =?UTF-8?q?=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deal.Api/Http -> Services/Models/Extensions; Contracts/Integrations и SharedKernel/Tenants -> Abstractions/Models; extension-классы telegram/ml -> Extensions. namespace/using/FQN мигрированы, using дедуплицированы. --- .../Deal.Api/Endpoints/AiCheckEndpoint.cs | 5 +- .../Deal.Api/Endpoints/AiSuggestEndpoints.cs | 7 +- src/core/Deal.Api/Endpoints/AuthEndpoints.cs | 5 +- .../Endpoints/CardDetailsEndpoints.cs | 7 +- src/core/Deal.Api/Endpoints/CardsEndpoints.cs | 5 +- .../Deal.Api/Endpoints/ContainersEndpoints.cs | 5 +- .../Deal.Api/Endpoints/DiscoveryEndpoints.cs | 7 +- src/core/Deal.Api/Endpoints/EventsEndpoint.cs | 3 +- .../Endpoints/FilterTesterEndpoints.cs | 5 +- src/core/Deal.Api/Endpoints/JoinEndpoint.cs | 5 +- src/core/Deal.Api/Endpoints/MlEndpoints.cs | 7 +- .../Endpoints/OperatorAnalyticsEndpoints.cs | 5 +- .../Endpoints/OperatorAuditEndpoints.cs | 5 +- .../Endpoints/OperatorAuthEndpoints.cs | 5 +- .../Endpoints/OperatorHealthEndpoints.cs | 5 +- .../Endpoints/OperatorInvitesEndpoints.cs | 5 +- .../Endpoints/OperatorLimitsEndpoints.cs | 5 +- .../Endpoints/OperatorMaintenanceEndpoints.cs | 133 ++-- .../Endpoints/OperatorSettingsEndpoints.cs | 5 +- .../Endpoints/OperatorTenantsEndpoints.cs | 5 +- .../Deal.Api/Endpoints/PipelineEndpoints.cs | 5 +- src/core/Deal.Api/Endpoints/RatesEndpoints.cs | 5 +- .../Deal.Api/Endpoints/SettingsEndpoints.cs | 3 +- .../Deal.Api/Endpoints/StorageEndpoints.cs | 5 +- .../Deal.Api/Endpoints/TelegramEndpoints.cs | 7 +- .../Endpoints/TelegramQrImageEndpoint.cs | 7 +- .../{Http => Extensions}/AuthHelpers.cs | 3 +- .../Hosting/DiscoveryWorkerScheduler.cs | 3 +- .../Hosting/MlOutboxFlushScheduler.cs | 3 +- .../Deal.Api/Hosting/StorageTickScheduler.cs | 3 +- .../Middleware/OperatorSessionMiddleware.cs | 5 +- .../Deal.Api/Middleware/RateLimitPolicies.cs | 309 ++++---- .../Deal.Api/Middleware/SessionMiddleware.cs | 8 +- .../{Http => Models}/CurrentOperator.cs | 3 +- .../Deal.Api/{Http => Models}/CurrentUser.cs | 3 +- .../Observability/RuntimeDepthsCollector.cs | 3 +- src/core/Deal.Api/Program.cs | 9 +- .../{Http => Services}/AuditAppender.cs | 4 +- .../{Http => Services}/EndpointResults.cs | 4 +- .../{Http => Services}/LoginAttemptGuard.cs | 4 +- .../Services/PipelineWorkerScheduler.cs | 3 +- .../{Http => Services}/SessionCookieWriter.cs | 4 +- .../StoreBackedFixedWindowRateLimiter.cs | 4 +- .../Telegram/IngressRateLimitInterceptor.cs | 221 +++--- .../Telegram/TelegramIngressService.cs | 3 +- src/core/Deal.Api/Telegram/TgStatusService.cs | 2 +- .../{ => Abstractions}/IAiClassifier.cs | 2 +- .../{ => Abstractions}/IAiTools.cs | 2 +- .../{ => Abstractions}/IColumnSuggester.cs | 2 +- .../{ => Abstractions}/IFileStorage.cs | 2 +- .../{ => Abstractions}/IMlClient.cs | 2 +- .../{ => Abstractions}/ITelegramGateway.cs | 2 +- .../Integrations/Models/FileMeta.cs | 2 +- .../{ => Models}/MlLearningLabels.cs | 4 +- .../Models/MlStatusResponseDto.cs | 2 +- .../{ => Models}/SourceDefaults.cs | 4 +- .../Data/ConnectionStringProvider.cs | 3 +- .../Deal.Infrastructure/Data/TenantContext.cs | 3 +- .../Abstractions/IMlTrainClient.cs | 2 +- .../Services/BudgetedAiClassifier.cs | 5 +- .../Integrations/Services/BudgetedAiTools.cs | 5 +- .../Integrations/Services/GrpcAiClassifier.cs | 5 +- .../Integrations/Services/GrpcAiTools.cs | 5 +- .../Integrations/Services/GrpcMlClient.cs | 5 +- .../Services/GrpcTelegramClient.cs | 5 +- .../Services/LocalAiClassifier.cs | 2 +- .../Integrations/Services/LocalAiTools.cs | 2 +- .../Services/LocalColumnSuggester.cs | 2 +- .../Integrations/Services/LocalMlClient.cs | 2 +- .../Services/LocalTelegramGateway.cs | 2 +- .../Services/TokenUsageRecorder.cs | 3 +- .../Storage/Services/FileStorageRegistrar.cs | 3 +- .../Storage/Services/LocalFileStorage.cs | 2 +- .../Storage/Services/MinioFileStorage.cs | 2 +- .../ServiceCollectionExtensions.cs | 6 +- .../Tenancy/TenantProvisioningService.cs | 3 +- .../Tenancy/TenantSchemaMigrationService.cs | 3 +- .../Services/DiscoveryCandidatesService.cs | 3 +- .../Services/DiscoveryEvaluator.cs | 674 ++++++++-------- .../DiscoveryWorkerService.Evaluate.cs | 2 +- .../DiscoveryWorkerService.Helpers.cs | 2 +- .../Services/DiscoveryWorkerService.Join.cs | 2 +- .../Services/DiscoveryWorkerService.Search.cs | 2 +- .../Services/DiscoveryWorkerService.cs | 2 +- .../Services/CardsService.Files.cs | 3 +- .../Services/CardsService.Helpers.cs | 260 +++---- .../Services/CardsService.Operations.cs | 722 +++++++++--------- .../Application/Services/CardsService.cs | 2 +- .../Application/Services/AiCardLearning.cs | 2 +- .../Application/Services/CardComposer.cs | 2 +- .../Application/Services/CardReclassifier.cs | 2 +- .../Application/Services/MlReviewService.cs | 2 +- .../Services/PipelineProcessingService.cs | 3 +- .../Services/PipelineWorkerService.Checks.cs | 2 +- .../PipelineWorkerService.Decisions.cs | 2 +- .../PipelineWorkerService.Learning.cs | 2 +- .../Services/PipelineWorkerService.Pump.cs | 2 +- .../PipelineWorkerService.Rejections.cs | 2 +- .../PipelineWorkerService.Settings.cs | 2 +- .../Services/PipelineWorkerService.State.cs | 2 +- .../Services/PipelineWorkerService.cs | 2 +- .../Application/DialogsService.cs | 2 +- .../Application/TelegramModuleRegistrar.cs | 3 +- .../Abstractions/ITenantProvisioner.cs | 3 +- .../Application/Services/TenantService.cs | 3 +- .../{ => Abstractions}/ITenantContext.cs | 4 +- .../Tenants/{ => Models}/TenantId.cs | 4 +- .../BudgetedAiClassifierTests.cs | 5 +- .../Deal.Tests.Unit/BudgetedAiToolsTests.cs | 5 +- .../Deal.Tests.Unit/DialogsServiceTests.cs | 2 +- .../DiscoveryWorkerSchedulerTests.cs | 5 +- .../FailingTenantProvisioner.cs | 3 +- .../tests/Deal.Tests.Unit/FakeAiClassifier.cs | 2 +- src/core/tests/Deal.Tests.Unit/FakeAiTools.cs | 2 +- .../Deal.Tests.Unit/FakeDiscoveryGateway.cs | 2 +- .../tests/Deal.Tests.Unit/FakeFileStorage.cs | 2 +- .../tests/Deal.Tests.Unit/FakeMlClient.cs | 2 +- .../Deal.Tests.Unit/FakeTelegramGateway.cs | 2 +- .../Deal.Tests.Unit/FakeTenantProvisioner.cs | 3 +- .../Deal.Tests.Unit/GrpcAiClassifierTests.cs | 5 +- .../tests/Deal.Tests.Unit/GrpcAiToolsTests.cs | 5 +- .../Deal.Tests.Unit/GrpcMlClientTests.cs | 3 +- .../GrpcTelegramClientTests.cs | 5 +- .../Deal.Tests.Unit/IntegrationsDiTests.cs | 6 +- .../Deal.Tests.Unit/LocalAiClassifierTests.cs | 2 +- .../Deal.Tests.Unit/LocalAiToolsTests.cs | 3 +- .../LocalColumnSuggesterTests.cs | 2 +- .../LoginAttemptEndpointHttpTests.cs | 4 +- .../Deal.Tests.Unit/LoginAttemptGuardTests.cs | 4 +- .../MlOutboxFlushSchedulerTests.cs | 6 +- .../OperatorAuthEndpointsHttpTests.cs | 4 +- .../Deal.Tests.Unit/OperatorAuthHttpHost.cs | 7 +- .../PipelineWorkerGrpcAiTests.cs | 5 +- .../PipelineWorkerSchedulerTests.cs | 5 +- .../Deal.Tests.Unit/RateLimitHttpTests.cs | 4 +- .../RuntimeDepthsCollectorTests.cs | 6 +- .../StorageTickSchedulerTests.cs | 6 +- .../TelegramGatewayPortTests.cs | 3 +- .../TelegramIngressTestHost.cs | 6 +- .../tests/Deal.Tests.Unit/TenantIdTests.cs | 3 +- .../TokenUsageRecorderTests.cs | 3 +- src/ml-service/Deal.Ml.Tests/LearningData.cs | 1 + src/ml-service/Deal.Ml.Tests/MlTestHost.cs | 1 + .../Deal.Ml.Tests/MlTokenizerTests.cs | 1 + .../Deal.Ml.Tests/ModelPersistenceTests.cs | 1 + .../Deal.Ml.Tests/ModelPoolTests.cs | 1 + .../Deal.Ml.Tests/TenantModelLearningTests.cs | 1 + .../{Model => Extensions}/LabelExtensions.cs | 5 +- src/ml-service/Deal.Ml/MlServiceHost.cs | 1 + src/ml-service/Deal.Ml/MlServiceImpl.cs | 1 + src/ml-service/Deal.Ml/Model/EvalEntry.cs | 2 + src/ml-service/Deal.Ml/Model/LearnItem.cs | 2 + src/ml-service/Deal.Ml/Model/MlEvalInfo.cs | 2 + src/ml-service/Deal.Ml/Model/MlOptions.cs | 2 + .../Deal.Ml/Model/MlPredictResult.cs | 2 + .../Deal.Ml/Model/MlStatusResult.cs | 2 + .../Deal.Ml/Model/MlTypeDecision.cs | 2 + .../Deal.Ml/Model/ModelConstants.cs | 2 + src/ml-service/Deal.Ml/Model/ModelState.cs | 2 + .../Deal.Ml/Model/OnlineNaiveBayes.cs | 2 + src/ml-service/Deal.Ml/Storage/MlDb.cs | 1 + .../BackfillServiceTests.cs | 1 + .../Deal.Telegram.Tests/DialogRpcTests.cs | 1 + .../Deal.Telegram.Tests/DiscoveryOpsTests.cs | 1 + .../DiscoveryProtoMapperTests.cs | 1 + .../Deal.Telegram.Tests/DiscoveryRpcTests.cs | 1 + .../Deal.Telegram.Tests/FakeSessionClient.cs | 1 + .../RealtimeListenerTests.cs | 1 + .../Deal.Telegram.Tests/RealtimeSweepTests.cs | 1 + .../TelegramSessionRpcTests.cs | 1 + .../TlMessageMapperTests.cs | 1 + .../Deal.Telegram/Dialogs/BackfillService.cs | 1 + .../Dialogs/DialogProtoMapper.cs | 1 + .../Deal.Telegram/Dialogs/RealtimeListener.cs | 1 + .../Deal.Telegram/Dialogs/RealtimeSweep.cs | 1 + .../Deal.Telegram/Discovery/DiscoveryOps.cs | 1 + .../Discovery/DiscoveryProtoMapper.cs | 1 + .../ExceptionExtensions.cs | 4 +- .../Deal.Telegram/Sessions/SessionFarm.cs | 1 + .../Deal.Telegram/Sessions/TenantSession.cs | 1 + .../Deal.Telegram/Telegram/ClientFactory.cs | 2 + .../Deal.Telegram/Telegram/DialogKinds.cs | 2 + .../Telegram/DiscoveryMessage.cs | 2 + .../Telegram/DiscoveryReadResult.cs | 2 + .../Deal.Telegram/Telegram/ISessionClient.cs | 2 + .../Telegram/ITelegramClientFactory.cs | 2 + .../Deal.Telegram/Telegram/TelegramDialog.cs | 2 + .../Deal.Telegram/Telegram/TelegramMessage.cs | 2 + .../Telegram/TelegramSourceInfo.cs | 2 + .../Deal.Telegram/TelegramServiceHost.cs | 1 + .../Deal.Telegram/TelegramServiceImpl.cs | 1 + 191 files changed, 1539 insertions(+), 1350 deletions(-) rename src/core/Deal.Api/{Http => Extensions}/AuthHelpers.cs (98%) rename src/core/Deal.Api/{Http => Models}/CurrentOperator.cs (91%) rename src/core/Deal.Api/{Http => Models}/CurrentUser.cs (92%) rename src/core/Deal.Api/{Http => Services}/AuditAppender.cs (98%) rename src/core/Deal.Api/{Http => Services}/EndpointResults.cs (97%) rename src/core/Deal.Api/{Http => Services}/LoginAttemptGuard.cs (99%) rename src/core/Deal.Api/{Http => Services}/SessionCookieWriter.cs (93%) rename src/core/Deal.Api/{Http => Services}/StoreBackedFixedWindowRateLimiter.cs (97%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/IAiClassifier.cs (98%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/IAiTools.cs (98%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/IColumnSuggester.cs (98%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/IFileStorage.cs (99%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/IMlClient.cs (98%) rename src/core/Deal.Contracts/Integrations/{ => Abstractions}/ITelegramGateway.cs (99%) rename src/core/Deal.Contracts/Integrations/{ => Models}/MlLearningLabels.cs (94%) rename src/core/Deal.Contracts/Integrations/{ => Models}/SourceDefaults.cs (89%) rename src/core/Deal.SharedKernel/Tenants/{ => Abstractions}/ITenantContext.cs (92%) rename src/core/Deal.SharedKernel/Tenants/{ => Models}/TenantId.cs (96%) rename src/ml-service/Deal.Ml/{Model => Extensions}/LabelExtensions.cs (86%) rename src/telegram-service/Deal.Telegram/{Telegram => Extensions}/ExceptionExtensions.cs (87%) diff --git a/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs b/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs index 281c8b3..72646f2 100644 --- a/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs @@ -1,10 +1,11 @@ using System.Text.Json; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; using Deal.Modules.Settings.Application.Services; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/AiSuggestEndpoints.cs b/src/core/Deal.Api/Endpoints/AiSuggestEndpoints.cs index 80e4394..85e6b21 100644 --- a/src/core/Deal.Api/Endpoints/AiSuggestEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/AiSuggestEndpoints.cs @@ -1,8 +1,9 @@ using Deal.Api.Events; -using Deal.Api.Http; -using Deal.Contracts.Integrations; -using Deal.Contracts.Integrations.Models; +using Deal.Api.Extensions; +using Deal.Api.Models; using Deal.Api.Services; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/AuthEndpoints.cs b/src/core/Deal.Api/Endpoints/AuthEndpoints.cs index 0fef7d7..2dcfa90 100644 --- a/src/core/Deal.Api/Endpoints/AuthEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/AuthEndpoints.cs @@ -1,4 +1,6 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Middleware; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; @@ -6,7 +8,6 @@ using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.Options; -using Deal.Api.Services; using Deal.Api.Dtos; // Имя конфигурационного типа совпадает с Microsoft.AspNetCore.Http.CookieOptions — фиксируем алиасом. using CookieOptions = Deal.Api.Configuration.CookieOptions; diff --git a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs index b2815e0..2a6d8bf 100644 --- a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs @@ -1,7 +1,9 @@ using System.Text.Json; using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; -using Deal.Contracts.Integrations; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; @@ -13,7 +15,6 @@ 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs index eb2ca1d..f8f3635 100644 --- a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs @@ -1,6 +1,8 @@ using Deal.Api.Endpoints.RequestModels; using Deal.Api.Events; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; using Deal.Modules.Cards.Application.Models; @@ -18,7 +20,6 @@ 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs b/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs index 18ccdca..3fe2f57 100644 --- a/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs @@ -1,6 +1,8 @@ using System.Text.Json; using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; using Deal.Modules.Kanban.Application.Models; @@ -11,7 +13,6 @@ 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs b/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs index c84f691..2b0af03 100644 --- a/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs @@ -1,7 +1,9 @@ using System.Text.Json; using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; -using Deal.Contracts.Integrations; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Abstractions; using Deal.Modules.Discovery.Application.Exceptions; @@ -14,7 +16,6 @@ using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; using Deal.Modules.Settings.Application.Services; using Deal.Modules.Telegram.Application; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/EventsEndpoint.cs b/src/core/Deal.Api/Endpoints/EventsEndpoint.cs index ee18cd2..ab93445 100644 --- a/src/core/Deal.Api/Endpoints/EventsEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/EventsEndpoint.cs @@ -1,5 +1,6 @@ using Deal.Api.Events; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs b/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs index 0d9ecd4..33a4427 100644 --- a/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs @@ -1,9 +1,10 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; using Deal.Modules.Settings.Application.Services; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/JoinEndpoint.cs b/src/core/Deal.Api/Endpoints/JoinEndpoint.cs index d340780..c39877d 100644 --- a/src/core/Deal.Api/Endpoints/JoinEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/JoinEndpoint.cs @@ -1,10 +1,11 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/MlEndpoints.cs b/src/core/Deal.Api/Endpoints/MlEndpoints.cs index c063fa4..02d8d4d 100644 --- a/src/core/Deal.Api/Endpoints/MlEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/MlEndpoints.cs @@ -1,11 +1,12 @@ -using Deal.Api.Http; -using Deal.Contracts.Integrations; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Pipeline.Application.Abstractions; using Deal.Modules.Pipeline.Application.Models; using Deal.Modules.Pipeline.Application.Registrars; using Deal.Modules.Pipeline.Application.Services; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorAnalyticsEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorAnalyticsEndpoints.cs index a3f7a2a..ec01601 100644 --- a/src/core/Deal.Api/Endpoints/OperatorAnalyticsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorAnalyticsEndpoints.cs @@ -1,10 +1,11 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorAuditEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorAuditEndpoints.cs index 67ef3dd..4a909e6 100644 --- a/src/core/Deal.Api/Endpoints/OperatorAuditEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorAuditEndpoints.cs @@ -1,10 +1,11 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs index 5202f38..854ba92 100644 --- a/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs @@ -1,4 +1,6 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Middleware; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; @@ -6,7 +8,6 @@ using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.Options; -using Deal.Api.Services; using Deal.Api.Dtos; using AspNetCoreCookieOptions = Microsoft.AspNetCore.Http.CookieOptions; // Имя конфигурационного типа совпадает с Microsoft.AspNetCore.Http.CookieOptions — фиксируем алиасами. diff --git a/src/core/Deal.Api/Endpoints/OperatorHealthEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorHealthEndpoints.cs index 67a6b35..25ab469 100644 --- a/src/core/Deal.Api/Endpoints/OperatorHealthEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorHealthEndpoints.cs @@ -1,4 +1,6 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Observability; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; @@ -9,7 +11,6 @@ using Deal.Infrastructure.Integrations.Services; using Deal.Infrastructure.Persistence; using Deal.Infrastructure.Persistence.Configurations; using Microsoft.EntityFrameworkCore; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorInvitesEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorInvitesEndpoints.cs index 7a2c2a2..c627794 100644 --- a/src/core/Deal.Api/Endpoints/OperatorInvitesEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorInvitesEndpoints.cs @@ -1,10 +1,11 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorLimitsEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorLimitsEndpoints.cs index 6a82f7d..e627b29 100644 --- a/src/core/Deal.Api/Endpoints/OperatorLimitsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorLimitsEndpoints.cs @@ -1,10 +1,11 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorMaintenanceEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorMaintenanceEndpoints.cs index 4bd1715..b1dc9e1 100644 --- a/src/core/Deal.Api/Endpoints/OperatorMaintenanceEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorMaintenanceEndpoints.cs @@ -1,66 +1,67 @@ -using Deal.Api.Http; -using Deal.Infrastructure.Tenancy; -using Deal.Api.Services; -using Deal.Api.Dtos; - -namespace Deal.Api.Endpoints; - -/// -/// Операторские maintenance-ручки (этап 12, пакет C): пакетная миграция схем всех тенантов. -/// -/// -/// Ручка — только оператору (401 «Требуется вход оператора» без операторской сессии). -/// POST /api/operator/maintenance/tenants/migrate — идемпотентно проводит провижининг/миграции схем ВСЕХ -/// тенантов реестра (CREATE SCHEMA IF NOT EXISTS + EF Migrate, применяющий только неприменённые миграции) -/// с ограниченным параллелизмом и логированием прогресса (). -/// Ответ {ok, total, migrated, failed, failedSchemas, durationMs}; ok=false, если хотя бы одна схема -/// не мигрирована (сбой одной не прерывает остальные — оператор видит список проблемных схем). -/// -public static class OperatorMaintenanceEndpoints -{ - // Префикс группы операторских maintenance-ручек. - private const string MaintenanceGroupPrefix = "/api/operator/maintenance"; - - // Относительный путь пакетной миграции схем тенантов. - private const string MigrateTenantsPath = "/tenants/migrate"; - - // OpenAPI-тег группы. - private const string MaintenanceOpenApiTag = "operator-maintenance"; - - /// - /// Регистрирует группу /api/operator/maintenance: пакетная миграция схем тенантов. - /// - /// Построитель маршрутов приложения. - /// Построитель маршрутов для цепочки вызовов. - public static IEndpointRouteBuilder MapOperatorMaintenanceEndpoints(this IEndpointRouteBuilder app) - { - app.MapGroup(MaintenanceGroupPrefix) - .WithTags(MaintenanceOpenApiTag) - .MapPost(MigrateTenantsPath, MigrateTenantsAsync); - return app; - } - - // POST /api/operator/maintenance/tenants/migrate: миграция схем всех тенантов (сводка прогресса). - private static async Task MigrateTenantsAsync( - HttpContext context, - TenantSchemaMigrationService migrationService, - CancellationToken ct) - { - var operatorIdentity = context.GetCurrentOperator(); - if (operatorIdentity is null) - { - return EndpointResults.Unauthorized(AuthHelpers.OperatorUnauthorizedDetail); - } - - TenantMigrationSummary summary = await migrationService.MigrateAllAsync(ct); - return Results.Ok(new - { - ok = summary.Ok, - total = summary.Total, - migrated = summary.Migrated, - failed = summary.Failed, - failedSchemas = summary.FailedSchemas, - durationMs = summary.DurationMs, - }); - } -} +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Infrastructure.Tenancy; +using Deal.Api.Dtos; + +namespace Deal.Api.Endpoints; + +/// +/// Операторские maintenance-ручки (этап 12, пакет C): пакетная миграция схем всех тенантов. +/// +/// +/// Ручка — только оператору (401 «Требуется вход оператора» без операторской сессии). +/// POST /api/operator/maintenance/tenants/migrate — идемпотентно проводит провижининг/миграции схем ВСЕХ +/// тенантов реестра (CREATE SCHEMA IF NOT EXISTS + EF Migrate, применяющий только неприменённые миграции) +/// с ограниченным параллелизмом и логированием прогресса (). +/// Ответ {ok, total, migrated, failed, failedSchemas, durationMs}; ok=false, если хотя бы одна схема +/// не мигрирована (сбой одной не прерывает остальные — оператор видит список проблемных схем). +/// +public static class OperatorMaintenanceEndpoints +{ + // Префикс группы операторских maintenance-ручек. + private const string MaintenanceGroupPrefix = "/api/operator/maintenance"; + + // Относительный путь пакетной миграции схем тенантов. + private const string MigrateTenantsPath = "/tenants/migrate"; + + // OpenAPI-тег группы. + private const string MaintenanceOpenApiTag = "operator-maintenance"; + + /// + /// Регистрирует группу /api/operator/maintenance: пакетная миграция схем тенантов. + /// + /// Построитель маршрутов приложения. + /// Построитель маршрутов для цепочки вызовов. + public static IEndpointRouteBuilder MapOperatorMaintenanceEndpoints(this IEndpointRouteBuilder app) + { + app.MapGroup(MaintenanceGroupPrefix) + .WithTags(MaintenanceOpenApiTag) + .MapPost(MigrateTenantsPath, MigrateTenantsAsync); + return app; + } + + // POST /api/operator/maintenance/tenants/migrate: миграция схем всех тенантов (сводка прогресса). + private static async Task MigrateTenantsAsync( + HttpContext context, + TenantSchemaMigrationService migrationService, + CancellationToken ct) + { + var operatorIdentity = context.GetCurrentOperator(); + if (operatorIdentity is null) + { + return EndpointResults.Unauthorized(AuthHelpers.OperatorUnauthorizedDetail); + } + + TenantMigrationSummary summary = await migrationService.MigrateAllAsync(ct); + return Results.Ok(new + { + ok = summary.Ok, + total = summary.Total, + migrated = summary.Migrated, + failed = summary.Failed, + failedSchemas = summary.FailedSchemas, + durationMs = summary.DurationMs, + }); + } +} diff --git a/src/core/Deal.Api/Endpoints/OperatorSettingsEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorSettingsEndpoints.cs index ad3a881..7aa7886 100644 --- a/src/core/Deal.Api/Endpoints/OperatorSettingsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorSettingsEndpoints.cs @@ -1,12 +1,13 @@ using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Telegram; 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/OperatorTenantsEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorTenantsEndpoints.cs index 989fc16..7e70f48 100644 --- a/src/core/Deal.Api/Endpoints/OperatorTenantsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorTenantsEndpoints.cs @@ -1,11 +1,12 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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.Options; -using Deal.Api.Services; using Deal.Api.Dtos; using CookieOptions = Deal.Api.Configuration.CookieOptions; diff --git a/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs b/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs index f1eb701..9840e56 100644 --- a/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs @@ -1,10 +1,11 @@ using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Pipeline.Application.Abstractions; using Deal.Modules.Pipeline.Application.Models; using Deal.Modules.Pipeline.Application.Registrars; using Deal.Modules.Pipeline.Application.Services; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/RatesEndpoints.cs b/src/core/Deal.Api/Endpoints/RatesEndpoints.cs index 666eece..16c242c 100644 --- a/src/core/Deal.Api/Endpoints/RatesEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/RatesEndpoints.cs @@ -1,9 +1,10 @@ -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; using Deal.Modules.Settings.Application.Services; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/SettingsEndpoints.cs b/src/core/Deal.Api/Endpoints/SettingsEndpoints.cs index 56e6504..c2d35b1 100644 --- a/src/core/Deal.Api/Endpoints/SettingsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/SettingsEndpoints.cs @@ -2,7 +2,8 @@ using System.Text.Json; using Deal.Api; using Deal.Api.Dtos; using Deal.Api.Services; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; diff --git a/src/core/Deal.Api/Endpoints/StorageEndpoints.cs b/src/core/Deal.Api/Endpoints/StorageEndpoints.cs index 9b137fd..de79cb2 100644 --- a/src/core/Deal.Api/Endpoints/StorageEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/StorageEndpoints.cs @@ -1,6 +1,7 @@ -using Deal.Api.Http; -using Deal.Infrastructure.Services; +using Deal.Api.Extensions; +using Deal.Api.Models; using Deal.Api.Services; +using Deal.Infrastructure.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs b/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs index f2d905a..e27196f 100644 --- a/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs @@ -1,7 +1,9 @@ using Deal.Api.Endpoints.RequestModels; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Telegram; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Telegram.Application; using Deal.Modules.Telegram.Application.Models; @@ -10,7 +12,6 @@ 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 Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Endpoints/TelegramQrImageEndpoint.cs b/src/core/Deal.Api/Endpoints/TelegramQrImageEndpoint.cs index 8ea3e26..354b5d0 100644 --- a/src/core/Deal.Api/Endpoints/TelegramQrImageEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/TelegramQrImageEndpoint.cs @@ -1,8 +1,9 @@ -using Deal.Api.Http; -using Deal.Contracts.Integrations; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Net.Codecrete.QrCodeGenerator; -using Deal.Api.Services; using Deal.Api.Dtos; namespace Deal.Api.Endpoints; diff --git a/src/core/Deal.Api/Http/AuthHelpers.cs b/src/core/Deal.Api/Extensions/AuthHelpers.cs similarity index 98% rename from src/core/Deal.Api/Http/AuthHelpers.cs rename to src/core/Deal.Api/Extensions/AuthHelpers.cs index 0d3fa8f..f32d05b 100644 --- a/src/core/Deal.Api/Http/AuthHelpers.cs +++ b/src/core/Deal.Api/Extensions/AuthHelpers.cs @@ -1,7 +1,8 @@ using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Extensions; /// /// Хелперы доступа к текущему пользователю запроса (минимальные API). diff --git a/src/core/Deal.Api/Hosting/DiscoveryWorkerScheduler.cs b/src/core/Deal.Api/Hosting/DiscoveryWorkerScheduler.cs index 759addb..1da90c2 100644 --- a/src/core/Deal.Api/Hosting/DiscoveryWorkerScheduler.cs +++ b/src/core/Deal.Api/Hosting/DiscoveryWorkerScheduler.cs @@ -9,7 +9,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Hosting/MlOutboxFlushScheduler.cs b/src/core/Deal.Api/Hosting/MlOutboxFlushScheduler.cs index 8081f6f..95bc71f 100644 --- a/src/core/Deal.Api/Hosting/MlOutboxFlushScheduler.cs +++ b/src/core/Deal.Api/Hosting/MlOutboxFlushScheduler.cs @@ -14,7 +14,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Hosting/StorageTickScheduler.cs b/src/core/Deal.Api/Hosting/StorageTickScheduler.cs index e67816b..e732e7e 100644 --- a/src/core/Deal.Api/Hosting/StorageTickScheduler.cs +++ b/src/core/Deal.Api/Hosting/StorageTickScheduler.cs @@ -13,7 +13,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs b/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs index c5eefc9..4881601 100644 --- a/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs +++ b/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs @@ -1,12 +1,13 @@ using Deal.Api.Configuration; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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.Options; -using Deal.Api.Services; using Deal.Api.Dtos; // Имя конфигурационного типа совпадает с Microsoft.AspNetCore.Http.CookieOptions — фиксируем алиасом. using OperatorCookieOptions = Deal.Api.Configuration.OperatorCookieOptions; diff --git a/src/core/Deal.Api/Middleware/RateLimitPolicies.cs b/src/core/Deal.Api/Middleware/RateLimitPolicies.cs index b91e191..40d086f 100644 --- a/src/core/Deal.Api/Middleware/RateLimitPolicies.cs +++ b/src/core/Deal.Api/Middleware/RateLimitPolicies.cs @@ -1,154 +1,155 @@ -using System.Threading.RateLimiting; -using Deal.Api.Configuration; -using Deal.Api.Http; -using Microsoft.AspNetCore.RateLimiting; -using Microsoft.Extensions.DependencyInjection; -using Deal.Api.Services; -using Deal.Api.Dtos; - -namespace Deal.Api.Middleware; - -/// -/// Регистрация встроенного rate limiter ASP.NET Core (план Task 11, Ruling 5; этап 12, пакет B — хранилище -/// на Postgres) и его политики: "auth" — фиксированное окно на IP клиента (ручки входа /api/auth/login и -/// /api/operator/auth/login), "api" — на тенанта из либо IP анонима -/// (Session/OperatorSessionMiddleware отрабатывают раньше — порядок Session → Operator → RateLimiter). -/// API-партиция выставляется и глобальным лимитером: все /api-эндпоинты без собственной политики -/// ограничены 600/мин на тенанта/IP. Отказ любого лимитера — HTTP 429 с телом {"detail": "…"}. -/// -/// -/// Регистрируется только при (в dev/тестах middleware и политики -/// не создаются — Ruling 5). gRPC-ингресс (:5082) HTTP-лимитером освобождён (DisableRateLimiting на -/// MapGrpcService): лимит по tenant-id там считает IngressRateLimitInterceptor — иначе входящий поток -/// telegram-service резался бы общим окном на IP. -/// -/// Счётчики окон живут в Postgres ( через scoped -/// ) — лимиты общие для всех инстансов core. Пороги/окна -/// не менялись: фиксированные 1 минута, PermitLimit из . -/// -/// -public static class RateLimitPolicies -{ - /// - /// Имя политики входа: фиксированное окно по IP (RateLimit:AuthPerMinute). - /// - public const string AuthPolicy = "auth"; - - /// - /// Имя API-политики: фиксированное окно по CurrentUser.TenantId либо IP анонима (RateLimit:ApiPerMinute). - /// - public const string ApiPolicy = "api"; - - /// - /// Текст 429 rate limiter (Ruling 5): все отказы лимитов запросов — единый detail. - /// - public const string RejectedDetail = "Слишком много запросов. Повторите позже"; - - // Префикс ключа хранилища для HTTP-политик: изолирует счётчики от gRPC-ингресса и login-guard. - private const string HttpKeyPrefix = "http:"; - - // Пространство ключа глобального лимитера: отдельные счётчики от именованной "api"-политики - // (как и раньше — независимые FixedWindowRateLimiter'ы, лимиты не схлопываются в один бакет). - private const string GlobalScope = "global"; - - // Префикс партиции API-политики для анонима (IP не разрешён в сессию). - private const string IpKeyPrefix = "ip:"; - - // Префикс партиции API-политики для аутентифицированного пользователя (по тенанту). - private const string TenantKeyPrefix = "tenant:"; - - // Ключ партиции, когда IP клиента недоступен (служебные/тестовые вызовы). - private const string UnknownClientKey = "unknown"; - - // Длина окна политик — 1 минута (имена настроек — «PerMinute»). - private static readonly TimeSpan Window = TimeSpan.FromMinutes(1); - - /// - /// Регистрирует AddRateLimiter с политиками "auth"/"api" и глобальным лимитером API-партиции. - /// - /// Коллекция сервисов хоста. - /// Настройки rate limiting (секция RateLimit). - /// Коллекция сервисов для цепочки вызовов. - public static IServiceCollection AddDealRateLimiter(this IServiceCollection services, RateLimitOptions options) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(options); - - int apiPerMinute = options.ApiPerMinute; - services.AddRateLimiter(limiter => - { - limiter.RejectionStatusCode = StatusCodes.Status429TooManyRequests; - limiter.OnRejected = OnRejectedAsync; - limiter.AddPolicy(AuthPolicy, context => AuthPartition(context, options.AuthPerMinute)); - limiter.AddPolicy(ApiPolicy, context => ApiPartition(context, ApiPolicy, apiPerMinute)); - // Глобальный лимитер — та же API-партиция (своё пространство ключей): эндпоинты без - // RequireRateLimiting ограничены по тенанту/IP (весь /api, кроме ручек входа и gRPC). - limiter.GlobalLimiter = PartitionedRateLimiter.Create( - context => ApiPartition(context, GlobalScope, apiPerMinute)); - }); - - return services; - } - - // Партиция политики "auth": фиксированное окно в минуту на IP клиента. - // context: Контекст запроса (адрес клиента). - // permitsPerMinute: Разрешено запросов в минуту на партицию (RateLimit:AuthPerMinute). - // Возвращает: Партиция лимитера с ключом IP. - private static RateLimitPartition AuthPartition(HttpContext context, int permitsPerMinute) - => StorePartition(context, AuthPolicy, ClientKey(context), permitsPerMinute); - - // Партиция политики "api": ключ — тенант сессии (CurrentUser) либо IP анонима. - // context: Контекст запроса (CurrentUser кладёт SessionMiddleware до UseRateLimiter). - // scope: Пространство ключей (имя политики или "global" для глобального лимитера). - // permitsPerMinute: Разрешено запросов в минуту на партицию (RateLimit:ApiPerMinute). - // Возвращает: Партиция лимитера с ключом tenant:{id} либо IP. - private static RateLimitPartition ApiPartition( - HttpContext context, - string scope, - int permitsPerMinute) - { - string partitionKey = context.GetCurrentUser() is { } user - ? TenantKeyPrefix + user.TenantId.ToString("N") - : ClientKey(context); - return StorePartition(context, scope, partitionKey, permitsPerMinute); - } - - // Строит партицию на хранилище Postgres: ключ = http:{scope}:{partitionKey}, лимитер — лениво. - // context: Контекст запроса (источник IServiceScopeFactory для лениво создаваемого лимитера). - // scope: Пространство ключей (политика/global). - // partitionKey: Ключ партиции (IP/тенант). - // permitsPerMinute: Разрешено запросов в минуту. - // Возвращает: Партиция store-backed лимитера (ленивое создание и кеш — менеджер партиций). - private static RateLimitPartition StorePartition( - HttpContext context, - string scope, - string partitionKey, - int permitsPerMinute) - { - string storeKey = HttpKeyPrefix + scope + ":" + partitionKey; - return RateLimitPartition.Get(storeKey, key => new StoreBackedFixedWindowRateLimiter( - context.RequestServices.GetRequiredService(), - key, - permitsPerMinute, - Window, - () => DateTimeOffset.UtcNow)); - } - - // Ключ клиента: строка IP (или unknown, если адрес недоступен). - // context: Контекст запроса. - // Возвращает: Ключ партиции по IP. - private static string ClientKey(HttpContext context) - { - string? ip = context.Connection.RemoteIpAddress?.ToString(); - return string.IsNullOrEmpty(ip) ? UnknownClientKey : IpKeyPrefix + ip; - } - - // Пишет ответ 429 формата прототипа: {"detail": "…"} (Ruling 10/5). - // context: Контекст отклонённого запроса. - // cancellationToken: Токен отмены ответа. - private static async ValueTask OnRejectedAsync(OnRejectedContext context, CancellationToken cancellationToken) - { - context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; - await context.HttpContext.Response.WriteAsJsonAsync(new { detail = RejectedDetail }, cancellationToken); - } -} +using System.Threading.RateLimiting; +using Deal.Api.Configuration; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.Extensions.DependencyInjection; +using Deal.Api.Dtos; + +namespace Deal.Api.Middleware; + +/// +/// Регистрация встроенного rate limiter ASP.NET Core (план Task 11, Ruling 5; этап 12, пакет B — хранилище +/// на Postgres) и его политики: "auth" — фиксированное окно на IP клиента (ручки входа /api/auth/login и +/// /api/operator/auth/login), "api" — на тенанта из либо IP анонима +/// (Session/OperatorSessionMiddleware отрабатывают раньше — порядок Session → Operator → RateLimiter). +/// API-партиция выставляется и глобальным лимитером: все /api-эндпоинты без собственной политики +/// ограничены 600/мин на тенанта/IP. Отказ любого лимитера — HTTP 429 с телом {"detail": "…"}. +/// +/// +/// Регистрируется только при (в dev/тестах middleware и политики +/// не создаются — Ruling 5). gRPC-ингресс (:5082) HTTP-лимитером освобождён (DisableRateLimiting на +/// MapGrpcService): лимит по tenant-id там считает IngressRateLimitInterceptor — иначе входящий поток +/// telegram-service резался бы общим окном на IP. +/// +/// Счётчики окон живут в Postgres ( через scoped +/// ) — лимиты общие для всех инстансов core. Пороги/окна +/// не менялись: фиксированные 1 минута, PermitLimit из . +/// +/// +public static class RateLimitPolicies +{ + /// + /// Имя политики входа: фиксированное окно по IP (RateLimit:AuthPerMinute). + /// + public const string AuthPolicy = "auth"; + + /// + /// Имя API-политики: фиксированное окно по CurrentUser.TenantId либо IP анонима (RateLimit:ApiPerMinute). + /// + public const string ApiPolicy = "api"; + + /// + /// Текст 429 rate limiter (Ruling 5): все отказы лимитов запросов — единый detail. + /// + public const string RejectedDetail = "Слишком много запросов. Повторите позже"; + + // Префикс ключа хранилища для HTTP-политик: изолирует счётчики от gRPC-ингресса и login-guard. + private const string HttpKeyPrefix = "http:"; + + // Пространство ключа глобального лимитера: отдельные счётчики от именованной "api"-политики + // (как и раньше — независимые FixedWindowRateLimiter'ы, лимиты не схлопываются в один бакет). + private const string GlobalScope = "global"; + + // Префикс партиции API-политики для анонима (IP не разрешён в сессию). + private const string IpKeyPrefix = "ip:"; + + // Префикс партиции API-политики для аутентифицированного пользователя (по тенанту). + private const string TenantKeyPrefix = "tenant:"; + + // Ключ партиции, когда IP клиента недоступен (служебные/тестовые вызовы). + private const string UnknownClientKey = "unknown"; + + // Длина окна политик — 1 минута (имена настроек — «PerMinute»). + private static readonly TimeSpan Window = TimeSpan.FromMinutes(1); + + /// + /// Регистрирует AddRateLimiter с политиками "auth"/"api" и глобальным лимитером API-партиции. + /// + /// Коллекция сервисов хоста. + /// Настройки rate limiting (секция RateLimit). + /// Коллекция сервисов для цепочки вызовов. + public static IServiceCollection AddDealRateLimiter(this IServiceCollection services, RateLimitOptions options) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(options); + + int apiPerMinute = options.ApiPerMinute; + services.AddRateLimiter(limiter => + { + limiter.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + limiter.OnRejected = OnRejectedAsync; + limiter.AddPolicy(AuthPolicy, context => AuthPartition(context, options.AuthPerMinute)); + limiter.AddPolicy(ApiPolicy, context => ApiPartition(context, ApiPolicy, apiPerMinute)); + // Глобальный лимитер — та же API-партиция (своё пространство ключей): эндпоинты без + // RequireRateLimiting ограничены по тенанту/IP (весь /api, кроме ручек входа и gRPC). + limiter.GlobalLimiter = PartitionedRateLimiter.Create( + context => ApiPartition(context, GlobalScope, apiPerMinute)); + }); + + return services; + } + + // Партиция политики "auth": фиксированное окно в минуту на IP клиента. + // context: Контекст запроса (адрес клиента). + // permitsPerMinute: Разрешено запросов в минуту на партицию (RateLimit:AuthPerMinute). + // Возвращает: Партиция лимитера с ключом IP. + private static RateLimitPartition AuthPartition(HttpContext context, int permitsPerMinute) + => StorePartition(context, AuthPolicy, ClientKey(context), permitsPerMinute); + + // Партиция политики "api": ключ — тенант сессии (CurrentUser) либо IP анонима. + // context: Контекст запроса (CurrentUser кладёт SessionMiddleware до UseRateLimiter). + // scope: Пространство ключей (имя политики или "global" для глобального лимитера). + // permitsPerMinute: Разрешено запросов в минуту на партицию (RateLimit:ApiPerMinute). + // Возвращает: Партиция лимитера с ключом tenant:{id} либо IP. + private static RateLimitPartition ApiPartition( + HttpContext context, + string scope, + int permitsPerMinute) + { + string partitionKey = context.GetCurrentUser() is { } user + ? TenantKeyPrefix + user.TenantId.ToString("N") + : ClientKey(context); + return StorePartition(context, scope, partitionKey, permitsPerMinute); + } + + // Строит партицию на хранилище Postgres: ключ = http:{scope}:{partitionKey}, лимитер — лениво. + // context: Контекст запроса (источник IServiceScopeFactory для лениво создаваемого лимитера). + // scope: Пространство ключей (политика/global). + // partitionKey: Ключ партиции (IP/тенант). + // permitsPerMinute: Разрешено запросов в минуту. + // Возвращает: Партиция store-backed лимитера (ленивое создание и кеш — менеджер партиций). + private static RateLimitPartition StorePartition( + HttpContext context, + string scope, + string partitionKey, + int permitsPerMinute) + { + string storeKey = HttpKeyPrefix + scope + ":" + partitionKey; + return RateLimitPartition.Get(storeKey, key => new StoreBackedFixedWindowRateLimiter( + context.RequestServices.GetRequiredService(), + key, + permitsPerMinute, + Window, + () => DateTimeOffset.UtcNow)); + } + + // Ключ клиента: строка IP (или unknown, если адрес недоступен). + // context: Контекст запроса. + // Возвращает: Ключ партиции по IP. + private static string ClientKey(HttpContext context) + { + string? ip = context.Connection.RemoteIpAddress?.ToString(); + return string.IsNullOrEmpty(ip) ? UnknownClientKey : IpKeyPrefix + ip; + } + + // Пишет ответ 429 формата прототипа: {"detail": "…"} (Ruling 10/5). + // context: Контекст отклонённого запроса. + // cancellationToken: Токен отмены ответа. + private static async ValueTask OnRejectedAsync(OnRejectedContext context, CancellationToken cancellationToken) + { + context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; + await context.HttpContext.Response.WriteAsJsonAsync(new { detail = RejectedDetail }, cancellationToken); + } +} diff --git a/src/core/Deal.Api/Middleware/SessionMiddleware.cs b/src/core/Deal.Api/Middleware/SessionMiddleware.cs index 6e17e9e..ca5b847 100644 --- a/src/core/Deal.Api/Middleware/SessionMiddleware.cs +++ b/src/core/Deal.Api/Middleware/SessionMiddleware.cs @@ -1,13 +1,15 @@ using Deal.Api.Configuration; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Options; -using Deal.Api.Services; using Deal.Api.Dtos; // Имя конфигурационного типа совпадает с Microsoft.AspNetCore.Http.CookieOptions — фиксируем алиасом. using CookieOptions = Deal.Api.Configuration.CookieOptions; diff --git a/src/core/Deal.Api/Http/CurrentOperator.cs b/src/core/Deal.Api/Models/CurrentOperator.cs similarity index 91% rename from src/core/Deal.Api/Http/CurrentOperator.cs rename to src/core/Deal.Api/Models/CurrentOperator.cs index 12dd4af..91f6cfa 100644 --- a/src/core/Deal.Api/Http/CurrentOperator.cs +++ b/src/core/Deal.Api/Models/CurrentOperator.cs @@ -1,7 +1,8 @@ using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; -namespace Deal.Api.Http; +namespace Deal.Api.Models; /// /// Текущий оператор запроса — кладёт OperatorSessionMiddleware в HttpContext.Items (Ruling 1). diff --git a/src/core/Deal.Api/Http/CurrentUser.cs b/src/core/Deal.Api/Models/CurrentUser.cs similarity index 92% rename from src/core/Deal.Api/Http/CurrentUser.cs rename to src/core/Deal.Api/Models/CurrentUser.cs index cdff07d..86ce773 100644 --- a/src/core/Deal.Api/Http/CurrentUser.cs +++ b/src/core/Deal.Api/Models/CurrentUser.cs @@ -1,7 +1,8 @@ using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; -namespace Deal.Api.Http; +namespace Deal.Api.Models; /// /// Текущий пользователь запроса — кладётся SessionMiddleware в HttpContext.Items. diff --git a/src/core/Deal.Api/Observability/RuntimeDepthsCollector.cs b/src/core/Deal.Api/Observability/RuntimeDepthsCollector.cs index e76e474..1d5ae9d 100644 --- a/src/core/Deal.Api/Observability/RuntimeDepthsCollector.cs +++ b/src/core/Deal.Api/Observability/RuntimeDepthsCollector.cs @@ -14,7 +14,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.EntityFrameworkCore; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Program.cs b/src/core/Deal.Api/Program.cs index 603285a..30c25de 100644 --- a/src/core/Deal.Api/Program.cs +++ b/src/core/Deal.Api/Program.cs @@ -8,12 +8,14 @@ using Deal.Api.Configuration; using Deal.Api.Endpoints; using Deal.Api.Events; using Deal.Api.Hosting; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; using Deal.Api.Logging; using Deal.Api.Middleware; using Deal.Api.Observability; using Deal.Api.Telegram; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure; using Deal.Infrastructure.Data; using Deal.Infrastructure.Integrations.Abstractions; @@ -53,7 +55,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; diff --git a/src/core/Deal.Api/Http/AuditAppender.cs b/src/core/Deal.Api/Services/AuditAppender.cs similarity index 98% rename from src/core/Deal.Api/Http/AuditAppender.cs rename to src/core/Deal.Api/Services/AuditAppender.cs index 185f8ed..b6a6e2d 100644 --- a/src/core/Deal.Api/Http/AuditAppender.cs +++ b/src/core/Deal.Api/Services/AuditAppender.cs @@ -6,8 +6,10 @@ using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.DependencyInjection; using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Services; /// /// Хелпер записи аудита действий пользователей тенанта и операторов (единая точка — ). diff --git a/src/core/Deal.Api/Http/EndpointResults.cs b/src/core/Deal.Api/Services/EndpointResults.cs similarity index 97% rename from src/core/Deal.Api/Http/EndpointResults.cs rename to src/core/Deal.Api/Services/EndpointResults.cs index 246bd3f..2d1678d 100644 --- a/src/core/Deal.Api/Http/EndpointResults.cs +++ b/src/core/Deal.Api/Services/EndpointResults.cs @@ -1,7 +1,9 @@ using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Services; /// /// Общие ответы ошибок минимальных API: HTTP-код + {"detail":"…"} (формат прототипа, Ruling 10). diff --git a/src/core/Deal.Api/Http/LoginAttemptGuard.cs b/src/core/Deal.Api/Services/LoginAttemptGuard.cs similarity index 99% rename from src/core/Deal.Api/Http/LoginAttemptGuard.cs rename to src/core/Deal.Api/Services/LoginAttemptGuard.cs index 4f6c75b..d30ba42 100644 --- a/src/core/Deal.Api/Http/LoginAttemptGuard.cs +++ b/src/core/Deal.Api/Services/LoginAttemptGuard.cs @@ -6,8 +6,10 @@ using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Services; /// /// Прикладной guard неудачных попыток входа (план Task 11, Ruling 5; этап 12, пакет B — хранилище на diff --git a/src/core/Deal.Api/Services/PipelineWorkerScheduler.cs b/src/core/Deal.Api/Services/PipelineWorkerScheduler.cs index deaf2a3..267b87b 100644 --- a/src/core/Deal.Api/Services/PipelineWorkerScheduler.cs +++ b/src/core/Deal.Api/Services/PipelineWorkerScheduler.cs @@ -9,7 +9,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Api.Dtos; using Deal.Api; using Deal.Api.Services; diff --git a/src/core/Deal.Api/Http/SessionCookieWriter.cs b/src/core/Deal.Api/Services/SessionCookieWriter.cs similarity index 93% rename from src/core/Deal.Api/Http/SessionCookieWriter.cs rename to src/core/Deal.Api/Services/SessionCookieWriter.cs index cbef70d..0a2ebc2 100644 --- a/src/core/Deal.Api/Http/SessionCookieWriter.cs +++ b/src/core/Deal.Api/Services/SessionCookieWriter.cs @@ -3,8 +3,10 @@ using Deal.Api.Dtos; using AspNetCoreCookieOptions = Microsoft.AspNetCore.Http.CookieOptions; using CookieOptions = Deal.Api.Configuration.CookieOptions; +using Deal.Api.Extensions; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Services; /// /// Единая запись httpOnly-куки сессии тенант-пользователя (login/change-password/impersonation). diff --git a/src/core/Deal.Api/Http/StoreBackedFixedWindowRateLimiter.cs b/src/core/Deal.Api/Services/StoreBackedFixedWindowRateLimiter.cs similarity index 97% rename from src/core/Deal.Api/Http/StoreBackedFixedWindowRateLimiter.cs rename to src/core/Deal.Api/Services/StoreBackedFixedWindowRateLimiter.cs index 4f3b3e1..f03932b 100644 --- a/src/core/Deal.Api/Http/StoreBackedFixedWindowRateLimiter.cs +++ b/src/core/Deal.Api/Services/StoreBackedFixedWindowRateLimiter.cs @@ -7,8 +7,10 @@ using Deal.Modules.Tenants.Application.Services; using Microsoft.Extensions.DependencyInjection; using Deal.Api.Services; using Deal.Api.Dtos; +using Deal.Api.Extensions; +using Deal.Api.Models; -namespace Deal.Api.Http; +namespace Deal.Api.Services; /// /// RateLimiter фиксированного окна с состоянием в Postgres (этап 12, пакет B): лимиты переживают diff --git a/src/core/Deal.Api/Telegram/IngressRateLimitInterceptor.cs b/src/core/Deal.Api/Telegram/IngressRateLimitInterceptor.cs index c690d98..263f4a7 100644 --- a/src/core/Deal.Api/Telegram/IngressRateLimitInterceptor.cs +++ b/src/core/Deal.Api/Telegram/IngressRateLimitInterceptor.cs @@ -1,110 +1,111 @@ -using System.Threading.RateLimiting; -using Deal.Api.Http; -using Deal.Api.Middleware; -using Grpc.Core; -using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection; -using Deal.Api.Services; -using Deal.Api.Dtos; - -namespace Deal.Api.Telegram; - -/// -/// Серверный интерцептор rate limit gRPC-ингресса (план Task 11, Ruling 5; этап 12, пакет B — хранилище -/// на Postgres): фиксированное окно (GrpcIngressPerMinute в минуту) по gRPC-metadata «tenant-id» — -/// каждый тенант имеет собственный счётчик входящего потока telegram-service, общий для всех инстансов -/// core. Стандартный grpc.health.v1.Health не ограничивается (инфраструктурный liveness, как у -/// IngressServiceTokenInterceptor). Превышение лимита — RPC-отказ RESOURCE_EXHAUSTED (gRPC-аналог -/// HTTP 429) до вызова метода сервиса. -/// -/// -/// Окно считает общий (регистрируется в DI singleton'ом -/// рядом с AddGrpc): экземпляры интерцептора создаются фреймворком, но партиции окон живут в одном -/// лимитере — иначе каждая регистрация/вызов получил бы собственное окно и лимит не работал бы. -/// Счётчики партиций — в Postgres через . Лимитер -/// регистрируется и интерцептор добавляется только при RateLimit:Enabled=true (dev — plaintext-поток -/// без лимита, как HTTP-политики, Ruling 5). -/// -public sealed class IngressRateLimitInterceptor : Interceptor -{ - // Ключ партиции вызовов без metadata tenant-id (общий «мусорный» бакет — сервис всё равно отклонит). - private const string MissingTenantIdKey = "missing-tenant-id"; - - // Префикс ключа хранилища: изолирует счётчики ингресса от HTTP-политик и login-guard. - private const string StoreKeyPrefix = "grpc:ingress:"; - - // Окно политики ингресса — 1 минута (имя настройки — GrpcIngressPerMinute). - private static readonly TimeSpan Window = TimeSpan.FromMinutes(1); - - private readonly PartitionedRateLimiter _limiter; - - /// - /// Создаёт интерцептор с общим лимитером ингресса (см. CreateLimiter). - /// - /// Singleton-лимитер, зарегистрированный хостом. - public IngressRateLimitInterceptor(PartitionedRateLimiter limiter) - { - _limiter = limiter ?? throw new ArgumentNullException(nameof(limiter)); - } - - /// - /// Создаёт лимитер ингресса: фиксированное окно в минуту, партиция на каждый tenant-id, - /// счётчики — в общем хранилище Postgres. - /// - /// Фабрика scope: store-backed лимитер резолвит хранилище на каждое приобретение. - /// Разрешено вызовов на тенанта в минуту (RateLimit:GrpcIngressPerMinute). - /// Общий PartitionedRateLimiter по строковому tenant-id. - public static PartitionedRateLimiter CreateLimiter(IServiceScopeFactory scopeFactory, int permitsPerMinute) - { - ArgumentNullException.ThrowIfNull(scopeFactory); - ArgumentOutOfRangeException.ThrowIfLessThan(permitsPerMinute, 1); - return PartitionedRateLimiter.Create( - tenantId => - { - string key = StoreKeyPrefix + tenantId; - return RateLimitPartition.Get(key, storeKey => new StoreBackedFixedWindowRateLimiter( - scopeFactory, - storeKey, - permitsPerMinute, - Window, - () => DateTimeOffset.UtcNow)); - }); - } - - /// - /// Проверка лимита для unary-RPC: health-методы пропускаются, остальные получают разрешение - /// партиции tenant-id; исчерпание окна — RESOURCE_EXHAUSTED. - /// - /// Тип запроса gRPC. - /// Тип ответа gRPC. - /// Тело запроса. - /// Контекст вызова (metadata tenant-id/service-token). - /// Следующий обработчик в цепочке. - /// Ответ нижестоящего обработчика. - public override async Task UnaryServerHandler( - TRequest request, - ServerCallContext context, - UnaryServerMethod continuation) - { - if (context.Method.StartsWith(IngressServiceTokenInterceptor.HealthMethodPrefix, StringComparison.Ordinal)) - { - return await continuation(request, context).ConfigureAwait(false); - } - - string? tenantId = context.RequestHeaders.GetValue(TelegramIngressService.TenantIdMetadataKey); - // permitCount указывается явно: AcquireAsync(TResource, int permitCount, CancellationToken). - using RateLimitLease lease = await _limiter - .AcquireAsync(tenantId ?? MissingTenantIdKey, permitCount: 1, context.CancellationToken) - .ConfigureAwait(false); - if (!lease.IsAcquired) - { - throw Rejection(); - } - - return await continuation(request, context).ConfigureAwait(false); - } - - // Создаёт отказ RESOURCE_EXHAUSTED с текстом 429-детали политик (единый detail rate limit). - private static RpcException Rejection() - => new(new Status(StatusCode.ResourceExhausted, RateLimitPolicies.RejectedDetail)); -} +using System.Threading.RateLimiting; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; +using Deal.Api.Middleware; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Microsoft.Extensions.DependencyInjection; +using Deal.Api.Dtos; + +namespace Deal.Api.Telegram; + +/// +/// Серверный интерцептор rate limit gRPC-ингресса (план Task 11, Ruling 5; этап 12, пакет B — хранилище +/// на Postgres): фиксированное окно (GrpcIngressPerMinute в минуту) по gRPC-metadata «tenant-id» — +/// каждый тенант имеет собственный счётчик входящего потока telegram-service, общий для всех инстансов +/// core. Стандартный grpc.health.v1.Health не ограничивается (инфраструктурный liveness, как у +/// IngressServiceTokenInterceptor). Превышение лимита — RPC-отказ RESOURCE_EXHAUSTED (gRPC-аналог +/// HTTP 429) до вызова метода сервиса. +/// +/// +/// Окно считает общий (регистрируется в DI singleton'ом +/// рядом с AddGrpc): экземпляры интерцептора создаются фреймворком, но партиции окон живут в одном +/// лимитере — иначе каждая регистрация/вызов получил бы собственное окно и лимит не работал бы. +/// Счётчики партиций — в Postgres через . Лимитер +/// регистрируется и интерцептор добавляется только при RateLimit:Enabled=true (dev — plaintext-поток +/// без лимита, как HTTP-политики, Ruling 5). +/// +public sealed class IngressRateLimitInterceptor : Interceptor +{ + // Ключ партиции вызовов без metadata tenant-id (общий «мусорный» бакет — сервис всё равно отклонит). + private const string MissingTenantIdKey = "missing-tenant-id"; + + // Префикс ключа хранилища: изолирует счётчики ингресса от HTTP-политик и login-guard. + private const string StoreKeyPrefix = "grpc:ingress:"; + + // Окно политики ингресса — 1 минута (имя настройки — GrpcIngressPerMinute). + private static readonly TimeSpan Window = TimeSpan.FromMinutes(1); + + private readonly PartitionedRateLimiter _limiter; + + /// + /// Создаёт интерцептор с общим лимитером ингресса (см. CreateLimiter). + /// + /// Singleton-лимитер, зарегистрированный хостом. + public IngressRateLimitInterceptor(PartitionedRateLimiter limiter) + { + _limiter = limiter ?? throw new ArgumentNullException(nameof(limiter)); + } + + /// + /// Создаёт лимитер ингресса: фиксированное окно в минуту, партиция на каждый tenant-id, + /// счётчики — в общем хранилище Postgres. + /// + /// Фабрика scope: store-backed лимитер резолвит хранилище на каждое приобретение. + /// Разрешено вызовов на тенанта в минуту (RateLimit:GrpcIngressPerMinute). + /// Общий PartitionedRateLimiter по строковому tenant-id. + public static PartitionedRateLimiter CreateLimiter(IServiceScopeFactory scopeFactory, int permitsPerMinute) + { + ArgumentNullException.ThrowIfNull(scopeFactory); + ArgumentOutOfRangeException.ThrowIfLessThan(permitsPerMinute, 1); + return PartitionedRateLimiter.Create( + tenantId => + { + string key = StoreKeyPrefix + tenantId; + return RateLimitPartition.Get(key, storeKey => new StoreBackedFixedWindowRateLimiter( + scopeFactory, + storeKey, + permitsPerMinute, + Window, + () => DateTimeOffset.UtcNow)); + }); + } + + /// + /// Проверка лимита для unary-RPC: health-методы пропускаются, остальные получают разрешение + /// партиции tenant-id; исчерпание окна — RESOURCE_EXHAUSTED. + /// + /// Тип запроса gRPC. + /// Тип ответа gRPC. + /// Тело запроса. + /// Контекст вызова (metadata tenant-id/service-token). + /// Следующий обработчик в цепочке. + /// Ответ нижестоящего обработчика. + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + if (context.Method.StartsWith(IngressServiceTokenInterceptor.HealthMethodPrefix, StringComparison.Ordinal)) + { + return await continuation(request, context).ConfigureAwait(false); + } + + string? tenantId = context.RequestHeaders.GetValue(TelegramIngressService.TenantIdMetadataKey); + // permitCount указывается явно: AcquireAsync(TResource, int permitCount, CancellationToken). + using RateLimitLease lease = await _limiter + .AcquireAsync(tenantId ?? MissingTenantIdKey, permitCount: 1, context.CancellationToken) + .ConfigureAwait(false); + if (!lease.IsAcquired) + { + throw Rejection(); + } + + return await continuation(request, context).ConfigureAwait(false); + } + + // Создаёт отказ RESOURCE_EXHAUSTED с текстом 429-детали политик (единый detail rate limit). + private static RpcException Rejection() + => new(new Status(StatusCode.ResourceExhausted, RateLimitPolicies.RejectedDetail)); +} diff --git a/src/core/Deal.Api/Telegram/TelegramIngressService.cs b/src/core/Deal.Api/Telegram/TelegramIngressService.cs index e327372..2705d1c 100644 --- a/src/core/Deal.Api/Telegram/TelegramIngressService.cs +++ b/src/core/Deal.Api/Telegram/TelegramIngressService.cs @@ -16,7 +16,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Deal.Api.Services; using Deal.Api.Dtos; diff --git a/src/core/Deal.Api/Telegram/TgStatusService.cs b/src/core/Deal.Api/Telegram/TgStatusService.cs index 08d96c6..1380afb 100644 --- a/src/core/Deal.Api/Telegram/TgStatusService.cs +++ b/src/core/Deal.Api/Telegram/TgStatusService.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; diff --git a/src/core/Deal.Contracts/Integrations/IAiClassifier.cs b/src/core/Deal.Contracts/Integrations/Abstractions/IAiClassifier.cs similarity index 98% rename from src/core/Deal.Contracts/Integrations/IAiClassifier.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/IAiClassifier.cs index 2293e63..8c61e00 100644 --- a/src/core/Deal.Contracts/Integrations/IAiClassifier.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/IAiClassifier.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт ИИ-классификатора входящих сообщений (Ruling 5, план Task 6 L370–388). diff --git a/src/core/Deal.Contracts/Integrations/IAiTools.cs b/src/core/Deal.Contracts/Integrations/Abstractions/IAiTools.cs similarity index 98% rename from src/core/Deal.Contracts/Integrations/IAiTools.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/IAiTools.cs index 72ae040..1c685ce 100644 --- a/src/core/Deal.Contracts/Integrations/IAiTools.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/IAiTools.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт ИИ-инструментов Discovery и генерации ключевых слов (Ruling 9, план Task 15/18/19). diff --git a/src/core/Deal.Contracts/Integrations/IColumnSuggester.cs b/src/core/Deal.Contracts/Integrations/Abstractions/IColumnSuggester.cs similarity index 98% rename from src/core/Deal.Contracts/Integrations/IColumnSuggester.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/IColumnSuggester.cs index df8b846..c57b2c1 100644 --- a/src/core/Deal.Contracts/Integrations/IColumnSuggester.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/IColumnSuggester.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт ИИ-предложений колонок и ключей — план Task 14 (L462–491), Ruling 3. diff --git a/src/core/Deal.Contracts/Integrations/IFileStorage.cs b/src/core/Deal.Contracts/Integrations/Abstractions/IFileStorage.cs similarity index 99% rename from src/core/Deal.Contracts/Integrations/IFileStorage.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/IFileStorage.cs index f4fb07c..06c3fc7 100644 --- a/src/core/Deal.Contracts/Integrations/IFileStorage.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/IFileStorage.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт файлового хранилища вложений (Ruling 4, план Task 6; 1:1 backend/app/services/object_store.py L61–107). diff --git a/src/core/Deal.Contracts/Integrations/IMlClient.cs b/src/core/Deal.Contracts/Integrations/Abstractions/IMlClient.cs similarity index 98% rename from src/core/Deal.Contracts/Integrations/IMlClient.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/IMlClient.cs index a10e4ba..f837901 100644 --- a/src/core/Deal.Contracts/Integrations/IMlClient.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/IMlClient.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт клиента автономного ML-сервиса (Ruling 4, план Task 9 L333–367). diff --git a/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs b/src/core/Deal.Contracts/Integrations/Abstractions/ITelegramGateway.cs similarity index 99% rename from src/core/Deal.Contracts/Integrations/ITelegramGateway.cs rename to src/core/Deal.Contracts/Integrations/Abstractions/ITelegramGateway.cs index 795326f..dc979b1 100644 --- a/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs +++ b/src/core/Deal.Contracts/Integrations/Abstractions/ITelegramGateway.cs @@ -1,6 +1,6 @@ using Deal.Contracts.Integrations.Models; -namespace Deal.Contracts.Integrations; +namespace Deal.Contracts.Integrations.Abstractions; /// /// Порт-гейт к автономному telegram-service: команды ядра наружу (Ruling 7, план Task 13/14/18). diff --git a/src/core/Deal.Contracts/Integrations/Models/FileMeta.cs b/src/core/Deal.Contracts/Integrations/Models/FileMeta.cs index 9f39919..663e084 100644 --- a/src/core/Deal.Contracts/Integrations/Models/FileMeta.cs +++ b/src/core/Deal.Contracts/Integrations/Models/FileMeta.cs @@ -4,7 +4,7 @@ namespace Deal.Contracts.Integrations.Models; /// Дескриптор объекта файлового хранилища: ключ, размер и MIME-тип (Ruling 4, Task 6). /// /// -/// Тип-описатель порта : в нём хранилище отдаёт +/// Тип-описатель порта : в нём хранилище отдаёт /// метаданные объекта (аналог stat object_store.py — MinIO StatObject / размер файла на диске) методом /// StatAsync. Потребитель — download-эндпоинт файлов проектных карточек (Task 9, Ruling T6): /// Content-Length/Content-Type ответа берутся из дескриптора, а null (объекта нет) мапится в 404 diff --git a/src/core/Deal.Contracts/Integrations/MlLearningLabels.cs b/src/core/Deal.Contracts/Integrations/Models/MlLearningLabels.cs similarity index 94% rename from src/core/Deal.Contracts/Integrations/MlLearningLabels.cs rename to src/core/Deal.Contracts/Integrations/Models/MlLearningLabels.cs index 895f85a..348d8e1 100644 --- a/src/core/Deal.Contracts/Integrations/MlLearningLabels.cs +++ b/src/core/Deal.Contracts/Integrations/Models/MlLearningLabels.cs @@ -1,4 +1,6 @@ -namespace Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; + +namespace Deal.Contracts.Integrations.Models; /// /// Общие метки обучения ML-модели (значения 1:1 с ml_client.py). Единый источник для всех модулей, diff --git a/src/core/Deal.Contracts/Integrations/Models/MlStatusResponseDto.cs b/src/core/Deal.Contracts/Integrations/Models/MlStatusResponseDto.cs index 879fca0..e2e73eb 100644 --- a/src/core/Deal.Contracts/Integrations/Models/MlStatusResponseDto.cs +++ b/src/core/Deal.Contracts/Integrations/Models/MlStatusResponseDto.cs @@ -4,7 +4,7 @@ namespace Deal.Contracts.Integrations.Models; /// Тело ответа GET /api/ml/status (ml_routes.py L66–75 + ml_client.snapshot() L138–150). /// /// -/// Собирается реализацией из статуса +/// Собирается реализацией из статуса /// ML-сервиса () и локальных настроек/счётчиков тенанта (KV, Ruling 1): /// enabled — выключатель mlEnabled (семантика «не false», ml_routes.py L71), reachable — /// доступность сервиса, stats — локальная статистика. Фронт читает: reachable, diff --git a/src/core/Deal.Contracts/Integrations/SourceDefaults.cs b/src/core/Deal.Contracts/Integrations/Models/SourceDefaults.cs similarity index 89% rename from src/core/Deal.Contracts/Integrations/SourceDefaults.cs rename to src/core/Deal.Contracts/Integrations/Models/SourceDefaults.cs index 7e4fe83..b01da82 100644 --- a/src/core/Deal.Contracts/Integrations/SourceDefaults.cs +++ b/src/core/Deal.Contracts/Integrations/Models/SourceDefaults.cs @@ -1,4 +1,6 @@ -namespace Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; + +namespace Deal.Contracts.Integrations.Models; /// /// Общие дефолты источника (канал/группа/кандидат), 1:1 с db.py (hue по умолчанию — «#666»). diff --git a/src/core/Deal.Infrastructure/Data/ConnectionStringProvider.cs b/src/core/Deal.Infrastructure/Data/ConnectionStringProvider.cs index b8ae072..e263477 100644 --- a/src/core/Deal.Infrastructure/Data/ConnectionStringProvider.cs +++ b/src/core/Deal.Infrastructure/Data/ConnectionStringProvider.cs @@ -1,4 +1,5 @@ -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Configuration; namespace Deal.Infrastructure.Data; diff --git a/src/core/Deal.Infrastructure/Data/TenantContext.cs b/src/core/Deal.Infrastructure/Data/TenantContext.cs index 2abd0ec..3250384 100644 --- a/src/core/Deal.Infrastructure/Data/TenantContext.cs +++ b/src/core/Deal.Infrastructure/Data/TenantContext.cs @@ -1,4 +1,5 @@ -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; namespace Deal.Infrastructure.Data; diff --git a/src/core/Deal.Infrastructure/Integrations/Abstractions/IMlTrainClient.cs b/src/core/Deal.Infrastructure/Integrations/Abstractions/IMlTrainClient.cs index 3092080..9482755 100644 --- a/src/core/Deal.Infrastructure/Integrations/Abstractions/IMlTrainClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Abstractions/IMlTrainClient.cs @@ -11,7 +11,7 @@ namespace Deal.Infrastructure.Integrations.Abstractions; /// Порт отправки батча обучения в ml-service (RPC TrainBatch, ml.proto L52–55) — для MlOutboxFlushScheduler. /// /// -/// Отдельный от порт: сигнатура IMlClient не меняется +/// Отдельный от порт: сигнатура IMlClient не меняется /// (Self-Review плана L530), а выгрузку очереди делает фоновый флашер (Ruling 6), которому нужен только /// TrainBatch. Реализуется gRPC-адаптером и регистрируется только при /// Services:Ml:UseLocal=false (Local-режиму ml-service не нужен — очередь копится, как в этапе 3). diff --git a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiClassifier.cs b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiClassifier.cs index 5dc10fc..e5dc35e 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiClassifier.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiClassifier.cs @@ -1,11 +1,12 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs index 03df614..c7c1057 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs @@ -1,11 +1,12 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiClassifier.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiClassifier.cs index 9e9953f..e8ef5da 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiClassifier.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiClassifier.cs @@ -1,11 +1,12 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ai; using Deal.Modules.Pipeline.Application.Abstractions; using Deal.Modules.Pipeline.Application.Models; using Deal.Modules.Pipeline.Application.Registrars; using Deal.Modules.Pipeline.Application.Services; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs index a662353..f0aa4b4 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs @@ -1,7 +1,8 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ai; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs index 8801dbf..a30e78f 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ml; using Deal.Modules.Kanban.Application.Abstractions; @@ -16,7 +16,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs index 31eeb4d..10b5bee 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs @@ -1,7 +1,8 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Telegram; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Microsoft.Extensions.Logging; using Deal.Infrastructure.Integrations.Abstractions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiClassifier.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiClassifier.cs index 980bb74..f8b4bd7 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiClassifier.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiClassifier.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Pipeline.Application.Abstractions; using Deal.Modules.Pipeline.Application.Models; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs index fa8d7f1..2dde240 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalColumnSuggester.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalColumnSuggester.cs index 5040b0b..6f4d045 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalColumnSuggester.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalColumnSuggester.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs index 980b1a9..ff04a28 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs index 003da3a..0230bce 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs b/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs index 3420c0b..d28113d 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs @@ -11,7 +11,8 @@ using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; using Deal.Modules.Tenants.Application.Services; using Deal.SharedKernel.Observability; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; using Deal.Infrastructure.Integrations.Extensions; diff --git a/src/core/Deal.Infrastructure/Integrations/Storage/Services/FileStorageRegistrar.cs b/src/core/Deal.Infrastructure/Integrations/Storage/Services/FileStorageRegistrar.cs index 131d29d..75845e3 100644 --- a/src/core/Deal.Infrastructure/Integrations/Storage/Services/FileStorageRegistrar.cs +++ b/src/core/Deal.Infrastructure/Integrations/Storage/Services/FileStorageRegistrar.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs b/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs index 45fb7e5..2a86a5f 100644 --- a/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs +++ b/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Storage.Extensions; using Deal.Infrastructure.Integrations.Storage.Options; diff --git a/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs b/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs index 5865665..45e425b 100644 --- a/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs +++ b/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Microsoft.Extensions.Logging; using Minio; diff --git a/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs b/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs index fc50b79..a29c720 100644 --- a/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs +++ b/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; using Deal.Infrastructure.Integrations.Extensions; @@ -39,7 +40,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs b/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs index c6cfc51..e83f2dc 100644 --- a/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs +++ b/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs @@ -8,7 +8,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.EntityFrameworkCore; using Npgsql; diff --git a/src/core/Deal.Infrastructure/Tenancy/TenantSchemaMigrationService.cs b/src/core/Deal.Infrastructure/Tenancy/TenantSchemaMigrationService.cs index 3887445..cf55d42 100644 --- a/src/core/Deal.Infrastructure/Tenancy/TenantSchemaMigrationService.cs +++ b/src/core/Deal.Infrastructure/Tenancy/TenantSchemaMigrationService.cs @@ -5,7 +5,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging; namespace Deal.Infrastructure.Tenancy; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs index 98b4beb..fb6293a 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; using Deal.Modules.Discovery.Application.Exceptions; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs index 8d50c2f..5ea4f45 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs @@ -1,337 +1,337 @@ -using System.Text; -using Deal.Contracts.Integrations; -using Deal.Contracts.Integrations.Models; -using Deal.Modules.Discovery.Application.Models; -using Deal.Modules.Settings.Application.Abstractions; -using Deal.Modules.Settings.Application.Models; -using Deal.Modules.Settings.Application.Registrars; -using Deal.Modules.Settings.Application.Services; -using Deal.Modules.Discovery.Application.Abstractions; -using Deal.Modules.Discovery.Application.Exceptions; -using Deal.Modules.Discovery.Application.Extensions; -using Deal.Modules.Discovery.Application.Registrars; - -namespace Deal.Modules.Discovery.Application.Services; - -/// -/// Оценка содержания кандидата: фит сообщений под задачу поиска (1:1 discovery_eval.py целиком, план Task 18). -/// -/// -/// Каскад оценки одного сообщения (python evaluate_message L174–194): -/// -/// текст пустой/короче 10 символов → «слишком короткое» (False, эвристика); -/// ML при mlEnabled (): take + label=spam → «ML: спам» (False); -/// сбой прогноза не роняет оценку (python L101–107: «не уверен»); -/// ИИ при aiEnabled (): любая ошибка (Local — NotSupportedException, -/// gRPC — AiUnavailableException/…, Ruling 10) ловится и оценка продолжается эвристикой (python L186–194); -/// эвристика: любой ключ задачи входит в очищенный текст без учёта регистра. -/// -/// — группировка выборки форума по topic_id (null → «main») с заголовками-сниппетами -/// (python L86–117); — вердикт «источник подходит»: выборка ≥3 сообщений и доля fit ≥ -/// threshold, % (python L229–237). Результаты по сообщениям собирает -/// в агрегат (fit_count/total/fit_ratio, -/// python L197–226). Язык выборки — отдельный детектор (зовёт воркер). -/// -public sealed class DiscoveryEvaluator -{ - // Минимальная длина сообщения для содержательной оценки (python _MIN_TEXT_LEN L39). - private const int MinTextLength = 10; - - // Лимит текста, уходящего ИИ-провайдеру, в символах (python _AI_TEXT_LIMIT L41). - private const int AiTextLimit = 4000; - - // Потолок причины из ИИ (python _AI_REASON_LIMIT L43). - private const int AiReasonLimit = 200; - - // Минимальный объём содержательной выборки для вердикта оценки (python _MIN_CONTENT L77). - private const int MinContentMessages = 3; - - // topic_id=null в выборке/группировке → общая тема «main» (python _MAIN_TOPIC L47). - private const string MainTopic = "main"; - - // Длина заголовка темы-сниппета (python _TITLE_LIMIT L45). - private const int TopicTitleLimit = 60; - - // Причина по умолчанию при фите ИИ (python _ai_reason L170). - private const string AiFitReasonDefault = "подходит"; - - // Причина по умолчанию при не-фите ИИ (python _ai_reason L170). - private const string AiNotFitReasonDefault = "не подходит"; - - private readonly ISettingsStore _settings; - private readonly IMlClient _mlClient; - private readonly IAiTools _aiTools; - - /// - /// Создаёт оценщик содержания кандидатов Discovery. - /// - /// KV-настройки тенанта (mlEnabled/aiEnabled — ветки каскада). - /// ML-порт (спам-отсев при mlEnabled; сбой — «не уверен»). - /// ИИ-порт (фит при aiEnabled; сбой — эвристика, Ruling 10). - public DiscoveryEvaluator( - ISettingsStore settings, - IMlClient mlClient, - IAiTools aiTools) - { - ArgumentNullException.ThrowIfNull(settings); - ArgumentNullException.ThrowIfNull(mlClient); - ArgumentNullException.ThrowIfNull(aiTools); - _settings = settings; - _mlClient = mlClient; - _aiTools = aiTools; - } - - /// - /// Последовательная оценка всех сообщений выборки (python evaluate_sample L197–226). - /// - /// fit_count/total/fit_ratio считаются по всей выборке; per-message повторяет входной порядок - /// (воркеру нужны только агрегаты; per-message — для тестов и будущих разборов). - /// Задача поиска (description/keywords — промпт и эвристика). - /// Тексты сообщений выборки (порядок — входной). - /// Токен отмены. - /// Агрегат выборки: fit_count, total, fit_ratio, per-message. - public async Task EvaluateSampleAsync( - DiscoveryTaskDto task, - IReadOnlyList texts, - CancellationToken ct) - { - // Флаги веток (mlEnabled/aiEnabled) не меняются в пределах выборки: один типизированный снимок - // настроек на выборку (C30) вместо двух GetAsync на каждое сообщение. - TenantSettingsSnapshot settingsSnapshot = await TenantSettingsSnapshot.LoadAsync(_settings, ct).ConfigureAwait(false); - var perMessage = new List(texts?.Count ?? 0); - int fitCount = 0; - foreach (string text in texts ?? []) - { - DiscoveryMessageFit fit = - await EvaluateMessageCoreAsync(task, text, settingsSnapshot, ct).ConfigureAwait(false); - perMessage.Add(fit); - if (fit.Fit) - { - fitCount++; - } - } - - int total = perMessage.Count; - return new DiscoveryEvalSample( - fitCount, - total, - total > 0 ? (double)fitCount / total : 0.0, - perMessage); - } - - /// - /// Оценка фита одного сообщения под задачу: каскад короткое → ML-спам → ИИ → эвристика (python L174–194). - /// - /// Задача поиска (description/keywords). - /// Текст сообщения. - /// Токен отмены. - /// Вердикт: fit + причина + источник (heuristic|ml|ai). - public async Task EvaluateMessageAsync( - DiscoveryTaskDto task, - string text, - CancellationToken ct) - { - TenantSettingsSnapshot settingsSnapshot = await TenantSettingsSnapshot.LoadAsync(_settings, ct).ConfigureAwait(false); - return await EvaluateMessageCoreAsync(task, text, settingsSnapshot, ct).ConfigureAwait(false); - } - - // Чистое ядро оценки одного сообщения по фиксированному снимку настроек (см. EvaluateMessageAsync). - // task: Задача поиска (description/keywords). - // text: Текст сообщения. - // settingsSnapshot: Снимок настроек тенанта (mlEnabled/aiEnabled). - // ct: Токен отмены. - // Возвращает: Вердикт: fit + причина + источник (heuristic|ml|ai). - private async Task EvaluateMessageCoreAsync( - DiscoveryTaskDto task, - string text, - TenantSettingsSnapshot settingsSnapshot, - CancellationToken ct) - { - string raw = text ?? string.Empty; - if (raw.Trim().Length < MinTextLength) - { - return new DiscoveryMessageFit(false, "слишком короткое", "heuristic"); - } - - if (settingsSnapshot.GetBool(SettingsKeys.MlEnabled, SettingsDefaults.MlEnabled)) - { - try - { - MlPredictResultDto prediction = await _mlClient.PredictAsync(raw, ct).ConfigureAwait(false); - if (prediction.Take && string.Equals(prediction.Label, MlLearningLabels.Spam, StringComparison.OrdinalIgnoreCase)) - { - return new DiscoveryMessageFit(false, "ML: спам", "ml"); - } - } - catch (Exception exception) when (exception is not OperationCanceledException) - { - // Прогноз недоступен («не уверен», python ml_client.predict L101–107) — решает ИИ/эвристика. - } - } - - if (settingsSnapshot.GetBool(SettingsKeys.AiEnabled, SettingsDefaults.AiEnabled)) - { - try - { - AiEvaluateFitResultDto result = await _aiTools.EvaluateFitAsync( - SliceCodePoints(raw, AiTextLimit), - task.Description ?? string.Empty, - task.Keywords, - ct).ConfigureAwait(false); - string reason = (result.Reason ?? string.Empty).Trim(); - if (reason.Length == 0) - { - reason = result.Fit ? AiFitReasonDefault : AiNotFitReasonDefault; - } - - return new DiscoveryMessageFit(result.Fit, SliceCodePoints(reason, AiReasonLimit), "ai"); - } - catch (Exception exception) when (exception is not OperationCanceledException) - { - // Сбой ИИ (нет ключа/сеть/не-JSON; Local — NotSupportedException) не роняет оценку (python L191–192). - } - } - - return Heuristic(task, raw); - } - - // Эвристика: ключ задачи входит в очищенный текст без учёта регистра (python _heuristic L144–150). - private static DiscoveryMessageFit Heuristic(DiscoveryTaskDto task, string text) - { - string haystack = CleanShort(text); - foreach (string keyword in task.Keywords) - { - string kw = (keyword ?? string.Empty).Trim(); - if (kw.Length > 0 && haystack.Contains(kw.ToLowerInvariant(), StringComparison.Ordinal)) - { - return new DiscoveryMessageFit(true, $"совпал ключ \"{kw}\"", "heuristic"); - } - } - - return new DiscoveryMessageFit(false, "нет совпадений с ключами", "heuristic"); - } - - /// - /// Вердикт «источник подходит»: выборка ≥3 сообщений и доля fit ≥ threshold, % (python passed L229–237). - /// - /// Агрегат оценки выборки/темы. - /// Порог задачи (1..100; дефолт discEvalThreshold). - /// True — источник/тема проходят порог (каналы часто разбавляют контент офтопом — доля, не сплошное). - public static bool Passed(DiscoveryEvalSample sample, int thresholdPercent) - { - return sample.Total >= MinContentMessages && sample.FitRatio * 100 >= Math.Max(1, thresholdPercent); - } - - /// - /// Группирует сообщения выборки по topic_id (null → «main») и сортирует группы по числу сообщений - /// (убыв.), порядок сообщений внутри группы — входной (python group_by_topic L96–117). - /// - /// Заголовок группы — сниппет первого непустого текста (python _topic_title L86–93, ≤60 символов, - /// whitespace схлопнут). Возвращаемые группы переиспользуют исходные DTO (без копий). - /// Сообщения выборки (для форумов заполнен TopicId). - /// Группы от большей к меньшей; пусто — выборки нет. - public static IReadOnlyList GroupByTopic(IReadOnlyList messages) - { - var groups = new Dictionary>(StringComparer.Ordinal); - var order = new List(); - foreach (TelegramEvalMessageDto message in messages ?? []) - { - string key = message.TopicId is long topicId ? topicId.ToString() : MainTopic; - if (!groups.TryGetValue(key, out List? bucket)) - { - bucket = []; - groups[key] = bucket; - order.Add(key); - } - - bucket.Add(message); - } - - var outGroups = new List(order.Count); - foreach (string key in order) - { - List bucket = groups[key]; - outGroups.Add(new DiscoveryTopicGroup(key, TopicTitle(bucket), bucket)); - } - - // python L116: sort(key=len(messages), reverse=True) — стабильная сортировка сохраняет входной порядок равных. - return outGroups - .OrderByDescending(group => group.Messages.Count) - .ToList(); - } - - // Сниппет первого непустого текста темы (python _topic_title L86–93). - private static string TopicTitle(IReadOnlyList messages) - { - foreach (TelegramEvalMessageDto message in messages) - { - string text = (message.Text ?? string.Empty).Trim(); - if (text.Length > 0) - { - return SliceCodePoints(CollapseWhitespace(text), TopicTitleLimit); - } - } - - return string.Empty; - } - - // Очистка текста для эвристики: схлопывание whitespace (python clean_short ≈ «\n+»→« » + trim). - private static string CleanShort(string text) - { - string collapsed = CollapseWhitespace(text); - return collapsed.ToLowerInvariant(); - } - - // Схлопывает любые пробельные последовательности в один пробел и обрезает края (python " ".join(split())). - private static string CollapseWhitespace(string text) - { - var builder = new StringBuilder(text.Length); - bool pendingSpace = false; - foreach (char ch in text) - { - if (char.IsWhiteSpace(ch)) - { - pendingSpace = builder.Length > 0; - continue; - } - - if (pendingSpace) - { - builder.Append(' '); - pendingSpace = false; - } - - builder.Append(ch); - } - - return builder.ToString(); - } - - // Первые max кодовых точек строки (python-срез без разрыва суррогатных пар). - private static string SliceCodePoints(string text, int max) - { - if (text.Length <= max) - { - return text; - } - - var builder = new StringBuilder(max); - int taken = 0; - for (int index = 0; index < text.Length && taken < max; index++) - { - bool pair = char.IsHighSurrogate(text[index]) - && index + 1 < text.Length - && char.IsLowSurrogate(text[index + 1]); - builder.Append(text[index]); - if (pair) - { - index++; - builder.Append(text[index]); - } - - taken++; - } - - return builder.ToString(); - } -} +using System.Text; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; +using Deal.Modules.Discovery.Application.Models; +using Deal.Modules.Settings.Application.Abstractions; +using Deal.Modules.Settings.Application.Models; +using Deal.Modules.Settings.Application.Registrars; +using Deal.Modules.Settings.Application.Services; +using Deal.Modules.Discovery.Application.Abstractions; +using Deal.Modules.Discovery.Application.Exceptions; +using Deal.Modules.Discovery.Application.Extensions; +using Deal.Modules.Discovery.Application.Registrars; + +namespace Deal.Modules.Discovery.Application.Services; + +/// +/// Оценка содержания кандидата: фит сообщений под задачу поиска (1:1 discovery_eval.py целиком, план Task 18). +/// +/// +/// Каскад оценки одного сообщения (python evaluate_message L174–194): +/// +/// текст пустой/короче 10 символов → «слишком короткое» (False, эвристика); +/// ML при mlEnabled (): take + label=spam → «ML: спам» (False); +/// сбой прогноза не роняет оценку (python L101–107: «не уверен»); +/// ИИ при aiEnabled (): любая ошибка (Local — NotSupportedException, +/// gRPC — AiUnavailableException/…, Ruling 10) ловится и оценка продолжается эвристикой (python L186–194); +/// эвристика: любой ключ задачи входит в очищенный текст без учёта регистра. +/// +/// — группировка выборки форума по topic_id (null → «main») с заголовками-сниппетами +/// (python L86–117); — вердикт «источник подходит»: выборка ≥3 сообщений и доля fit ≥ +/// threshold, % (python L229–237). Результаты по сообщениям собирает +/// в агрегат (fit_count/total/fit_ratio, +/// python L197–226). Язык выборки — отдельный детектор (зовёт воркер). +/// +public sealed class DiscoveryEvaluator +{ + // Минимальная длина сообщения для содержательной оценки (python _MIN_TEXT_LEN L39). + private const int MinTextLength = 10; + + // Лимит текста, уходящего ИИ-провайдеру, в символах (python _AI_TEXT_LIMIT L41). + private const int AiTextLimit = 4000; + + // Потолок причины из ИИ (python _AI_REASON_LIMIT L43). + private const int AiReasonLimit = 200; + + // Минимальный объём содержательной выборки для вердикта оценки (python _MIN_CONTENT L77). + private const int MinContentMessages = 3; + + // topic_id=null в выборке/группировке → общая тема «main» (python _MAIN_TOPIC L47). + private const string MainTopic = "main"; + + // Длина заголовка темы-сниппета (python _TITLE_LIMIT L45). + private const int TopicTitleLimit = 60; + + // Причина по умолчанию при фите ИИ (python _ai_reason L170). + private const string AiFitReasonDefault = "подходит"; + + // Причина по умолчанию при не-фите ИИ (python _ai_reason L170). + private const string AiNotFitReasonDefault = "не подходит"; + + private readonly ISettingsStore _settings; + private readonly IMlClient _mlClient; + private readonly IAiTools _aiTools; + + /// + /// Создаёт оценщик содержания кандидатов Discovery. + /// + /// KV-настройки тенанта (mlEnabled/aiEnabled — ветки каскада). + /// ML-порт (спам-отсев при mlEnabled; сбой — «не уверен»). + /// ИИ-порт (фит при aiEnabled; сбой — эвристика, Ruling 10). + public DiscoveryEvaluator( + ISettingsStore settings, + IMlClient mlClient, + IAiTools aiTools) + { + ArgumentNullException.ThrowIfNull(settings); + ArgumentNullException.ThrowIfNull(mlClient); + ArgumentNullException.ThrowIfNull(aiTools); + _settings = settings; + _mlClient = mlClient; + _aiTools = aiTools; + } + + /// + /// Последовательная оценка всех сообщений выборки (python evaluate_sample L197–226). + /// + /// fit_count/total/fit_ratio считаются по всей выборке; per-message повторяет входной порядок + /// (воркеру нужны только агрегаты; per-message — для тестов и будущих разборов). + /// Задача поиска (description/keywords — промпт и эвристика). + /// Тексты сообщений выборки (порядок — входной). + /// Токен отмены. + /// Агрегат выборки: fit_count, total, fit_ratio, per-message. + public async Task EvaluateSampleAsync( + DiscoveryTaskDto task, + IReadOnlyList texts, + CancellationToken ct) + { + // Флаги веток (mlEnabled/aiEnabled) не меняются в пределах выборки: один типизированный снимок + // настроек на выборку (C30) вместо двух GetAsync на каждое сообщение. + TenantSettingsSnapshot settingsSnapshot = await TenantSettingsSnapshot.LoadAsync(_settings, ct).ConfigureAwait(false); + var perMessage = new List(texts?.Count ?? 0); + int fitCount = 0; + foreach (string text in texts ?? []) + { + DiscoveryMessageFit fit = + await EvaluateMessageCoreAsync(task, text, settingsSnapshot, ct).ConfigureAwait(false); + perMessage.Add(fit); + if (fit.Fit) + { + fitCount++; + } + } + + int total = perMessage.Count; + return new DiscoveryEvalSample( + fitCount, + total, + total > 0 ? (double)fitCount / total : 0.0, + perMessage); + } + + /// + /// Оценка фита одного сообщения под задачу: каскад короткое → ML-спам → ИИ → эвристика (python L174–194). + /// + /// Задача поиска (description/keywords). + /// Текст сообщения. + /// Токен отмены. + /// Вердикт: fit + причина + источник (heuristic|ml|ai). + public async Task EvaluateMessageAsync( + DiscoveryTaskDto task, + string text, + CancellationToken ct) + { + TenantSettingsSnapshot settingsSnapshot = await TenantSettingsSnapshot.LoadAsync(_settings, ct).ConfigureAwait(false); + return await EvaluateMessageCoreAsync(task, text, settingsSnapshot, ct).ConfigureAwait(false); + } + + // Чистое ядро оценки одного сообщения по фиксированному снимку настроек (см. EvaluateMessageAsync). + // task: Задача поиска (description/keywords). + // text: Текст сообщения. + // settingsSnapshot: Снимок настроек тенанта (mlEnabled/aiEnabled). + // ct: Токен отмены. + // Возвращает: Вердикт: fit + причина + источник (heuristic|ml|ai). + private async Task EvaluateMessageCoreAsync( + DiscoveryTaskDto task, + string text, + TenantSettingsSnapshot settingsSnapshot, + CancellationToken ct) + { + string raw = text ?? string.Empty; + if (raw.Trim().Length < MinTextLength) + { + return new DiscoveryMessageFit(false, "слишком короткое", "heuristic"); + } + + if (settingsSnapshot.GetBool(SettingsKeys.MlEnabled, SettingsDefaults.MlEnabled)) + { + try + { + MlPredictResultDto prediction = await _mlClient.PredictAsync(raw, ct).ConfigureAwait(false); + if (prediction.Take && string.Equals(prediction.Label, MlLearningLabels.Spam, StringComparison.OrdinalIgnoreCase)) + { + return new DiscoveryMessageFit(false, "ML: спам", "ml"); + } + } + catch (Exception exception) when (exception is not OperationCanceledException) + { + // Прогноз недоступен («не уверен», python ml_client.predict L101–107) — решает ИИ/эвристика. + } + } + + if (settingsSnapshot.GetBool(SettingsKeys.AiEnabled, SettingsDefaults.AiEnabled)) + { + try + { + AiEvaluateFitResultDto result = await _aiTools.EvaluateFitAsync( + SliceCodePoints(raw, AiTextLimit), + task.Description ?? string.Empty, + task.Keywords, + ct).ConfigureAwait(false); + string reason = (result.Reason ?? string.Empty).Trim(); + if (reason.Length == 0) + { + reason = result.Fit ? AiFitReasonDefault : AiNotFitReasonDefault; + } + + return new DiscoveryMessageFit(result.Fit, SliceCodePoints(reason, AiReasonLimit), "ai"); + } + catch (Exception exception) when (exception is not OperationCanceledException) + { + // Сбой ИИ (нет ключа/сеть/не-JSON; Local — NotSupportedException) не роняет оценку (python L191–192). + } + } + + return Heuristic(task, raw); + } + + // Эвристика: ключ задачи входит в очищенный текст без учёта регистра (python _heuristic L144–150). + private static DiscoveryMessageFit Heuristic(DiscoveryTaskDto task, string text) + { + string haystack = CleanShort(text); + foreach (string keyword in task.Keywords) + { + string kw = (keyword ?? string.Empty).Trim(); + if (kw.Length > 0 && haystack.Contains(kw.ToLowerInvariant(), StringComparison.Ordinal)) + { + return new DiscoveryMessageFit(true, $"совпал ключ \"{kw}\"", "heuristic"); + } + } + + return new DiscoveryMessageFit(false, "нет совпадений с ключами", "heuristic"); + } + + /// + /// Вердикт «источник подходит»: выборка ≥3 сообщений и доля fit ≥ threshold, % (python passed L229–237). + /// + /// Агрегат оценки выборки/темы. + /// Порог задачи (1..100; дефолт discEvalThreshold). + /// True — источник/тема проходят порог (каналы часто разбавляют контент офтопом — доля, не сплошное). + public static bool Passed(DiscoveryEvalSample sample, int thresholdPercent) + { + return sample.Total >= MinContentMessages && sample.FitRatio * 100 >= Math.Max(1, thresholdPercent); + } + + /// + /// Группирует сообщения выборки по topic_id (null → «main») и сортирует группы по числу сообщений + /// (убыв.), порядок сообщений внутри группы — входной (python group_by_topic L96–117). + /// + /// Заголовок группы — сниппет первого непустого текста (python _topic_title L86–93, ≤60 символов, + /// whitespace схлопнут). Возвращаемые группы переиспользуют исходные DTO (без копий). + /// Сообщения выборки (для форумов заполнен TopicId). + /// Группы от большей к меньшей; пусто — выборки нет. + public static IReadOnlyList GroupByTopic(IReadOnlyList messages) + { + var groups = new Dictionary>(StringComparer.Ordinal); + var order = new List(); + foreach (TelegramEvalMessageDto message in messages ?? []) + { + string key = message.TopicId is long topicId ? topicId.ToString() : MainTopic; + if (!groups.TryGetValue(key, out List? bucket)) + { + bucket = []; + groups[key] = bucket; + order.Add(key); + } + + bucket.Add(message); + } + + var outGroups = new List(order.Count); + foreach (string key in order) + { + List bucket = groups[key]; + outGroups.Add(new DiscoveryTopicGroup(key, TopicTitle(bucket), bucket)); + } + + // python L116: sort(key=len(messages), reverse=True) — стабильная сортировка сохраняет входной порядок равных. + return outGroups + .OrderByDescending(group => group.Messages.Count) + .ToList(); + } + + // Сниппет первого непустого текста темы (python _topic_title L86–93). + private static string TopicTitle(IReadOnlyList messages) + { + foreach (TelegramEvalMessageDto message in messages) + { + string text = (message.Text ?? string.Empty).Trim(); + if (text.Length > 0) + { + return SliceCodePoints(CollapseWhitespace(text), TopicTitleLimit); + } + } + + return string.Empty; + } + + // Очистка текста для эвристики: схлопывание whitespace (python clean_short ≈ «\n+»→« » + trim). + private static string CleanShort(string text) + { + string collapsed = CollapseWhitespace(text); + return collapsed.ToLowerInvariant(); + } + + // Схлопывает любые пробельные последовательности в один пробел и обрезает края (python " ".join(split())). + private static string CollapseWhitespace(string text) + { + var builder = new StringBuilder(text.Length); + bool pendingSpace = false; + foreach (char ch in text) + { + if (char.IsWhiteSpace(ch)) + { + pendingSpace = builder.Length > 0; + continue; + } + + if (pendingSpace) + { + builder.Append(' '); + pendingSpace = false; + } + + builder.Append(ch); + } + + return builder.ToString(); + } + + // Первые max кодовых точек строки (python-срез без разрыва суррогатных пар). + private static string SliceCodePoints(string text, int max) + { + if (text.Length <= max) + { + return text; + } + + var builder = new StringBuilder(max); + int taken = 0; + for (int index = 0; index < text.Length && taken < max; index++) + { + bool pair = char.IsHighSurrogate(text[index]) + && index + 1 < text.Length + && char.IsLowSurrogate(text[index + 1]); + builder.Append(text[index]); + if (pair) + { + index++; + builder.Append(text[index]); + } + + taken++; + } + + return builder.ToString(); + } +} diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs index a8765a7..8375134 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Helpers.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Helpers.cs index 54f414e..4bbd755 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Helpers.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Helpers.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs index 665a934..3c2663b 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Search.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Search.cs index eff9ef3..ce7f171 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Search.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Search.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.cs index 4ce01df..0c39767 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Discovery.Application.Models; using Deal.Modules.Discovery.Application.Abstractions; diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs index e4c5638..4e6e9eb 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs index a632b21..049fe2a 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs @@ -1,130 +1,130 @@ -using Deal.Contracts.Integrations; -using Deal.Contracts.Integrations.Models; -using Deal.Modules.Cards.Application.Abstractions; -using Deal.Modules.Cards.Application.Dtos; -using Deal.Modules.Cards.Application.Models; -using Deal.Modules.Kanban.Application.ColumnRules; -using Deal.Modules.Kanban.Application.Models; -using Deal.Modules.Settings.Application.Abstractions; -using Deal.Modules.Settings.Application.Models; -using Deal.Modules.Settings.Application.Registrars; -using Deal.Modules.Settings.Application.Services; -// Алиас: статический класс ColumnRules лежит в одноимённом пространстве имён — внутри пространства имён -// Deal.Modules.Kanban.Application имя ColumnRules резолвится в пространство (CS0234), нужен явный алиас. -using KanbanColumnRules = Deal.Modules.Kanban.Application.ColumnRules.ColumnRules; -using Deal.Modules.Kanban.Application.Abstractions; -using Deal.Modules.Kanban.Application.Extensions; -using Deal.Modules.Kanban.Application.Registrars; - -namespace Deal.Modules.Kanban.Application.Services; - -/// -/// Приватные помощники — partial-часть (C32: выделено из общего файла, -/// поведение не менялось): текст обучающего примера, перенос колонки с журналом, hits правил доски и -/// колонка возврата (leads.py L163–174, L209, L311–319; Ruling 2/4). -/// -public sealed partial class CardsService -{ - // ── Внутреннее ───────────────────────────────────────────────────────── - - // Текст обучающего примера: source_msg (после Trim) или title (leads.py L167, L189–190, L199–200). - // card: Карточка. - // Возвращает: source_msg без краевых пробелов; пустой — title как сохранён (1:1 с (x or "").strip() or (y or "")). - private static string LearningText(CardDto card) - { - string source = card.SourceMsg.Trim(); - return source.Length > 0 ? source : card.Title; - } - - // Меняет колонку карточки и пишет строку журнала CardMoves (leads.py _move L170–174). - // card: Карточка ДО переноса (для prev_col/from_col журнала). - // toCol: Новая колонка. - // hits: matchHits для новой колонки (пересчитаны вызывающим, Ruling 2). - // action: Действие журнала: move/trash. - // ct: Токен отмены. - private async Task MoveToColumnAsync( - CardDto card, - string toCol, - IReadOnlyList hits, - string action, - CancellationToken ct) - { - await _store.UpdateColumnAsync(new CardColumnUpdateDto( - CardId: card.Id, - Col: toCol, - IsNew: false, - PrevCol: card.Col, - ArchivedAt: null, - MatchHits: hits), ct); - await LogMoveAsync(card.Id, action, card.Col, toCol, ct); - } - - // Пишет строку журнала действия (leads.py _log_learning L40–44): id lm_ генерирует модуль. - // cardId: Id карточки. - // action: Действие: move/trash/restore/comment. - // fromCol: Прежняя колонка (для comment — null). - // toCol: Новая колонка (для comment — null). - // ct: Токен отмены. - private async Task LogMoveAsync( - string cardId, - string action, - string? fromCol, - string? toCol, - CancellationToken ct) - { - await _store.AddMoveAsync(new CardMoveDto( - PrefixId.New(KanbanIdPrefixes.CardMove), - cardId, - action, - fromCol, - toCol), ct); - } - - // Совпавшие критерии правил доски (hits_for_board L311–319 через ColumnRules, Ruling 2). - // boardId: Id доски (b_...). - // text: Текст карточки для правил (source_msg или title). - // ct: Токен отмены. - // Возвращает: Список совпавших критериев; доски нет/правил нет → пусто. - private async Task> ComputeHitsForBoardAsync( - string boardId, - string text, - CancellationToken ct) - { - ContainerDto? board = await _store.GetContainerAsync(boardId, ct); - return board is null - ? Array.Empty() - : await ComputeHitsAsync(board.Rules, text, ct); - } - - // Совпавшие критерии правил колонки-доски (ColumnRules.ComputeHits с курсами для бюджета). - // Резолв имени — через алиас KanbanColumnRules: одноимённые класс и namespace ColumnRules в одном модуле. - // rules: Правила доски; null («правил нет») → пусто (Ruling 2). - // text: Текст карточки для правил. - // ct: Токен отмены. - // Возвращает: Совпавшие критерии (label/term[/word]); нет активных правил → пусто. - private async Task> ComputeHitsAsync( - ContainerRulesDto? rules, - string text, - CancellationToken ct) - { - // Кэш курсов из типизированного снимка настроек (C30): null → мок-курсы (дефолт RatesService). - IReadOnlyDictionary rates = - (await TenantSettingsSnapshot.LoadAsync(_settings, ct)).TryGetRatesCache()?.Rates ?? MockRates.Values; - return KanbanColumnRules.ComputeHits(rules, text, rates); - } - - // Колонка возврата карточки: prev_col, если inbox или существующая доска, иначе inbox (restore_lead L209). - // prevCol: Сохранённая prev_col карточки. - // ct: Токен отмены. - // Возвращает: Колонка возврата (inbox/доска). - private async Task ResolveReturnColAsync(string prevCol, CancellationToken ct) - { - if (prevCol == CardIds.Inbox) - { - return prevCol; - } - - ContainerDto? board = await _store.GetContainerAsync(prevCol, ct); - return board is null ? CardIds.Inbox : prevCol; - } -} +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; +using Deal.Modules.Cards.Application.Abstractions; +using Deal.Modules.Cards.Application.Dtos; +using Deal.Modules.Cards.Application.Models; +using Deal.Modules.Kanban.Application.ColumnRules; +using Deal.Modules.Kanban.Application.Models; +using Deal.Modules.Settings.Application.Abstractions; +using Deal.Modules.Settings.Application.Models; +using Deal.Modules.Settings.Application.Registrars; +using Deal.Modules.Settings.Application.Services; +// Алиас: статический класс ColumnRules лежит в одноимённом пространстве имён — внутри пространства имён +// Deal.Modules.Kanban.Application имя ColumnRules резолвится в пространство (CS0234), нужен явный алиас. +using KanbanColumnRules = Deal.Modules.Kanban.Application.ColumnRules.ColumnRules; +using Deal.Modules.Kanban.Application.Abstractions; +using Deal.Modules.Kanban.Application.Extensions; +using Deal.Modules.Kanban.Application.Registrars; + +namespace Deal.Modules.Kanban.Application.Services; + +/// +/// Приватные помощники — partial-часть (C32: выделено из общего файла, +/// поведение не менялось): текст обучающего примера, перенос колонки с журналом, hits правил доски и +/// колонка возврата (leads.py L163–174, L209, L311–319; Ruling 2/4). +/// +public sealed partial class CardsService +{ + // ── Внутреннее ───────────────────────────────────────────────────────── + + // Текст обучающего примера: source_msg (после Trim) или title (leads.py L167, L189–190, L199–200). + // card: Карточка. + // Возвращает: source_msg без краевых пробелов; пустой — title как сохранён (1:1 с (x or "").strip() or (y or "")). + private static string LearningText(CardDto card) + { + string source = card.SourceMsg.Trim(); + return source.Length > 0 ? source : card.Title; + } + + // Меняет колонку карточки и пишет строку журнала CardMoves (leads.py _move L170–174). + // card: Карточка ДО переноса (для prev_col/from_col журнала). + // toCol: Новая колонка. + // hits: matchHits для новой колонки (пересчитаны вызывающим, Ruling 2). + // action: Действие журнала: move/trash. + // ct: Токен отмены. + private async Task MoveToColumnAsync( + CardDto card, + string toCol, + IReadOnlyList hits, + string action, + CancellationToken ct) + { + await _store.UpdateColumnAsync(new CardColumnUpdateDto( + CardId: card.Id, + Col: toCol, + IsNew: false, + PrevCol: card.Col, + ArchivedAt: null, + MatchHits: hits), ct); + await LogMoveAsync(card.Id, action, card.Col, toCol, ct); + } + + // Пишет строку журнала действия (leads.py _log_learning L40–44): id lm_ генерирует модуль. + // cardId: Id карточки. + // action: Действие: move/trash/restore/comment. + // fromCol: Прежняя колонка (для comment — null). + // toCol: Новая колонка (для comment — null). + // ct: Токен отмены. + private async Task LogMoveAsync( + string cardId, + string action, + string? fromCol, + string? toCol, + CancellationToken ct) + { + await _store.AddMoveAsync(new CardMoveDto( + PrefixId.New(KanbanIdPrefixes.CardMove), + cardId, + action, + fromCol, + toCol), ct); + } + + // Совпавшие критерии правил доски (hits_for_board L311–319 через ColumnRules, Ruling 2). + // boardId: Id доски (b_...). + // text: Текст карточки для правил (source_msg или title). + // ct: Токен отмены. + // Возвращает: Список совпавших критериев; доски нет/правил нет → пусто. + private async Task> ComputeHitsForBoardAsync( + string boardId, + string text, + CancellationToken ct) + { + ContainerDto? board = await _store.GetContainerAsync(boardId, ct); + return board is null + ? Array.Empty() + : await ComputeHitsAsync(board.Rules, text, ct); + } + + // Совпавшие критерии правил колонки-доски (ColumnRules.ComputeHits с курсами для бюджета). + // Резолв имени — через алиас KanbanColumnRules: одноимённые класс и namespace ColumnRules в одном модуле. + // rules: Правила доски; null («правил нет») → пусто (Ruling 2). + // text: Текст карточки для правил. + // ct: Токен отмены. + // Возвращает: Совпавшие критерии (label/term[/word]); нет активных правил → пусто. + private async Task> ComputeHitsAsync( + ContainerRulesDto? rules, + string text, + CancellationToken ct) + { + // Кэш курсов из типизированного снимка настроек (C30): null → мок-курсы (дефолт RatesService). + IReadOnlyDictionary rates = + (await TenantSettingsSnapshot.LoadAsync(_settings, ct)).TryGetRatesCache()?.Rates ?? MockRates.Values; + return KanbanColumnRules.ComputeHits(rules, text, rates); + } + + // Колонка возврата карточки: prev_col, если inbox или существующая доска, иначе inbox (restore_lead L209). + // prevCol: Сохранённая prev_col карточки. + // ct: Токен отмены. + // Возвращает: Колонка возврата (inbox/доска). + private async Task ResolveReturnColAsync(string prevCol, CancellationToken ct) + { + if (prevCol == CardIds.Inbox) + { + return prevCol; + } + + ContainerDto? board = await _store.GetContainerAsync(prevCol, ct); + return board is null ? CardIds.Inbox : prevCol; + } +} diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs index 1a9d366..9105ddd 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs @@ -1,361 +1,361 @@ -using Deal.Contracts.Integrations; -using Deal.Contracts.Integrations.Models; -using Deal.Modules.Cards.Application.Abstractions; -using Deal.Modules.Cards.Application.Dtos; -using Deal.Modules.Cards.Application.Models; -using Deal.Modules.Kanban.Application.ColumnRules; -using Deal.Modules.Kanban.Application.Models; -using Deal.Modules.Settings.Application.Abstractions; -using Deal.Modules.Settings.Application.Models; -using Deal.Modules.Settings.Application.Registrars; -using Deal.Modules.Settings.Application.Services; -using Deal.Modules.Kanban.Application.Abstractions; -using Deal.Modules.Kanban.Application.Extensions; -using Deal.Modules.Kanban.Application.Registrars; - -namespace Deal.Modules.Kanban.Application.Services; - -/// -/// Публичные операции карточек — partial-часть (C32: выделено из общего -/// файла по темам, поведение не менялось): чтение списка/карточки, переносы/корзина/возврат/удаление/ -/// очистка колонки, комментарии, mark-seen, счётчики и поиск (leads.py L151–279, L509–551). -/// -public sealed partial class CardsService -{ - // ── Чтение (list_leads/get_lead L151–160) ─────────────────────────────── - - /// - /// Карточки колонки или всех колонок дашборда, received_at DESC (list_leads L151–156). - /// - /// Колонка-фильтр (inbox/archive/trash/доска); null — все колонки дашборда. - /// Токен отмены. - /// Полные карточки (маппинг/комментарии/time — адаптер); пусто — карточек нет. - public Task> ListCardsAsync(string? col, CancellationToken ct) - { - return _store.ListCardsAsync(new CardsQuery(col), ct); - } - - /// - /// Одна карточка по id (get_lead L159–160; GET /api/cards/{id}). - /// - /// Id карточки (c_...). - /// Токен отмены. - /// Карточка или null — строки нет (эндпоинт отвечает 404 «Карточка не найдена»). - public Task GetCardAsync(string cardId, CancellationToken ct) - { - return _store.GetCardAsync(cardId, ct); - } - - // ── Переносы / архив / корзина (L163–247) ─────────────────────────────── - - /// - /// Перенос карточки на доску или в «Неразобранное» (move_lead L177–191 + _move L163–174). - /// - /// - /// Цель валидируется до чтения карточки: не inbox и не существующая доска → 400 - /// . Исходная колонка archive/trash для MoveLeadAsync недоступна - /// → 400 : вывод из них — только restore_lead (иначе перенос минует - /// снятие метки «спам» возврата из корзины, Ruling 4). Перенос «в ту же колонку» — no-op (карточка - /// возвращается без изменений; журнал и обучение не пишутся). При реальном переносе: колонка меняется - /// (is_new=FALSE, prev_col = прежняя колонка), matchHits пересчитываются для доски через - /// (Ruling 2; для inbox — пусто), пишется строка журнала action=move, - /// а при to≠inbox — обучающий сигнал PushAsync(text, id доски, 1.0) (Ruling 4; текст = source_msg или title; - /// пустой текст не учим — L188–191). - /// - /// Id карточки (c_...). - /// Цель: inbox либо id доски (b_...). - /// Токен отмены. - /// Результат: Error (400) | Card=null (карточки нет, 404) | Card — карточка после переноса. - public async Task MoveDashboardCardAsync( - string cardId, - string toCol, - CancellationToken ct) - { - ContainerDto? board = null; - if (toCol != CardIds.Inbox) - { - board = await _store.GetContainerAsync(toCol, ct); - if (board is null) - { - return new CardResultDto(MoveTargetInvalidDetail, null); - } - } - - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return new CardResultDto(null, null); - } - - // Из archive/trash карточку выводит только restore (L204–222): прямой move в доску - // прошёл бы мимо снятия у ML веса «спама» возврата из корзины (Ruling 4, L218–221). - if (card.Col == CardIds.Archive || card.Col == CardIds.Trash) - { - return new CardResultDto(MoveSourceRestrictedDetail, null); - } - - if (card.Col == toCol) - { - return new CardResultDto(null, card); - } - - string text = LearningText(card); - IReadOnlyList hits = toCol == CardIds.Inbox - ? Array.Empty() - : await ComputeHitsAsync(board!.Rules, text, ct); - - await MoveToColumnAsync(card, toCol, hits, ActionMove, ct); - if (toCol != CardIds.Inbox && text.Length > 0) - { - await _mlClient.PushAsync(text, toCol, PushWeightUser, ct); - } - - return new CardResultDto(null, await _store.GetCardAsync(cardId, ct)); - } - - /// - /// Перенос карточки в корзину (trash_lead L194–201): col=trash, is_new=FALSE, matchHits пусто. - /// - /// - /// Карточка уже в корзине — no-op (как в _move L165–166). Журнал action=trash пишется при реальном - /// переносе; обучающий сигнал «спам» 1.0 — только если карточка была НЕ в trash/archive (L198–201, - /// Ruling 4). Исключение archive: перенос архива в корзину не «переучивает» на спам. - /// - /// Id карточки (c_...). - /// Токен отмены. - /// Карточка после переноса (при no-op — как была) либо null — карточки нет (404). - public Task TrashCardAsync(string cardId, CancellationToken ct) - { - return TrashCardAsync(cardId, teach: true, ct); - } - - /// - /// Перенос карточки в корзину с управлением обучением ML (trash_lead L194–201). - /// - /// - /// = false — «тихое» перемещение без сигнала «спам»: используется ручной - /// переклассификацией (leads.py reclassify_lead L311/L316), где обучение кладётся ЯВНО одним сигналом - /// с весом гипотезы ИИ (0.4), а не весом действия пользователя (1.0). Журнал action=trash пишется всегда. - /// - /// Id карточки (c_...). - /// True — писать сигнал «спам» (действие пользователя); false — не писать. - /// Токен отмены. - /// Карточка после переноса (при no-op — как была) либо null — карточки нет (404). - public async Task TrashCardAsync( - string cardId, - bool teach, - CancellationToken ct) - { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return null; - } - - if (card.Col == CardIds.Trash) - { - return card; - } - - string text = LearningText(card); - await MoveToColumnAsync(card, CardIds.Trash, Array.Empty(), ActionTrash, ct); - if (teach && card.Col != CardIds.Archive && text.Length > 0) - { - await _mlClient.PushAsync(text, MlLearningLabels.Spam, PushWeightUser, ct); - } - - return await _store.GetCardAsync(cardId, ct); - } - - /// - /// Возврат карточки из архива/корзины на канбан (restore_lead L204–222). - /// - /// - /// Куда возвращаем: prev_col, если это «Неразобранное» или существующая доска, иначе inbox (L209). - /// При возврате is_new=TRUE, prev_col='inbox', archived_at=NULL (Ruling 10), matchHits пересчитаны для - /// доски (Ruling 2), журнал action=restore. Возврат ИЗ корзины снимает метку спам: - /// PushAsync(text, "spam", −1.0) (L218–221, Ruling 4); из архива сигнал не шлётся. - /// - /// Id карточки (c_...). - /// Токен отмены. - /// Колонка возврата (inbox/доска) либо null — карточки нет (404). - public async Task RestoreCardAsync(string cardId, CancellationToken ct) - { - CardDto? card = await _store.GetCardAsync(cardId, ct); - if (card is null) - { - return null; - } - - string back = await ResolveReturnColAsync(card.PrevCol, ct); - string text = LearningText(card); - IReadOnlyList hits = back == CardIds.Inbox - ? Array.Empty() - : await ComputeHitsForBoardAsync(back, text, ct); - - await _store.UpdateColumnAsync(new CardColumnUpdateDto( - CardId: cardId, - Col: back, - IsNew: true, - PrevCol: CardIds.Inbox, - ArchivedAt: null, - MatchHits: hits), ct); - await LogMoveAsync(cardId, ActionRestore, card.Col, back, ct); - - if (card.Col == CardIds.Trash && text.Length > 0) - { - await _mlClient.PushAsync(text, MlLearningLabels.Spam, PushWeightUnlearn, ct); - } - - return back; - } - - /// - /// Полное удаление карточки (delete_forever L225–234): Cards + комментарии (FK cascade), - /// журнал CardMoves/MlOutbox не трогаются (Ruling 10). - /// - /// Id карточки (c_...). - /// Токен отмены. - /// False — карточки нет (404 «Карточка не найдена»); True — удалена. - public async Task DeleteForeverAsync(string cardId, CancellationToken ct) - { - if (await _store.GetCardAsync(cardId, ct) is null) - { - return false; - } - - await _store.DeleteForeverAsync(cardId, ct); - return true; - } - - /// - /// Полная ручная очистка служебной колонки trash/archive (clear_col L237–247). - /// - /// Другая колонка (inbox/доска/…) → 400 (как ValueError L239–240). - /// Очищаемая колонка: trash | archive. - /// Токен отмены. - /// Результат: Error (400) либо Cleared — сколько карточек удалено навсегда. - public async Task ClearColAsync(string col, CancellationToken ct) - { - if (col != CardIds.Trash && col != CardIds.Archive) - { - return new ClearColResultDto(ClearColInvalidDetail, 0); - } - - int cleared = await _store.ClearColAsync(col, ct); - return new ClearColResultDto(null, cleared); - } - - // ── Комментарии (add_comment L259–265) ────────────────────────────────── - - /// - /// Добавляет комментарий к карточке: строка LeadComments (id cm_) + журнал action=comment. - /// - /// - /// Текст Trim'ится (пустой после Trim → 400 , как dashboard_routes L240–241); - /// автор — «Вы»; ответ — полный список комментариев (свежий — «только что», маппинг адаптера). Карточки - /// нет → Comments=null, Error=null (404 «Карточка не найдена» — сервис читает карточку до записи, порт - /// LeadComments ссылается FK, Task 4). - /// - /// Id карточки (c_...). - /// Текст комментария (непустой после Trim). - /// Токен отмены. - /// Результат: Error (400) | Comments=null (404) | Comments — список после добавления. - public async Task AddCommentAsync( - string cardId, - string text, - CancellationToken ct) - { - string trimmed = (text ?? string.Empty).Trim(); - if (trimmed.Length == 0) - { - return new AddCommentResultDto(EmptyCommentDetail, null); - } - - if (await _store.GetCardAsync(cardId, ct) is null) - { - return new AddCommentResultDto(null, null); - } - - await _store.AddCommentAsync(PrefixId.New(KanbanIdPrefixes.Comment), cardId, CommentAuthor, trimmed, ct); - await LogMoveAsync(cardId, ActionComment, null, null, ct); - IReadOnlyList comments = await _store.ListCommentsAsync(cardId, ct); - return new AddCommentResultDto(null, comments); - } - - // ── Пометить прочитанным (mark_seen L250–256) ─────────────────────────── - - /// - /// Снимает флаг «новое»: с одной карточки (cardId), колонки (col) или всех (оба null/пустые). - /// - /// Семантика 1:1 с mark_seen L250–256 (проверка на truthiness: пустая строка = параметр не задан). - /// Эндпоинты этапа: mark-col-seen {col}, mark-all-seen (Ruling 11); /leads/{id}/seen фронтом не вызывается. - /// Id карточки либо null/пусто. - /// Колонка либо null/пусто (используется, когда cardId не задан). - /// Токен отмены. - public Task MarkSeenAsync( - string? cardId, - string? col, - CancellationToken ct) - { - return _store.UpdateSeenAsync( - string.IsNullOrEmpty(cardId) ? null : cardId, - string.IsNullOrEmpty(col) ? null : col, - ct); - } - - // ── Счётчики (counts L268–279) ────────────────────────────────────────── - - /// - /// Счётчики колонок (count+new по Cards) + статистика обучения/решений ML (learning/ml/ai). - /// - /// - /// Форма CardCountsDto: Columns — только колонки с карточками; New — сумма «новых» по колонкам - /// (counts L270–274). learning/ml/ai — из IMlClient.StatusAsync (L275–278, план Task 7 L321): learning = - /// count(CardMoves), ml/ai — KV-счётчики решений пайплайна (на этапе 3 — 0, Ruling 4). Плоскую wire-форму - /// «{new, <col>:{…}, learning, ml, ai}» собирает эндпоинт Task 8. - /// - /// Токен отмены. - /// Счётчики: колонки + learning/ml/ai (поля New/Learning/Ml/Ai и словарь Columns). - public async Task CountsAsync(CancellationToken ct) - { - IReadOnlyDictionary columns = await _store.CountCardsByColAsync(ct); - MlStatusResponseDto mlStatus = await _mlClient.StatusAsync(ct); - return new CardCountsDto - { - New = columns.Values.Sum(column => column.New), - Columns = columns, - Learning = mlStatus.Stats.Learning, - Ml = mlStatus.Stats.Ml, - Ai = mlStatus.Stats.Ai, - }; - } - - // ── Поиск (search L509–551, LIKE-вариант Ruling 6) ────────────────────── - - /// - /// Поиск карточек: FTS по Cards.SearchTsv + LIKE-дополнение (search L509–551, Ruling 6/Task 12). - /// - /// - /// q после Trim короче 2 символов → пусто, порт не вызывается (поведение этапа 3, L511–512). Сам поиск - /// выполняет адаптер — : SearchTsv @@ plainto_tsquery('russian') - /// (морфология) ∪ LIKE по lower(title/summary/contact/source_msg), контейнеры-стадии «Выбранных» - /// исключены, порядок - /// ts_rank DESC, ReceivedAt DESC, результат ограничен = 12. messages: [] — на - /// совесть эндпоинта (Task 8). Запрос нормализуется trim+lowercase (как отсев-поиск Ruling 6). - /// - /// Поисковый запрос (trim + lowercase внутри). - /// Токен отмены. - /// Найденные карточки (≤12); пусто — запрос короче 2 символов или нет совпадений. - public async Task> SearchCardsAsync(string? query, CancellationToken ct) - { - string lowered = (query ?? string.Empty).Trim().ToLowerInvariant(); - if (lowered.Length < MinSearchQueryLength) - { - return Array.Empty(); - } - - return await _store.SearchCardsAsync(lowered, SearchLimit, ct); - } - -} +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; +using Deal.Modules.Cards.Application.Abstractions; +using Deal.Modules.Cards.Application.Dtos; +using Deal.Modules.Cards.Application.Models; +using Deal.Modules.Kanban.Application.ColumnRules; +using Deal.Modules.Kanban.Application.Models; +using Deal.Modules.Settings.Application.Abstractions; +using Deal.Modules.Settings.Application.Models; +using Deal.Modules.Settings.Application.Registrars; +using Deal.Modules.Settings.Application.Services; +using Deal.Modules.Kanban.Application.Abstractions; +using Deal.Modules.Kanban.Application.Extensions; +using Deal.Modules.Kanban.Application.Registrars; + +namespace Deal.Modules.Kanban.Application.Services; + +/// +/// Публичные операции карточек — partial-часть (C32: выделено из общего +/// файла по темам, поведение не менялось): чтение списка/карточки, переносы/корзина/возврат/удаление/ +/// очистка колонки, комментарии, mark-seen, счётчики и поиск (leads.py L151–279, L509–551). +/// +public sealed partial class CardsService +{ + // ── Чтение (list_leads/get_lead L151–160) ─────────────────────────────── + + /// + /// Карточки колонки или всех колонок дашборда, received_at DESC (list_leads L151–156). + /// + /// Колонка-фильтр (inbox/archive/trash/доска); null — все колонки дашборда. + /// Токен отмены. + /// Полные карточки (маппинг/комментарии/time — адаптер); пусто — карточек нет. + public Task> ListCardsAsync(string? col, CancellationToken ct) + { + return _store.ListCardsAsync(new CardsQuery(col), ct); + } + + /// + /// Одна карточка по id (get_lead L159–160; GET /api/cards/{id}). + /// + /// Id карточки (c_...). + /// Токен отмены. + /// Карточка или null — строки нет (эндпоинт отвечает 404 «Карточка не найдена»). + public Task GetCardAsync(string cardId, CancellationToken ct) + { + return _store.GetCardAsync(cardId, ct); + } + + // ── Переносы / архив / корзина (L163–247) ─────────────────────────────── + + /// + /// Перенос карточки на доску или в «Неразобранное» (move_lead L177–191 + _move L163–174). + /// + /// + /// Цель валидируется до чтения карточки: не inbox и не существующая доска → 400 + /// . Исходная колонка archive/trash для MoveLeadAsync недоступна + /// → 400 : вывод из них — только restore_lead (иначе перенос минует + /// снятие метки «спам» возврата из корзины, Ruling 4). Перенос «в ту же колонку» — no-op (карточка + /// возвращается без изменений; журнал и обучение не пишутся). При реальном переносе: колонка меняется + /// (is_new=FALSE, prev_col = прежняя колонка), matchHits пересчитываются для доски через + /// (Ruling 2; для inbox — пусто), пишется строка журнала action=move, + /// а при to≠inbox — обучающий сигнал PushAsync(text, id доски, 1.0) (Ruling 4; текст = source_msg или title; + /// пустой текст не учим — L188–191). + /// + /// Id карточки (c_...). + /// Цель: inbox либо id доски (b_...). + /// Токен отмены. + /// Результат: Error (400) | Card=null (карточки нет, 404) | Card — карточка после переноса. + public async Task MoveDashboardCardAsync( + string cardId, + string toCol, + CancellationToken ct) + { + ContainerDto? board = null; + if (toCol != CardIds.Inbox) + { + board = await _store.GetContainerAsync(toCol, ct); + if (board is null) + { + return new CardResultDto(MoveTargetInvalidDetail, null); + } + } + + CardDto? card = await _store.GetCardAsync(cardId, ct); + if (card is null) + { + return new CardResultDto(null, null); + } + + // Из archive/trash карточку выводит только restore (L204–222): прямой move в доску + // прошёл бы мимо снятия у ML веса «спама» возврата из корзины (Ruling 4, L218–221). + if (card.Col == CardIds.Archive || card.Col == CardIds.Trash) + { + return new CardResultDto(MoveSourceRestrictedDetail, null); + } + + if (card.Col == toCol) + { + return new CardResultDto(null, card); + } + + string text = LearningText(card); + IReadOnlyList hits = toCol == CardIds.Inbox + ? Array.Empty() + : await ComputeHitsAsync(board!.Rules, text, ct); + + await MoveToColumnAsync(card, toCol, hits, ActionMove, ct); + if (toCol != CardIds.Inbox && text.Length > 0) + { + await _mlClient.PushAsync(text, toCol, PushWeightUser, ct); + } + + return new CardResultDto(null, await _store.GetCardAsync(cardId, ct)); + } + + /// + /// Перенос карточки в корзину (trash_lead L194–201): col=trash, is_new=FALSE, matchHits пусто. + /// + /// + /// Карточка уже в корзине — no-op (как в _move L165–166). Журнал action=trash пишется при реальном + /// переносе; обучающий сигнал «спам» 1.0 — только если карточка была НЕ в trash/archive (L198–201, + /// Ruling 4). Исключение archive: перенос архива в корзину не «переучивает» на спам. + /// + /// Id карточки (c_...). + /// Токен отмены. + /// Карточка после переноса (при no-op — как была) либо null — карточки нет (404). + public Task TrashCardAsync(string cardId, CancellationToken ct) + { + return TrashCardAsync(cardId, teach: true, ct); + } + + /// + /// Перенос карточки в корзину с управлением обучением ML (trash_lead L194–201). + /// + /// + /// = false — «тихое» перемещение без сигнала «спам»: используется ручной + /// переклассификацией (leads.py reclassify_lead L311/L316), где обучение кладётся ЯВНО одним сигналом + /// с весом гипотезы ИИ (0.4), а не весом действия пользователя (1.0). Журнал action=trash пишется всегда. + /// + /// Id карточки (c_...). + /// True — писать сигнал «спам» (действие пользователя); false — не писать. + /// Токен отмены. + /// Карточка после переноса (при no-op — как была) либо null — карточки нет (404). + public async Task TrashCardAsync( + string cardId, + bool teach, + CancellationToken ct) + { + CardDto? card = await _store.GetCardAsync(cardId, ct); + if (card is null) + { + return null; + } + + if (card.Col == CardIds.Trash) + { + return card; + } + + string text = LearningText(card); + await MoveToColumnAsync(card, CardIds.Trash, Array.Empty(), ActionTrash, ct); + if (teach && card.Col != CardIds.Archive && text.Length > 0) + { + await _mlClient.PushAsync(text, MlLearningLabels.Spam, PushWeightUser, ct); + } + + return await _store.GetCardAsync(cardId, ct); + } + + /// + /// Возврат карточки из архива/корзины на канбан (restore_lead L204–222). + /// + /// + /// Куда возвращаем: prev_col, если это «Неразобранное» или существующая доска, иначе inbox (L209). + /// При возврате is_new=TRUE, prev_col='inbox', archived_at=NULL (Ruling 10), matchHits пересчитаны для + /// доски (Ruling 2), журнал action=restore. Возврат ИЗ корзины снимает метку спам: + /// PushAsync(text, "spam", −1.0) (L218–221, Ruling 4); из архива сигнал не шлётся. + /// + /// Id карточки (c_...). + /// Токен отмены. + /// Колонка возврата (inbox/доска) либо null — карточки нет (404). + public async Task RestoreCardAsync(string cardId, CancellationToken ct) + { + CardDto? card = await _store.GetCardAsync(cardId, ct); + if (card is null) + { + return null; + } + + string back = await ResolveReturnColAsync(card.PrevCol, ct); + string text = LearningText(card); + IReadOnlyList hits = back == CardIds.Inbox + ? Array.Empty() + : await ComputeHitsForBoardAsync(back, text, ct); + + await _store.UpdateColumnAsync(new CardColumnUpdateDto( + CardId: cardId, + Col: back, + IsNew: true, + PrevCol: CardIds.Inbox, + ArchivedAt: null, + MatchHits: hits), ct); + await LogMoveAsync(cardId, ActionRestore, card.Col, back, ct); + + if (card.Col == CardIds.Trash && text.Length > 0) + { + await _mlClient.PushAsync(text, MlLearningLabels.Spam, PushWeightUnlearn, ct); + } + + return back; + } + + /// + /// Полное удаление карточки (delete_forever L225–234): Cards + комментарии (FK cascade), + /// журнал CardMoves/MlOutbox не трогаются (Ruling 10). + /// + /// Id карточки (c_...). + /// Токен отмены. + /// False — карточки нет (404 «Карточка не найдена»); True — удалена. + public async Task DeleteForeverAsync(string cardId, CancellationToken ct) + { + if (await _store.GetCardAsync(cardId, ct) is null) + { + return false; + } + + await _store.DeleteForeverAsync(cardId, ct); + return true; + } + + /// + /// Полная ручная очистка служебной колонки trash/archive (clear_col L237–247). + /// + /// Другая колонка (inbox/доска/…) → 400 (как ValueError L239–240). + /// Очищаемая колонка: trash | archive. + /// Токен отмены. + /// Результат: Error (400) либо Cleared — сколько карточек удалено навсегда. + public async Task ClearColAsync(string col, CancellationToken ct) + { + if (col != CardIds.Trash && col != CardIds.Archive) + { + return new ClearColResultDto(ClearColInvalidDetail, 0); + } + + int cleared = await _store.ClearColAsync(col, ct); + return new ClearColResultDto(null, cleared); + } + + // ── Комментарии (add_comment L259–265) ────────────────────────────────── + + /// + /// Добавляет комментарий к карточке: строка LeadComments (id cm_) + журнал action=comment. + /// + /// + /// Текст Trim'ится (пустой после Trim → 400 , как dashboard_routes L240–241); + /// автор — «Вы»; ответ — полный список комментариев (свежий — «только что», маппинг адаптера). Карточки + /// нет → Comments=null, Error=null (404 «Карточка не найдена» — сервис читает карточку до записи, порт + /// LeadComments ссылается FK, Task 4). + /// + /// Id карточки (c_...). + /// Текст комментария (непустой после Trim). + /// Токен отмены. + /// Результат: Error (400) | Comments=null (404) | Comments — список после добавления. + public async Task AddCommentAsync( + string cardId, + string text, + CancellationToken ct) + { + string trimmed = (text ?? string.Empty).Trim(); + if (trimmed.Length == 0) + { + return new AddCommentResultDto(EmptyCommentDetail, null); + } + + if (await _store.GetCardAsync(cardId, ct) is null) + { + return new AddCommentResultDto(null, null); + } + + await _store.AddCommentAsync(PrefixId.New(KanbanIdPrefixes.Comment), cardId, CommentAuthor, trimmed, ct); + await LogMoveAsync(cardId, ActionComment, null, null, ct); + IReadOnlyList comments = await _store.ListCommentsAsync(cardId, ct); + return new AddCommentResultDto(null, comments); + } + + // ── Пометить прочитанным (mark_seen L250–256) ─────────────────────────── + + /// + /// Снимает флаг «новое»: с одной карточки (cardId), колонки (col) или всех (оба null/пустые). + /// + /// Семантика 1:1 с mark_seen L250–256 (проверка на truthiness: пустая строка = параметр не задан). + /// Эндпоинты этапа: mark-col-seen {col}, mark-all-seen (Ruling 11); /leads/{id}/seen фронтом не вызывается. + /// Id карточки либо null/пусто. + /// Колонка либо null/пусто (используется, когда cardId не задан). + /// Токен отмены. + public Task MarkSeenAsync( + string? cardId, + string? col, + CancellationToken ct) + { + return _store.UpdateSeenAsync( + string.IsNullOrEmpty(cardId) ? null : cardId, + string.IsNullOrEmpty(col) ? null : col, + ct); + } + + // ── Счётчики (counts L268–279) ────────────────────────────────────────── + + /// + /// Счётчики колонок (count+new по Cards) + статистика обучения/решений ML (learning/ml/ai). + /// + /// + /// Форма CardCountsDto: Columns — только колонки с карточками; New — сумма «новых» по колонкам + /// (counts L270–274). learning/ml/ai — из IMlClient.StatusAsync (L275–278, план Task 7 L321): learning = + /// count(CardMoves), ml/ai — KV-счётчики решений пайплайна (на этапе 3 — 0, Ruling 4). Плоскую wire-форму + /// «{new, <col>:{…}, learning, ml, ai}» собирает эндпоинт Task 8. + /// + /// Токен отмены. + /// Счётчики: колонки + learning/ml/ai (поля New/Learning/Ml/Ai и словарь Columns). + public async Task CountsAsync(CancellationToken ct) + { + IReadOnlyDictionary columns = await _store.CountCardsByColAsync(ct); + MlStatusResponseDto mlStatus = await _mlClient.StatusAsync(ct); + return new CardCountsDto + { + New = columns.Values.Sum(column => column.New), + Columns = columns, + Learning = mlStatus.Stats.Learning, + Ml = mlStatus.Stats.Ml, + Ai = mlStatus.Stats.Ai, + }; + } + + // ── Поиск (search L509–551, LIKE-вариант Ruling 6) ────────────────────── + + /// + /// Поиск карточек: FTS по Cards.SearchTsv + LIKE-дополнение (search L509–551, Ruling 6/Task 12). + /// + /// + /// q после Trim короче 2 символов → пусто, порт не вызывается (поведение этапа 3, L511–512). Сам поиск + /// выполняет адаптер — : SearchTsv @@ plainto_tsquery('russian') + /// (морфология) ∪ LIKE по lower(title/summary/contact/source_msg), контейнеры-стадии «Выбранных» + /// исключены, порядок + /// ts_rank DESC, ReceivedAt DESC, результат ограничен = 12. messages: [] — на + /// совесть эндпоинта (Task 8). Запрос нормализуется trim+lowercase (как отсев-поиск Ruling 6). + /// + /// Поисковый запрос (trim + lowercase внутри). + /// Токен отмены. + /// Найденные карточки (≤12); пусто — запрос короче 2 символов или нет совпадений. + public async Task> SearchCardsAsync(string? query, CancellationToken ct) + { + string lowered = (query ?? string.Empty).Trim().ToLowerInvariant(); + if (lowered.Length < MinSearchQueryLength) + { + return Array.Empty(); + } + + return await _store.SearchCardsAsync(lowered, SearchLimit, ct); + } + +} diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs index cf96e17..c287415 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.ColumnRules; using Deal.Modules.Kanban.Application.Models; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/AiCardLearning.cs b/src/core/Deal.Modules.Pipeline/Application/Services/AiCardLearning.cs index 64716b2..8adde08 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/AiCardLearning.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/AiCardLearning.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs b/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs index 7b87084..7615d72 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs b/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs index eba79e9..d828408 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs b/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs index f3c4fc0..179467c 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Cards.Application.Abstractions; using Deal.Modules.Cards.Application.Dtos; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs index f6adb37..7f91892 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; using Deal.Modules.Kanban.Application.Models; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs index 4c928dd..5f89b3b 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs index 0a20228..331eaac 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs index 28561c7..68b7f4d 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs index 95e8099..00cfb45 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs index abab740..5f50973 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Settings.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Settings.cs index 069f25b..dcc1034 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Settings.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Settings.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.State.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.State.cs index b2ef58a..b7e9526 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.State.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.State.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.cs index d921c00..b132c87 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.cs @@ -1,6 +1,6 @@ using System.Globalization; using System.Text.Json; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; diff --git a/src/core/Deal.Modules.Telegram/Application/DialogsService.cs b/src/core/Deal.Modules.Telegram/Application/DialogsService.cs index 1d412da..8201fef 100644 --- a/src/core/Deal.Modules.Telegram/Application/DialogsService.cs +++ b/src/core/Deal.Modules.Telegram/Application/DialogsService.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; diff --git a/src/core/Deal.Modules.Telegram/Application/TelegramModuleRegistrar.cs b/src/core/Deal.Modules.Telegram/Application/TelegramModuleRegistrar.cs index 6470b1a..82ee8b8 100644 --- a/src/core/Deal.Modules.Telegram/Application/TelegramModuleRegistrar.cs +++ b/src/core/Deal.Modules.Telegram/Application/TelegramModuleRegistrar.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; using Deal.Modules.Settings.Application.Registrars; diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantProvisioner.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantProvisioner.cs index 135fe98..a363848 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantProvisioner.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantProvisioner.cs @@ -1,4 +1,5 @@ -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Modules.Tenants.Application.Extensions; using Deal.Modules.Tenants.Application.Models; using Deal.Modules.Tenants.Application.Registrars; diff --git a/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs b/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs index 92c3de9..5d4a4dd 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs @@ -1,5 +1,6 @@ using Deal.Modules.Tenants.Application.Models; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; using Deal.Modules.Tenants.Application.Registrars; diff --git a/src/core/Deal.SharedKernel/Tenants/ITenantContext.cs b/src/core/Deal.SharedKernel/Tenants/Abstractions/ITenantContext.cs similarity index 92% rename from src/core/Deal.SharedKernel/Tenants/ITenantContext.cs rename to src/core/Deal.SharedKernel/Tenants/Abstractions/ITenantContext.cs index 9d91c78..e228890 100644 --- a/src/core/Deal.SharedKernel/Tenants/ITenantContext.cs +++ b/src/core/Deal.SharedKernel/Tenants/Abstractions/ITenantContext.cs @@ -1,4 +1,6 @@ -namespace Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Models; + +namespace Deal.SharedKernel.Tenants.Abstractions; /// /// Контекст текущего тенанта запроса. diff --git a/src/core/Deal.SharedKernel/Tenants/TenantId.cs b/src/core/Deal.SharedKernel/Tenants/Models/TenantId.cs similarity index 96% rename from src/core/Deal.SharedKernel/Tenants/TenantId.cs rename to src/core/Deal.SharedKernel/Tenants/Models/TenantId.cs index d918582..03f5328 100644 --- a/src/core/Deal.SharedKernel/Tenants/TenantId.cs +++ b/src/core/Deal.SharedKernel/Tenants/Models/TenantId.cs @@ -1,4 +1,6 @@ -namespace Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; + +namespace Deal.SharedKernel.Tenants.Models; /// /// Идентификатор тенанта. Инвариант: ровно 32 hex-символа (Guid в формате "N" без дефисов) — значение diff --git a/src/core/tests/Deal.Tests.Unit/BudgetedAiClassifierTests.cs b/src/core/tests/Deal.Tests.Unit/BudgetedAiClassifierTests.cs index c26571e..54f6a6d 100644 --- a/src/core/tests/Deal.Tests.Unit/BudgetedAiClassifierTests.cs +++ b/src/core/tests/Deal.Tests.Unit/BudgetedAiClassifierTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Infrastructure.Integrations.Abstractions; @@ -17,7 +17,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/BudgetedAiToolsTests.cs b/src/core/tests/Deal.Tests.Unit/BudgetedAiToolsTests.cs index eb04b4e..bb60003 100644 --- a/src/core/tests/Deal.Tests.Unit/BudgetedAiToolsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/BudgetedAiToolsTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Infrastructure.Integrations.Abstractions; @@ -12,7 +12,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs b/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs index c88508e..94b3f11 100644 --- a/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Modules.Settings.Application.Abstractions; using Deal.Modules.Settings.Application.Models; diff --git a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs index 053e0c6..cd164e3 100644 --- a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs @@ -1,5 +1,5 @@ using Deal.Api.Hosting; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Modules.Discovery.Application.Abstractions; @@ -17,7 +17,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/core/tests/Deal.Tests.Unit/FailingTenantProvisioner.cs b/src/core/tests/Deal.Tests.Unit/FailingTenantProvisioner.cs index ae8045c..0163841 100644 --- a/src/core/tests/Deal.Tests.Unit/FailingTenantProvisioner.cs +++ b/src/core/tests/Deal.Tests.Unit/FailingTenantProvisioner.cs @@ -3,7 +3,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeAiClassifier.cs b/src/core/tests/Deal.Tests.Unit/FakeAiClassifier.cs index 15ea1ce..600a8d8 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeAiClassifier.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeAiClassifier.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs b/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs index 737b4a1..43b4e2d 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs index 921c87e..2810ae9 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs b/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs index 749668f..e515dd5 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs b/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs index 10a3202..5836d4f 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs b/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs index dcb60b4..204e621 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Grpc.Core; diff --git a/src/core/tests/Deal.Tests.Unit/FakeTenantProvisioner.cs b/src/core/tests/Deal.Tests.Unit/FakeTenantProvisioner.cs index f235d79..718d103 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTenantProvisioner.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTenantProvisioner.cs @@ -3,7 +3,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs index 66a1cb8..9220900 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; using System.Text.Json.Nodes; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ai; using Deal.Infrastructure.Data; @@ -24,7 +24,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs index 709d7e5..5dd1849 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ai; using Deal.Infrastructure.Data; @@ -17,7 +17,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/GrpcMlClientTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcMlClientTests.cs index 2589572..a0bea74 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcMlClientTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcMlClientTests.cs @@ -17,7 +17,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/GrpcTelegramClientTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcTelegramClientTests.cs index 1b35c7b..0a5bd2f 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcTelegramClientTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcTelegramClientTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Telegram; using Deal.Infrastructure.Data; @@ -8,7 +8,8 @@ using Deal.Infrastructure.Integrations.Extensions; using Deal.Infrastructure.Integrations.Models; using Deal.Infrastructure.Integrations.Options; using Deal.Infrastructure.Integrations.Services; -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs b/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs index 8e7896c..3aea81c 100644 --- a/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs +++ b/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure; using Deal.Infrastructure.Data; using Deal.Infrastructure.Integrations.Abstractions; @@ -26,7 +27,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/LocalAiClassifierTests.cs b/src/core/tests/Deal.Tests.Unit/LocalAiClassifierTests.cs index aa07f3f..cf94954 100644 --- a/src/core/tests/Deal.Tests.Unit/LocalAiClassifierTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LocalAiClassifierTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/tests/Deal.Tests.Unit/LocalAiToolsTests.cs b/src/core/tests/Deal.Tests.Unit/LocalAiToolsTests.cs index 9996a57..ac4cb90 100644 --- a/src/core/tests/Deal.Tests.Unit/LocalAiToolsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LocalAiToolsTests.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; using Deal.Infrastructure.Integrations.Extensions; diff --git a/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs b/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs index 20d5179..5d2087e 100644 --- a/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs @@ -1,5 +1,5 @@ using System.Globalization; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; diff --git a/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs b/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs index 8791ce5..9d1dcaf 100644 --- a/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs @@ -2,7 +2,9 @@ using System.Net; using System.Text; using System.Text.Json; using Deal.Api.Configuration; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Tenants.Application.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/LoginAttemptGuardTests.cs b/src/core/tests/Deal.Tests.Unit/LoginAttemptGuardTests.cs index 54a0cd9..76e4ec6 100644 --- a/src/core/tests/Deal.Tests.Unit/LoginAttemptGuardTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LoginAttemptGuardTests.cs @@ -1,5 +1,7 @@ using Deal.Api.Configuration; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs index e56d2bc..4a63f9e 100644 --- a/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs @@ -1,5 +1,6 @@ using Deal.Api.Hosting; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Infrastructure.Integrations.Abstractions; using Deal.Infrastructure.Integrations.Exceptions; @@ -21,7 +22,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs index f00f5c1..4a1db49 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs @@ -1,7 +1,9 @@ using System.Net; using System.Text; using System.Text.Json; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; using Deal.Modules.Tenants.Application.Models; diff --git a/src/core/tests/Deal.Tests.Unit/OperatorAuthHttpHost.cs b/src/core/tests/Deal.Tests.Unit/OperatorAuthHttpHost.cs index 88983eb..8ced108 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorAuthHttpHost.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorAuthHttpHost.cs @@ -1,7 +1,9 @@ using System.Net; using Deal.Api.Configuration; using Deal.Api.Endpoints; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Middleware; using Deal.Api.Telegram; using Deal.Infrastructure.Data; @@ -23,7 +25,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; diff --git a/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs index 4030d20..92720fb 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs @@ -1,4 +1,4 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Grpc.Ai; using Deal.Infrastructure.Data; @@ -27,7 +27,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.Logging.Abstractions; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs index c18596f..c91a3ac 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs @@ -3,7 +3,7 @@ using Deal.Api; using Deal.Api.Dtos; using Deal.Api.Services; using Deal.Api.Events; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Modules.Kanban.Application.Abstractions; @@ -25,7 +25,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/core/tests/Deal.Tests.Unit/RateLimitHttpTests.cs b/src/core/tests/Deal.Tests.Unit/RateLimitHttpTests.cs index 6e49ecf..e662f4e 100644 --- a/src/core/tests/Deal.Tests.Unit/RateLimitHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/RateLimitHttpTests.cs @@ -1,7 +1,9 @@ using System.Net; using System.Text.Json; using Deal.Api.Configuration; -using Deal.Api.Http; +using Deal.Api.Extensions; +using Deal.Api.Models; +using Deal.Api.Services; using Deal.Api.Middleware; using Deal.Modules.Tenants.Application.Abstractions; using Deal.Modules.Tenants.Application.Extensions; diff --git a/src/core/tests/Deal.Tests.Unit/RuntimeDepthsCollectorTests.cs b/src/core/tests/Deal.Tests.Unit/RuntimeDepthsCollectorTests.cs index 8d572f6..9def148 100644 --- a/src/core/tests/Deal.Tests.Unit/RuntimeDepthsCollectorTests.cs +++ b/src/core/tests/Deal.Tests.Unit/RuntimeDepthsCollectorTests.cs @@ -1,5 +1,6 @@ using Deal.Api.Observability; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; @@ -15,7 +16,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs index 0e1b434..15076f1 100644 --- a/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs @@ -1,7 +1,8 @@ using System.Text.Json; using Deal.Api.Events; using Deal.Api.Hosting; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Infrastructure.Data; using Deal.Modules.Kanban.Application.Abstractions; using Deal.Modules.Kanban.Application.Extensions; @@ -21,7 +22,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/core/tests/Deal.Tests.Unit/TelegramGatewayPortTests.cs b/src/core/tests/Deal.Tests.Unit/TelegramGatewayPortTests.cs index 2ee89c9..1908971 100644 --- a/src/core/tests/Deal.Tests.Unit/TelegramGatewayPortTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TelegramGatewayPortTests.cs @@ -1,4 +1,5 @@ -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/TelegramIngressTestHost.cs b/src/core/tests/Deal.Tests.Unit/TelegramIngressTestHost.cs index 9ad836a..a89f8d6 100644 --- a/src/core/tests/Deal.Tests.Unit/TelegramIngressTestHost.cs +++ b/src/core/tests/Deal.Tests.Unit/TelegramIngressTestHost.cs @@ -5,7 +5,8 @@ using Deal.Api.Services; using Deal.Api.Configuration; using Deal.Api.Events; using Deal.Api.Telegram; -using Deal.Contracts.Integrations; +using Deal.Contracts.Integrations.Abstractions; +using Deal.Contracts.Integrations.Models; using Deal.Grpc.Telegram; using Deal.Infrastructure.Data; using Deal.Modules.Pipeline.Application.Abstractions; @@ -22,7 +23,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Builder; diff --git a/src/core/tests/Deal.Tests.Unit/TenantIdTests.cs b/src/core/tests/Deal.Tests.Unit/TenantIdTests.cs index eb1d991..9ebca24 100644 --- a/src/core/tests/Deal.Tests.Unit/TenantIdTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TenantIdTests.cs @@ -1,4 +1,5 @@ -using Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; namespace Deal.Tests.Unit; diff --git a/src/core/tests/Deal.Tests.Unit/TokenUsageRecorderTests.cs b/src/core/tests/Deal.Tests.Unit/TokenUsageRecorderTests.cs index 92ca7f4..0edee9b 100644 --- a/src/core/tests/Deal.Tests.Unit/TokenUsageRecorderTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TokenUsageRecorderTests.cs @@ -16,7 +16,8 @@ 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 Deal.SharedKernel.Tenants; +using Deal.SharedKernel.Tenants.Abstractions; +using Deal.SharedKernel.Tenants.Models; namespace Deal.Tests.Unit; diff --git a/src/ml-service/Deal.Ml.Tests/LearningData.cs b/src/ml-service/Deal.Ml.Tests/LearningData.cs index 91a6c56..6eab0aa 100644 --- a/src/ml-service/Deal.Ml.Tests/LearningData.cs +++ b/src/ml-service/Deal.Ml.Tests/LearningData.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; namespace Deal.Ml.Tests; diff --git a/src/ml-service/Deal.Ml.Tests/MlTestHost.cs b/src/ml-service/Deal.Ml.Tests/MlTestHost.cs index 0b6a27f..dfbd1ef 100644 --- a/src/ml-service/Deal.Ml.Tests/MlTestHost.cs +++ b/src/ml-service/Deal.Ml.Tests/MlTestHost.cs @@ -1,6 +1,7 @@ using Deal.Grpc.Hosting; using Deal.Ml; using Deal.Ml.Model; +using Deal.Ml.Extensions; using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Builder; diff --git a/src/ml-service/Deal.Ml.Tests/MlTokenizerTests.cs b/src/ml-service/Deal.Ml.Tests/MlTokenizerTests.cs index aa089b3..01a5422 100644 --- a/src/ml-service/Deal.Ml.Tests/MlTokenizerTests.cs +++ b/src/ml-service/Deal.Ml.Tests/MlTokenizerTests.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; namespace Deal.Ml.Tests; diff --git a/src/ml-service/Deal.Ml.Tests/ModelPersistenceTests.cs b/src/ml-service/Deal.Ml.Tests/ModelPersistenceTests.cs index f56d586..8175786 100644 --- a/src/ml-service/Deal.Ml.Tests/ModelPersistenceTests.cs +++ b/src/ml-service/Deal.Ml.Tests/ModelPersistenceTests.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; using Microsoft.Data.Sqlite; namespace Deal.Ml.Tests; diff --git a/src/ml-service/Deal.Ml.Tests/ModelPoolTests.cs b/src/ml-service/Deal.Ml.Tests/ModelPoolTests.cs index 4d74fab..e224906 100644 --- a/src/ml-service/Deal.Ml.Tests/ModelPoolTests.cs +++ b/src/ml-service/Deal.Ml.Tests/ModelPoolTests.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; namespace Deal.Ml.Tests; diff --git a/src/ml-service/Deal.Ml.Tests/TenantModelLearningTests.cs b/src/ml-service/Deal.Ml.Tests/TenantModelLearningTests.cs index fafa0eb..e410705 100644 --- a/src/ml-service/Deal.Ml.Tests/TenantModelLearningTests.cs +++ b/src/ml-service/Deal.Ml.Tests/TenantModelLearningTests.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; namespace Deal.Ml.Tests; diff --git a/src/ml-service/Deal.Ml/Model/LabelExtensions.cs b/src/ml-service/Deal.Ml/Extensions/LabelExtensions.cs similarity index 86% rename from src/ml-service/Deal.Ml/Model/LabelExtensions.cs rename to src/ml-service/Deal.Ml/Extensions/LabelExtensions.cs index 04a0383..ccc9775 100644 --- a/src/ml-service/Deal.Ml/Model/LabelExtensions.cs +++ b/src/ml-service/Deal.Ml/Extensions/LabelExtensions.cs @@ -1,4 +1,7 @@ -namespace Deal.Ml.Model; +using Deal.Ml.Model; +using Deal.Ml.Extensions; + +namespace Deal.Ml.Extensions; /// /// Расширения для меток классов ML-модели. diff --git a/src/ml-service/Deal.Ml/MlServiceHost.cs b/src/ml-service/Deal.Ml/MlServiceHost.cs index 6f71732..08adb10 100644 --- a/src/ml-service/Deal.Ml/MlServiceHost.cs +++ b/src/ml-service/Deal.Ml/MlServiceHost.cs @@ -1,5 +1,6 @@ using Deal.Grpc.Hosting; using Deal.Ml.Model; +using Deal.Ml.Extensions; namespace Deal.Ml; diff --git a/src/ml-service/Deal.Ml/MlServiceImpl.cs b/src/ml-service/Deal.Ml/MlServiceImpl.cs index b1b10ae..79cf39d 100644 --- a/src/ml-service/Deal.Ml/MlServiceImpl.cs +++ b/src/ml-service/Deal.Ml/MlServiceImpl.cs @@ -1,5 +1,6 @@ using Deal.Grpc.Ml; using Deal.Ml.Model; +using Deal.Ml.Extensions; using Grpc.Core; namespace Deal.Ml; diff --git a/src/ml-service/Deal.Ml/Model/EvalEntry.cs b/src/ml-service/Deal.Ml/Model/EvalEntry.cs index 7333044..587c85e 100644 --- a/src/ml-service/Deal.Ml/Model/EvalEntry.cs +++ b/src/ml-service/Deal.Ml/Model/EvalEntry.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/LearnItem.cs b/src/ml-service/Deal.Ml/Model/LearnItem.cs index 20dfbd1..d4995b2 100644 --- a/src/ml-service/Deal.Ml/Model/LearnItem.cs +++ b/src/ml-service/Deal.Ml/Model/LearnItem.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/MlEvalInfo.cs b/src/ml-service/Deal.Ml/Model/MlEvalInfo.cs index 48546b0..baffcad 100644 --- a/src/ml-service/Deal.Ml/Model/MlEvalInfo.cs +++ b/src/ml-service/Deal.Ml/Model/MlEvalInfo.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/MlOptions.cs b/src/ml-service/Deal.Ml/Model/MlOptions.cs index 1661486..3166611 100644 --- a/src/ml-service/Deal.Ml/Model/MlOptions.cs +++ b/src/ml-service/Deal.Ml/Model/MlOptions.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/MlPredictResult.cs b/src/ml-service/Deal.Ml/Model/MlPredictResult.cs index 7bda992..4f216e1 100644 --- a/src/ml-service/Deal.Ml/Model/MlPredictResult.cs +++ b/src/ml-service/Deal.Ml/Model/MlPredictResult.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/MlStatusResult.cs b/src/ml-service/Deal.Ml/Model/MlStatusResult.cs index 410386e..62388fa 100644 --- a/src/ml-service/Deal.Ml/Model/MlStatusResult.cs +++ b/src/ml-service/Deal.Ml/Model/MlStatusResult.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/MlTypeDecision.cs b/src/ml-service/Deal.Ml/Model/MlTypeDecision.cs index d3844c3..c49580b 100644 --- a/src/ml-service/Deal.Ml/Model/MlTypeDecision.cs +++ b/src/ml-service/Deal.Ml/Model/MlTypeDecision.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/ModelConstants.cs b/src/ml-service/Deal.Ml/Model/ModelConstants.cs index c601191..eb0938e 100644 --- a/src/ml-service/Deal.Ml/Model/ModelConstants.cs +++ b/src/ml-service/Deal.Ml/Model/ModelConstants.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/ModelState.cs b/src/ml-service/Deal.Ml/Model/ModelState.cs index 264b0bf..302a3f8 100644 --- a/src/ml-service/Deal.Ml/Model/ModelState.cs +++ b/src/ml-service/Deal.Ml/Model/ModelState.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs b/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs index b0b67a0..924c408 100644 --- a/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs +++ b/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs @@ -1,3 +1,5 @@ +using Deal.Ml.Extensions; + namespace Deal.Ml.Model; /// diff --git a/src/ml-service/Deal.Ml/Storage/MlDb.cs b/src/ml-service/Deal.Ml/Storage/MlDb.cs index c50490e..8bd4cfa 100644 --- a/src/ml-service/Deal.Ml/Storage/MlDb.cs +++ b/src/ml-service/Deal.Ml/Storage/MlDb.cs @@ -1,4 +1,5 @@ using Deal.Ml.Model; +using Deal.Ml.Extensions; using Microsoft.Data.Sqlite; namespace Deal.Ml.Storage; diff --git a/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs b/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs index b94db0e..b5aaf07 100644 --- a/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs @@ -1,6 +1,7 @@ using Deal.Telegram.Dialogs; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/src/telegram-service/Deal.Telegram.Tests/DialogRpcTests.cs b/src/telegram-service/Deal.Telegram.Tests/DialogRpcTests.cs index bb93913..e35b069 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DialogRpcTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DialogRpcTests.cs @@ -1,6 +1,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Dialogs; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Builder; diff --git a/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs b/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs index 5e92989..84179f5 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs @@ -1,6 +1,7 @@ using Deal.Telegram.Discovery; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/src/telegram-service/Deal.Telegram.Tests/DiscoveryProtoMapperTests.cs b/src/telegram-service/Deal.Telegram.Tests/DiscoveryProtoMapperTests.cs index 347f801..2d90db5 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DiscoveryProtoMapperTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DiscoveryProtoMapperTests.cs @@ -2,6 +2,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Discovery; using Deal.Telegram.Dialogs; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Xunit; namespace Deal.Telegram.Tests; diff --git a/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs b/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs index a4bdd68..6ce6eff 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs @@ -1,6 +1,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Dialogs; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Grpc.Net.Client; using Microsoft.AspNetCore.Builder; diff --git a/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs b/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs index 282a373..bb0e59e 100644 --- a/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs +++ b/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs @@ -1,5 +1,6 @@ using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Tests; diff --git a/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs b/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs index 9fe6915..0960f4d 100644 --- a/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs @@ -2,6 +2,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Dialogs; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs b/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs index 25bc56f..065bb5b 100644 --- a/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs @@ -1,6 +1,7 @@ using Deal.Telegram.Dialogs; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/src/telegram-service/Deal.Telegram.Tests/TelegramSessionRpcTests.cs b/src/telegram-service/Deal.Telegram.Tests/TelegramSessionRpcTests.cs index 0a002c2..d546163 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TelegramSessionRpcTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TelegramSessionRpcTests.cs @@ -1,5 +1,6 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.DependencyInjection; diff --git a/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs b/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs index 0da9617..41788ad 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs @@ -1,4 +1,5 @@ using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using TL; using Xunit; diff --git a/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs b/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs index a33fc2f..f7c7b93 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs @@ -3,6 +3,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Core; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Dialogs; diff --git a/src/telegram-service/Deal.Telegram/Dialogs/DialogProtoMapper.cs b/src/telegram-service/Deal.Telegram/Dialogs/DialogProtoMapper.cs index 014e39a..1c408e7 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/DialogProtoMapper.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/DialogProtoMapper.cs @@ -1,6 +1,7 @@ using System.Globalization; using Deal.Grpc.Telegram; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Dialogs; diff --git a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeListener.cs b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeListener.cs index d54aad7..7fb2221 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeListener.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeListener.cs @@ -2,6 +2,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Core; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Dialogs; diff --git a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs index 883a14a..aae1679 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs @@ -2,6 +2,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Core; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Dialogs; diff --git a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs index a482b71..9483216 100644 --- a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs +++ b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs @@ -1,6 +1,7 @@ using Deal.Telegram.Dialogs; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; namespace Deal.Telegram.Discovery; diff --git a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryProtoMapper.cs b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryProtoMapper.cs index 231229a..af6d6d7 100644 --- a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryProtoMapper.cs +++ b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryProtoMapper.cs @@ -1,6 +1,7 @@ using Deal.Grpc.Telegram; using Deal.Telegram.Dialogs; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Discovery; diff --git a/src/telegram-service/Deal.Telegram/Telegram/ExceptionExtensions.cs b/src/telegram-service/Deal.Telegram/Extensions/ExceptionExtensions.cs similarity index 87% rename from src/telegram-service/Deal.Telegram/Telegram/ExceptionExtensions.cs rename to src/telegram-service/Deal.Telegram/Extensions/ExceptionExtensions.cs index 3f7fba9..8a89b44 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ExceptionExtensions.cs +++ b/src/telegram-service/Deal.Telegram/Extensions/ExceptionExtensions.cs @@ -1,8 +1,10 @@ using System.IO; using System.Net.Http; using Grpc.Core; +using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; -namespace Deal.Telegram.Telegram; +namespace Deal.Telegram.Extensions; /// /// Расширения для классификации сбоев telegram-service. diff --git a/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs b/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs index c2586e3..a17aaa4 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using Grpc.Core; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Sessions; diff --git a/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs b/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs index 5fc5337..455d934 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs @@ -1,5 +1,6 @@ using Grpc.Core; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram.Sessions; diff --git a/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs b/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs index dcf278c..cf7311f 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/DialogKinds.cs b/src/telegram-service/Deal.Telegram/Telegram/DialogKinds.cs index 8e6cec3..ef05bf3 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/DialogKinds.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/DialogKinds.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs index 848a413..ae07109 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs index 4f024be..ff15945 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs b/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs index f7be887..6af5cde 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs b/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs index 68d9d37..7c94c8f 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs index 410bcb5..0635179 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs index 2c56da3..c76170e 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs index 9d8df17..c36d481 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs @@ -1,3 +1,5 @@ +using Deal.Telegram.Extensions; + namespace Deal.Telegram.Telegram; /// diff --git a/src/telegram-service/Deal.Telegram/TelegramServiceHost.cs b/src/telegram-service/Deal.Telegram/TelegramServiceHost.cs index 6eb0647..fef7e5f 100644 --- a/src/telegram-service/Deal.Telegram/TelegramServiceHost.cs +++ b/src/telegram-service/Deal.Telegram/TelegramServiceHost.cs @@ -5,6 +5,7 @@ using Deal.Telegram.Discovery; using Deal.Telegram.Hosting; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; namespace Deal.Telegram; diff --git a/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs b/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs index e91f33d..8f31cdf 100644 --- a/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs +++ b/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs @@ -5,6 +5,7 @@ using Deal.Telegram.Dialogs; using Deal.Telegram.Discovery; using Deal.Telegram.Sessions; using Deal.Telegram.Telegram; +using Deal.Telegram.Extensions; using Grpc.Core; namespace Deal.Telegram;