Отформатировать списки параметров по код-стайлу
Больше двух параметров — каждый на отдельной строке (закрывающая скобка в конце последнего); два и меньше — в одну строку. Правило добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам в 253 файлах.
This commit is contained in:
@@ -89,7 +89,10 @@ public sealed class MtlsCertificates
|
||||
/// <param name="certificate">Клиентский сертификат из рукопожатия (null — RequireCertificate не выполнен).</param>
|
||||
/// <param name="chain">Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA).</param>
|
||||
/// <param name="sslPolicyErrors">Ошибки стандартной проверки TLS.</param>
|
||||
public bool ValidateClientCertificate(X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
public bool ValidateClientCertificate(
|
||||
X509Certificate2? certificate,
|
||||
X509Chain? chain,
|
||||
SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (certificate is null)
|
||||
{
|
||||
@@ -132,7 +135,11 @@ public sealed class MtlsCertificates
|
||||
// certificate: Сертификат сервера из рукопожатия.
|
||||
// chain: Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA).
|
||||
// sslPolicyErrors: Ошибки стандартной проверки TLS.
|
||||
private bool ValidateServerCertificate(object? sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
|
||||
private bool ValidateServerCertificate(
|
||||
object? sender,
|
||||
X509Certificate? certificate,
|
||||
X509Chain? chain,
|
||||
SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (certificate is null)
|
||||
{
|
||||
@@ -222,7 +229,10 @@ public sealed class MtlsCertificates
|
||||
// configuredPath: Путь из env.
|
||||
// envKey: Env-ключ пути (для сообщения об ошибке).
|
||||
// role: Роль сертификата (для сообщения об ошибке).
|
||||
private static string RequireExistingFile(string configuredPath, string envKey, string role)
|
||||
private static string RequireExistingFile(
|
||||
string configuredPath,
|
||||
string envKey,
|
||||
string role)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuredPath))
|
||||
{
|
||||
|
||||
@@ -179,7 +179,11 @@ public sealed class AiConnectionChecker : IAiConnectionChecker
|
||||
// ok: Результат подключения.
|
||||
// message: Сообщение ветки.
|
||||
// Возвращает: DTO ответа (наружу — camelCase).
|
||||
private static AiCheckResultDto BuildResult(AiCheckRequest request, string name, bool ok, string message)
|
||||
private static AiCheckResultDto BuildResult(
|
||||
AiCheckRequest request,
|
||||
string name,
|
||||
bool ok,
|
||||
string message)
|
||||
{
|
||||
return new AiCheckResultDto(
|
||||
Ok: ok,
|
||||
@@ -216,7 +220,10 @@ public sealed class AiConnectionChecker : IAiConnectionChecker
|
||||
// apiStyle: Стиль API провайдера (null — OpenAI-совместимый).
|
||||
// modelsUri: URL списка моделей (валиден только при возврате true).
|
||||
// Возвращает: True — URL построен; False — base URL не абсолютный http(s) (SSRF-гейт).
|
||||
private static bool TryBuildModelsUri(string baseUrl, string? apiStyle, [NotNullWhen(true)] out Uri? modelsUri)
|
||||
private static bool TryBuildModelsUri(
|
||||
string baseUrl,
|
||||
string? apiStyle,
|
||||
[NotNullWhen(true)] out Uri? modelsUri)
|
||||
{
|
||||
modelsUri = null;
|
||||
if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out Uri? parsed)
|
||||
@@ -241,7 +248,10 @@ public sealed class AiConnectionChecker : IAiConnectionChecker
|
||||
// httpRequest: Запрос списка моделей.
|
||||
// apiKey: Ключ открытым текстом (непустой — ветка ключа пройдена).
|
||||
// apiStyle: Стиль API провайдера.
|
||||
private static void AddAuthHeaders(HttpRequestMessage httpRequest, string apiKey, string? apiStyle)
|
||||
private static void AddAuthHeaders(
|
||||
HttpRequestMessage httpRequest,
|
||||
string apiKey,
|
||||
string? apiStyle)
|
||||
{
|
||||
if (apiStyle == AnthropicApiStyle)
|
||||
{
|
||||
|
||||
@@ -98,7 +98,10 @@ public sealed class BudgetedAiTools : IAiTools
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<AiEvaluateFitResultDto> EvaluateFitAsync(
|
||||
string text, string description, IReadOnlyCollection<string> keywords, CancellationToken ct)
|
||||
string text,
|
||||
string description,
|
||||
IReadOnlyCollection<string> keywords,
|
||||
CancellationToken ct)
|
||||
{
|
||||
BudgetStateDto state = await GateStateAsync(ct);
|
||||
if (state.Allowed)
|
||||
|
||||
@@ -128,7 +128,10 @@ public sealed class GrpcAiTools : IAiTools
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<AiEvaluateFitResultDto> EvaluateFitAsync(
|
||||
string text, string description, IReadOnlyCollection<string> keywords, CancellationToken ct)
|
||||
string text,
|
||||
string description,
|
||||
IReadOnlyCollection<string> keywords,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
|
||||
@@ -210,7 +210,11 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task PushAsync(string text, string label, double delta, CancellationToken ct)
|
||||
public async Task PushAsync(
|
||||
string text,
|
||||
string label,
|
||||
double delta,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Обучение гарантированно и локально (Ruling 6): сигнал всегда пишется в MlOutbox, отправку батчами
|
||||
// делает MlOutboxFlushScheduler — и в Local-, и в gRPC-режиме (ml_client.py L6–7).
|
||||
@@ -332,7 +336,10 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
|
||||
// deadline: Лимит времени вызова.
|
||||
// ct: Токен отмены вызова.
|
||||
// Возвращает: Опции вызова с заголовками, deadline и отменой.
|
||||
private CallOptions CallOptions(string tenantId, TimeSpan deadline, CancellationToken ct)
|
||||
private CallOptions CallOptions(
|
||||
string tenantId,
|
||||
TimeSpan deadline,
|
||||
CancellationToken ct)
|
||||
=> new(
|
||||
headers: _connection.CreateMetadata(tenantId),
|
||||
deadline: DateTime.UtcNow.Add(deadline),
|
||||
|
||||
@@ -102,7 +102,11 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TelegramAuthResultDto> StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct)
|
||||
public async Task<TelegramAuthResultDto> StartPhoneAsync(
|
||||
string phone,
|
||||
int apiId,
|
||||
string apiHash,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -121,7 +125,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TelegramAuthResultDto> StartQrAsync(int apiId, string apiHash, CancellationToken ct)
|
||||
public async Task<TelegramAuthResultDto> StartQrAsync(
|
||||
int apiId,
|
||||
string apiHash,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -211,7 +218,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct)
|
||||
public async Task SetMonitorAsync(
|
||||
string dialogId,
|
||||
bool enabled,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -245,7 +255,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> BackfillAsync(string dialogId, bool force, CancellationToken ct)
|
||||
public async Task<int> BackfillAsync(
|
||||
string dialogId,
|
||||
bool force,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -263,7 +276,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TelegramRecentMessageDto>> ReadRecentAsync(string dialogId, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<TelegramRecentMessageDto>> ReadRecentAsync(
|
||||
string dialogId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -283,7 +299,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(string query, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(
|
||||
string query,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -327,7 +346,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TelegramEvalReadDto> ReadForEvalAsync(string dialogId, int limit, CancellationToken ct)
|
||||
public async Task<TelegramEvalReadDto> ReadForEvalAsync(
|
||||
string dialogId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantId tenantId = RequireTenant();
|
||||
try
|
||||
@@ -404,7 +426,9 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
// call: Вызов клиента (принимает клиент и CallOptions).
|
||||
// Возвращает: Ответ RPC.
|
||||
private async Task<TReply> CallAsync<TReply>(
|
||||
TenantId tenantId, TimeSpan deadline, CancellationToken ct,
|
||||
TenantId tenantId,
|
||||
TimeSpan deadline,
|
||||
CancellationToken ct,
|
||||
Func<TelegramService.TelegramServiceClient, CallOptions, AsyncUnaryCall<TReply>> call)
|
||||
where TReply : class
|
||||
{
|
||||
@@ -424,7 +448,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway
|
||||
// tenantId: Id тенанта (лог).
|
||||
// operation: Имя RPC (лог-аудит).
|
||||
// Возвращает: Исключение для проброса: транспортный сбой — нормализованный RpcException.
|
||||
private Exception TranslateTransportFailure(Exception exception, TenantId tenantId, string operation)
|
||||
private Exception TranslateTransportFailure(
|
||||
Exception exception,
|
||||
TenantId tenantId,
|
||||
string operation)
|
||||
{
|
||||
// Отмена по токену вызывающего — не ошибка сервиса (пробрасываем как обычно).
|
||||
if (exception is OperationCanceledException)
|
||||
|
||||
@@ -31,6 +31,9 @@ public sealed class LocalAiTools : IAiTools
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<AiEvaluateFitResultDto> EvaluateFitAsync(
|
||||
string text, string description, IReadOnlyCollection<string> keywords, CancellationToken ct)
|
||||
string text,
|
||||
string description,
|
||||
IReadOnlyCollection<string> keywords,
|
||||
CancellationToken ct)
|
||||
=> throw new NotSupportedException(NotSupportedMessage);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,11 @@ public sealed class LocalMlClient(ISettingsStore store, IMlLearningStore learnin
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task PushAsync(string text, string label, double delta, CancellationToken ct)
|
||||
public async Task PushAsync(
|
||||
string text,
|
||||
string label,
|
||||
double delta,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Обучение гарантированно и локально (ml_client.push L40–49): действие пользователя — строка
|
||||
// очереди MlOutbox (отправку в ML-сервис делает воркер этапа 6). Общая логика (trim text/label,
|
||||
|
||||
@@ -34,11 +34,18 @@ public sealed class LocalTelegramGateway : ITelegramGateway
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TelegramAuthResultDto> StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct)
|
||||
public Task<TelegramAuthResultDto> StartPhoneAsync(
|
||||
string phone,
|
||||
int apiId,
|
||||
string apiHash,
|
||||
CancellationToken ct)
|
||||
=> Task.FromResult(new TelegramAuthResultDto(IdlePhase, null));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TelegramAuthResultDto> StartQrAsync(int apiId, string apiHash, CancellationToken ct)
|
||||
public Task<TelegramAuthResultDto> StartQrAsync(
|
||||
int apiId,
|
||||
string apiHash,
|
||||
CancellationToken ct)
|
||||
=> Task.FromResult(new TelegramAuthResultDto(IdlePhase, null));
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -55,20 +62,32 @@ public sealed class LocalTelegramGateway : ITelegramGateway
|
||||
=> Task.FromResult<IReadOnlyList<TelegramDialogEntryDto>>([]);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) => Task.CompletedTask;
|
||||
public Task SetMonitorAsync(
|
||||
string dialogId,
|
||||
bool enabled,
|
||||
CancellationToken ct) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetMonitorAllAsync(bool enabled, CancellationToken ct) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> BackfillAsync(string dialogId, bool force, CancellationToken ct) => Task.FromResult(0);
|
||||
public Task<int> BackfillAsync(
|
||||
string dialogId,
|
||||
bool force,
|
||||
CancellationToken ct) => Task.FromResult(0);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<TelegramRecentMessageDto>> ReadRecentAsync(string dialogId, int limit, CancellationToken ct)
|
||||
public Task<IReadOnlyList<TelegramRecentMessageDto>> ReadRecentAsync(
|
||||
string dialogId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
=> Task.FromResult<IReadOnlyList<TelegramRecentMessageDto>>([]);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(string query, int limit, CancellationToken ct)
|
||||
public Task<IReadOnlyList<TelegramDialogEntryDto>> SearchAsync(
|
||||
string query,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
=> Task.FromResult<IReadOnlyList<TelegramDialogEntryDto>>([]);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -76,7 +95,10 @@ public sealed class LocalTelegramGateway : ITelegramGateway
|
||||
=> Task.FromResult(new TelegramChannelInfoDto(dialogId, string.Empty, string.Empty, string.Empty, SourceDefaults.DefaultHue, null, false));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TelegramEvalReadDto> ReadForEvalAsync(string dialogId, int limit, CancellationToken ct)
|
||||
public Task<TelegramEvalReadDto> ReadForEvalAsync(
|
||||
string dialogId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
=> Task.FromResult(new TelegramEvalReadDto(false, "no_history", []));
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -35,7 +35,12 @@ internal static class MlOutboxQueue
|
||||
/// <param name="delta">Вес сигнала (1.0 — учить, −1.0 — снять метку).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
/// <returns>Задача завершается после записи строки (отправку делает фоновый флашер).</returns>
|
||||
public static async Task PushAsync(IMlLearningStore learningStore, string text, string label, double delta, CancellationToken ct)
|
||||
public static async Task PushAsync(
|
||||
IMlLearningStore learningStore,
|
||||
string text,
|
||||
string label,
|
||||
double delta,
|
||||
CancellationToken ct)
|
||||
{
|
||||
string trimmedText = (text ?? string.Empty).Trim();
|
||||
string trimmedLabel = (label ?? string.Empty).Trim();
|
||||
|
||||
@@ -91,7 +91,10 @@ public sealed class MlStatusCache
|
||||
/// <param name="tenantId">Id тенанта (формат N).</param>
|
||||
/// <param name="service">Статус модели.</param>
|
||||
/// <param name="reachable">Доступность сервиса.</param>
|
||||
public void Set(string tenantId, MlServiceStatusDto service, bool reachable)
|
||||
public void Set(
|
||||
string tenantId,
|
||||
MlServiceStatusDto service,
|
||||
bool reachable)
|
||||
{
|
||||
_entries[tenantId] = new Snapshot(service, reachable, _utcNow().ToUnixTimeMilliseconds());
|
||||
}
|
||||
|
||||
@@ -89,7 +89,11 @@ public sealed class TokenUsageRecorder
|
||||
/// <param name="provider">Id активного провайдера (deepseek/openai/anthropic/…; событие истории).</param>
|
||||
/// <param name="model">Модель провайдера (событие истории).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
public async Task AddAsync(Usage? usage, string provider, string model, CancellationToken ct)
|
||||
public async Task AddAsync(
|
||||
Usage? usage,
|
||||
string provider,
|
||||
string model,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (usage is null)
|
||||
{
|
||||
@@ -124,7 +128,11 @@ public sealed class TokenUsageRecorder
|
||||
/// <param name="model">Модель/вид локального ML-вызова (событие истории).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
/// <returns>Оценка токенов (для тестов/наблюдаемости).</returns>
|
||||
public async Task<long> AddEstimatedAsync(string? text, string provider, string model, CancellationToken ct)
|
||||
public async Task<long> AddEstimatedAsync(
|
||||
string? text,
|
||||
string provider,
|
||||
string model,
|
||||
CancellationToken ct)
|
||||
{
|
||||
long promptTokens = EstimateTokens(text);
|
||||
// Прикладная метрика (этап 12, пакет A): вызов локального ML + оценка токенов (та же точка,
|
||||
|
||||
@@ -47,7 +47,11 @@ public sealed class LocalFileStorage : IFileStorage
|
||||
public override string ToString() => $"LocalFileStorage (root: {_rootPath})";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct)
|
||||
public async Task<string> PutAsync(
|
||||
string objectKey,
|
||||
Stream content,
|
||||
string contentType,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
|
||||
|
||||
@@ -83,7 +83,11 @@ public sealed class MinioFileStorage : IFileStorage
|
||||
public override string ToString() => $"MinioFileStorage (endpoint: {_endpoint}; bucket: {_bucket})";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct)
|
||||
public async Task<string> PutAsync(
|
||||
string objectKey,
|
||||
Stream content,
|
||||
string contentType,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
|
||||
|
||||
@@ -105,7 +105,10 @@ public sealed class AuthStore(DealDbContext dbContext) : IAuthStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdatePasswordHashAsync(Guid userId, string passwordHash, CancellationToken ct)
|
||||
public async Task UpdatePasswordHashAsync(
|
||||
Guid userId,
|
||||
string passwordHash,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await dbContext.Users
|
||||
.Where(u => u.Id == userId)
|
||||
|
||||
@@ -18,7 +18,11 @@ namespace Deal.Infrastructure.Persistence.Repositories;
|
||||
public sealed partial class DiscoveryStore
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task UpsertBlacklistAsync(string dialogId, string name, string reason, CancellationToken ct)
|
||||
public async Task UpsertBlacklistAsync(
|
||||
string dialogId,
|
||||
string name,
|
||||
string reason,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscBlacklistEntity? row = await _dbContext.DiscBlacklist
|
||||
.FirstOrDefaultAsync(entry => entry.DialogId == dialogId, ct);
|
||||
|
||||
@@ -19,7 +19,10 @@ namespace Deal.Infrastructure.Persistence.Repositories;
|
||||
public sealed partial class DiscoveryStore
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<DiscoveryCandidateDto>> ListCandidatesAsync(string taskId, string? status, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<DiscoveryCandidateDto>> ListCandidatesAsync(
|
||||
string taskId,
|
||||
string? status,
|
||||
CancellationToken ct)
|
||||
{
|
||||
IQueryable<DiscCandidateEntity> query = _dbContext.DiscCandidates.AsNoTracking().Where(candidate => candidate.TaskId == taskId);
|
||||
if (status is not null)
|
||||
@@ -78,7 +81,10 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> PatchCandidateAsync(string dialogId, DiscoveryCandidatePatch patch, CancellationToken ct)
|
||||
public async Task<bool> PatchCandidateAsync(
|
||||
string dialogId,
|
||||
DiscoveryCandidatePatch patch,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscCandidateEntity? row = await _dbContext.DiscCandidates
|
||||
.FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct);
|
||||
@@ -94,7 +100,10 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetCandidateStatusAsync(string dialogId, string status, CancellationToken ct)
|
||||
public async Task<bool> SetCandidateStatusAsync(
|
||||
string dialogId,
|
||||
string status,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscCandidateEntity? row = await _dbContext.DiscCandidates
|
||||
.FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct);
|
||||
@@ -110,7 +119,10 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetCandidateJoinedAsync(string dialogId, bool autoJoined, CancellationToken ct)
|
||||
public async Task<bool> SetCandidateJoinedAsync(
|
||||
string dialogId,
|
||||
bool autoJoined,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscCandidateEntity? row = await _dbContext.DiscCandidates
|
||||
.FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct);
|
||||
|
||||
@@ -18,7 +18,12 @@ namespace Deal.Infrastructure.Persistence.Repositories;
|
||||
public sealed partial class DiscoveryStore
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task AddLogAsync(string logId, string taskId, string logEvent, string text, CancellationToken ct)
|
||||
public async Task AddLogAsync(
|
||||
string logId,
|
||||
string taskId,
|
||||
string logEvent,
|
||||
string text,
|
||||
CancellationToken ct)
|
||||
{
|
||||
_dbContext.DiscLog.Add(new DiscLogEntity
|
||||
{
|
||||
@@ -32,14 +37,20 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> CountLogEventAsync(string logEvent, DateTimeOffset sinceUtc, CancellationToken ct)
|
||||
public async Task<int> CountLogEventAsync(
|
||||
string logEvent,
|
||||
DateTimeOffset sinceUtc,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// ban_guard.joins_today_auto L29–35: число событий лога по типу с начала UTC-суток (счётчик квоты).
|
||||
return await _dbContext.DiscLog.CountAsync(row => row.Event == logEvent && row.CreatedAt >= sinceUtc, ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<DiscoveryLogDto>> ListTaskLogAsync(string taskId, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<DiscoveryLogDto>> ListTaskLogAsync(
|
||||
string taskId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
List<DiscLogEntity> rows = await _dbContext.DiscLog
|
||||
.AsNoTracking()
|
||||
|
||||
@@ -61,7 +61,10 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> PatchTaskAsync(string taskId, DiscoveryTaskPatch patch, CancellationToken ct)
|
||||
public async Task<bool> PatchTaskAsync(
|
||||
string taskId,
|
||||
DiscoveryTaskPatch patch,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscTaskEntity? row = await _dbContext.DiscTasks
|
||||
.FirstOrDefaultAsync(task => task.Id == taskId, ct);
|
||||
@@ -95,7 +98,10 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> SetTaskRunningAsync(string taskId, bool resetProgress, CancellationToken ct)
|
||||
public async Task<bool> SetTaskRunningAsync(
|
||||
string taskId,
|
||||
bool resetProgress,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscTaskEntity? row = await _dbContext.DiscTasks
|
||||
.FirstOrDefaultAsync(task => task.Id == taskId, ct);
|
||||
@@ -155,7 +161,11 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> BumpTaskCounterAsync(string taskId, DiscoveryCounterField field, int n, CancellationToken ct)
|
||||
public async Task<bool> BumpTaskCounterAsync(
|
||||
string taskId,
|
||||
DiscoveryCounterField field,
|
||||
int n,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscTaskEntity? row = await _dbContext.DiscTasks
|
||||
.FirstOrDefaultAsync(task => task.Id == taskId, ct);
|
||||
@@ -189,7 +199,11 @@ public sealed partial class DiscoveryStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> AdvanceSearchAsync(string taskId, int nextIndex, bool searchDone, CancellationToken ct)
|
||||
public async Task<bool> AdvanceSearchAsync(
|
||||
string taskId,
|
||||
int nextIndex,
|
||||
bool searchDone,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DiscTaskEntity? row = await _dbContext.DiscTasks
|
||||
.FirstOrDefaultAsync(task => task.Id == taskId, ct);
|
||||
|
||||
@@ -29,7 +29,10 @@ public sealed class GlobalSettingsStore(DealDbContext dbContext) : IGlobalSettin
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetAsync(string key, string valueJson, CancellationToken ct)
|
||||
public async Task SetAsync(
|
||||
string key,
|
||||
string valueJson,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Upsert по ключу (PK): существующая строка обновляется, отсутствующая — добавляется.
|
||||
GlobalSettingEntity? entity = await dbContext.GlobalSettings
|
||||
|
||||
@@ -57,7 +57,11 @@ public sealed class InviteStore(DealDbContext dbContext) : IInviteStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpdateStatusAsync(string code, string status, DateTimeOffset? activatedAt, CancellationToken ct)
|
||||
public async Task<bool> UpdateStatusAsync(
|
||||
string code,
|
||||
string status,
|
||||
DateTimeOffset? activatedAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var entity = await dbContext.Invites.SingleOrDefaultAsync(i => i.Code == code, ct);
|
||||
if (entity is null)
|
||||
@@ -72,7 +76,10 @@ public sealed class InviteStore(DealDbContext dbContext) : IInviteStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> TryActivateAsync(string code, DateTimeOffset activatedAt, CancellationToken ct)
|
||||
public async Task<bool> TryActivateAsync(
|
||||
string code,
|
||||
DateTimeOffset activatedAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// CAS (Task 6): атомарный условный UPDATE — pending → activated только если статус всё ещё pending,
|
||||
// иначе параллельный отзыв/активация не перезаписываются (ExecuteUpdate выполняется одним оператором SQL).
|
||||
|
||||
@@ -39,7 +39,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<CardDto>> SearchCardsAsync(string q, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<CardDto>> SearchCardsAsync(
|
||||
string q,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
string query = q.Trim();
|
||||
if (query.Length == 0)
|
||||
@@ -87,7 +90,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<CardDto?> GetCardBySourceAsync(string dialogId, long msgId, CancellationToken ct)
|
||||
public async Task<CardDto?> GetCardBySourceAsync(
|
||||
string dialogId,
|
||||
long msgId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dialogId))
|
||||
{
|
||||
@@ -192,7 +198,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateSeenAsync(string? cardId, string? col, CancellationToken ct)
|
||||
public async Task UpdateSeenAsync(
|
||||
string? cardId,
|
||||
string? col,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Три режима leads.py mark_seen L250–256: одна карточка | колонка | все карточки.
|
||||
IQueryable<CardEntity> queryable = _dbContext.Cards;
|
||||
|
||||
@@ -33,7 +33,12 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddCommentAsync(string commentId, string cardId, string by, string text, CancellationToken ct)
|
||||
public async Task AddCommentAsync(
|
||||
string commentId,
|
||||
string cardId,
|
||||
string by,
|
||||
string text,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// CreatedAt — UTC-now (leads.py add_comment L259–265). Целостность ссылки на карточку держит FK:
|
||||
// комментарий без карточки не запишется (404-семантику несуществующей карточки отдаёт сервис,
|
||||
|
||||
@@ -93,7 +93,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ReorderContainersAsync(string space, IReadOnlyList<string> containerIds, CancellationToken ct)
|
||||
public async Task ReorderContainersAsync(
|
||||
string space,
|
||||
IReadOnlyList<string> containerIds,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Позиции 0..N-1 — одна транзакция: при сбое середины порядок не остаётся частично проставленным.
|
||||
await using var transaction = await _dbContext.Database.BeginTransactionAsync(ct);
|
||||
|
||||
@@ -33,7 +33,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> PatchCardAsync(string cardId, CardPatch patch, CancellationToken ct)
|
||||
public async Task<bool> PatchCardAsync(
|
||||
string cardId,
|
||||
CardPatch patch,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Точечная правка по присутствующим полям патча: null-поле не меняется, JSON-поля заменяются
|
||||
// целиком, в конце — bump UpdatedAt (patch_card projects.py L159–187).
|
||||
@@ -71,7 +74,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> AddLinkAsync(string cardId, CardLinkDto link, CancellationToken ct)
|
||||
public async Task<bool> AddLinkAsync(
|
||||
string cardId,
|
||||
CardLinkDto link,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Один UPDATE — jsonb-append в конец массива links (add_link L133–143 + bump UpdatedAt).
|
||||
return await ExecuteJsonMutationAsync(
|
||||
@@ -86,7 +92,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> RemoveLinkAsync(string cardId, string linkId, CancellationToken ct)
|
||||
public async Task<bool> RemoveLinkAsync(
|
||||
string cardId,
|
||||
string linkId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Один UPDATE — jsonb-фильтрация массива по id элемента (remove_link L146–150 + bump UpdatedAt).
|
||||
return await ExecuteJsonMutationAsync(
|
||||
@@ -105,7 +114,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> AddFileAsync(string cardId, CardFileDto file, CancellationToken ct)
|
||||
public async Task<bool> AddFileAsync(
|
||||
string cardId,
|
||||
CardFileDto file,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Один UPDATE — jsonb-append в конец массива files (files.py add_file L57–75 + bump UpdatedAt).
|
||||
return await ExecuteJsonMutationAsync(
|
||||
@@ -120,7 +132,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> RemoveFileAsync(string cardId, string fileId, CancellationToken ct)
|
||||
public async Task<bool> RemoveFileAsync(
|
||||
string cardId,
|
||||
string fileId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Один UPDATE — jsonb-фильтрация массива files по id записи (files.py remove_file L86–94 + bump).
|
||||
return await ExecuteJsonMutationAsync(
|
||||
@@ -140,7 +155,11 @@ public sealed partial class KanbanStore
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> MoveCardStageAsync(
|
||||
string cardId, string containerId, CardHistoryDto historyEntry, long atMs, CancellationToken ct)
|
||||
string cardId,
|
||||
string containerId,
|
||||
CardHistoryDto historyEntry,
|
||||
long atMs,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Смена контейнера-стадии: Col=containerId, сброс напоминания, updated_at=atMs, история + запись
|
||||
// (move_stage projects.py L202–216; Ruling 7). Нетрекинговое чтение + ExecuteUpdate.
|
||||
@@ -169,7 +188,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetReminderAsync(string cardId, long atMs, CancellationToken ct)
|
||||
public async Task SetReminderAsync(
|
||||
string cardId,
|
||||
long atMs,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await _dbContext.Cards
|
||||
.Where(card => card.Id == cardId)
|
||||
|
||||
@@ -41,7 +41,10 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> ArchiveAsync(IReadOnlyList<string> cardIds, DateTimeOffset archivedAt, CancellationToken ct)
|
||||
public async Task<int> ArchiveAsync(
|
||||
IReadOnlyList<string> cardIds,
|
||||
DateTimeOffset archivedAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Автоархив тика пачкой (tick_storage L462–473): один UPDATE вместо N по-карточных — как по-карточный
|
||||
// UpdateColumnAsync автоархива: col=archive, is_new=false, archived_at=now, matchHits пусто;
|
||||
@@ -121,7 +124,12 @@ public sealed partial class KanbanStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateConversionAsync(string cardId, double? convFrom, double? convTo, string convCur, CancellationToken ct)
|
||||
public async Task UpdateConversionAsync(
|
||||
string cardId,
|
||||
double? convFrom,
|
||||
double? convTo,
|
||||
string convCur,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Пишутся только conv-поля карточки (Ruling 7); convCur пуст — конверсия снята.
|
||||
await _dbContext.Cards
|
||||
|
||||
@@ -29,7 +29,12 @@ public sealed class MlLearningStore(TenantDbContext dbContext) : IMlLearningStor
|
||||
public Task<int> CountOutboxAsync(CancellationToken ct) => dbContext.MlOutbox.CountAsync(ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddOutboxAsync(string id, string text, string label, double delta, CancellationToken ct)
|
||||
public async Task AddOutboxAsync(
|
||||
string id,
|
||||
string text,
|
||||
string label,
|
||||
double delta,
|
||||
CancellationToken ct)
|
||||
{
|
||||
dbContext.MlOutbox.Add(new MlOutboxEntity
|
||||
{
|
||||
|
||||
@@ -36,7 +36,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
// ── Очередь (QueueItems) ───────────────────────────────────────────────
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> ExistsDuplicateAsync(string dialogId, long? msgId, CancellationToken ct)
|
||||
public Task<bool> ExistsDuplicateAsync(
|
||||
string dialogId,
|
||||
long? msgId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Дубль-гвард приёма (Ruling 2, enqueue L70–77): msgId null — проверять нечего.
|
||||
return msgId is null
|
||||
@@ -52,7 +55,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<QueueItemDto>> ListAsync(string? status, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<QueueItemDto>> ListAsync(
|
||||
string? status,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
IQueryable<QueueItemEntity> queryable = dbContext.QueueItems.AsNoTracking();
|
||||
if (status is not null)
|
||||
@@ -72,7 +78,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
=> dbContext.QueueItems.CountAsync(item => item.Status == status, ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetStatusAsync(string id, string status, CancellationToken ct)
|
||||
public async Task SetStatusAsync(
|
||||
string id,
|
||||
string status,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Смена статуса строки очереди (new → filtered в воркере): UpdatedAt = UTC-now (порт).
|
||||
await dbContext.QueueItems
|
||||
@@ -131,7 +140,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<RejectedItemDto>> ListPageAsync(int offset, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<RejectedItemDto>> ListPageAsync(
|
||||
int offset,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
List<RejectedItemEntity> entities = await dbContext.RejectedItems
|
||||
.AsNoTracking()
|
||||
@@ -143,7 +155,11 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<RejectedItemDto>> SearchAsync(string q, int limitFts, int limitLike, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<RejectedItemDto>> SearchAsync(
|
||||
string q,
|
||||
int limitFts,
|
||||
int limitLike,
|
||||
CancellationToken ct)
|
||||
{
|
||||
string query = q.Trim();
|
||||
if (query.Length == 0)
|
||||
@@ -231,7 +247,11 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MarkReturnedAsync(string id, string reason, DateTimeOffset returnedAt, CancellationToken ct)
|
||||
public async Task MarkReturnedAsync(
|
||||
string id,
|
||||
string reason,
|
||||
DateTimeOffset returnedAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Возврат из отсева: аудит-поля записи, строка не удаляется (return_to_queue L156–159).
|
||||
await dbContext.RejectedItems
|
||||
@@ -277,7 +297,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LinkAsync(string hash, string cardId, CancellationToken ct)
|
||||
public async Task LinkAsync(
|
||||
string hash,
|
||||
string cardId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Связь заявки с созданной карточкой (Ruling 4, порядок AddCard → LinkDedup L512–513).
|
||||
await dbContext.DedupEntries
|
||||
|
||||
@@ -46,7 +46,11 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> IncrementAsync(
|
||||
string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct)
|
||||
string key,
|
||||
DateTimeOffset windowStart,
|
||||
DateTimeOffset windowEnd,
|
||||
int amount,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
@@ -94,7 +98,11 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC
|
||||
// ct: Токен отмены.
|
||||
// Возвращает: Значение счётчика после операции.
|
||||
private async Task<int> IncrementNpgsqlAsync(
|
||||
string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct)
|
||||
string key,
|
||||
DateTimeOffset windowStart,
|
||||
DateTimeOffset windowEnd,
|
||||
int amount,
|
||||
CancellationToken ct)
|
||||
{
|
||||
DbConnection connection = dbContext.Database.GetDbConnection();
|
||||
bool openedHere = connection.State != ConnectionState.Open;
|
||||
@@ -127,7 +135,10 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC
|
||||
// command: Команда upsert.
|
||||
// name: Имя параметра (с префиксом @).
|
||||
// value: Значение.
|
||||
private static void AddParameter(DbCommand command, string name, object value)
|
||||
private static void AddParameter(
|
||||
DbCommand command,
|
||||
string name,
|
||||
object value)
|
||||
{
|
||||
DbParameter parameter = command.CreateParameter();
|
||||
parameter.ParameterName = name;
|
||||
@@ -136,7 +147,10 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> GetCountAsync(string key, DateTimeOffset windowStart, CancellationToken ct)
|
||||
public async Task<int> GetCountAsync(
|
||||
string key,
|
||||
DateTimeOffset windowStart,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
|
||||
|
||||
@@ -41,7 +41,10 @@ public sealed class SettingsStore(TenantDbContext dbContext) : ISettingsStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetAsync(string key, string valueJson, CancellationToken ct)
|
||||
public async Task SetAsync(
|
||||
string key,
|
||||
string valueJson,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Upsert по ключу (PK): существующая строка обновляется, отсутствующая — добавляется.
|
||||
TenantSettingEntity? entity = await dbContext.Settings
|
||||
|
||||
@@ -22,7 +22,9 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<int> SyncFromTelegramAsync(
|
||||
IReadOnlyCollection<TelegramDialogEntryDto> entries, bool autoMonitorNew, CancellationToken ct)
|
||||
IReadOnlyCollection<TelegramDialogEntryDto> entries,
|
||||
bool autoMonitorNew,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
@@ -95,7 +97,10 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct)
|
||||
public Task SetMonitorAsync(
|
||||
string dialogId,
|
||||
bool enabled,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return dbContext.Dialogs
|
||||
.Where(dialog => dialog.Id == dialogId)
|
||||
@@ -148,7 +153,12 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpsertDiscoveredMonitoredAsync(
|
||||
string dialogId, string name, string handle, string kind, string hue, CancellationToken ct)
|
||||
string dialogId,
|
||||
string name,
|
||||
string handle,
|
||||
string kind,
|
||||
string hue,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// 1:1 python add_dialog_monitored L850–873 (INSERT/ON CONFLICT DO UPDATE): строка каталога после
|
||||
// discovery-вступления — метаданные источника, monitor=TRUE, backfilled=FALSE (разбор подхватит
|
||||
@@ -185,7 +195,11 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SavePreviewAsync(
|
||||
string messageId, string dialogId, string text, DateTimeOffset msgAt, CancellationToken ct)
|
||||
string messageId,
|
||||
string dialogId,
|
||||
string text,
|
||||
DateTimeOffset msgAt,
|
||||
CancellationToken ct)
|
||||
{
|
||||
bool exists = await dbContext.TgMessages.AnyAsync(message => message.Id == messageId, ct);
|
||||
if (exists)
|
||||
@@ -205,7 +219,11 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task TouchDialogLastAsync(string dialogId, string text, DateTimeOffset at, CancellationToken ct)
|
||||
public Task TouchDialogLastAsync(
|
||||
string dialogId,
|
||||
string text,
|
||||
DateTimeOffset at,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// python _on_message L270–273: last_text/last_at/updated_at одним моментом приёма.
|
||||
return dbContext.Dialogs
|
||||
@@ -219,7 +237,10 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TelegramMessageDto>> ListMessagesAsync(string dialogId, int limit, CancellationToken ct)
|
||||
public async Task<IReadOnlyList<TelegramMessageDto>> ListMessagesAsync(
|
||||
string dialogId,
|
||||
int limit,
|
||||
CancellationToken ct)
|
||||
{
|
||||
List<TgMessageEntity> rows = await dbContext.TgMessages
|
||||
.Where(message => message.DialogId == dialogId)
|
||||
|
||||
@@ -75,7 +75,9 @@ public sealed class TenantLimitStore : ITenantLimitStore
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TenantLimitDto> GetOrCreateAsync(
|
||||
Guid tenantId, CancellationToken ct, TokenLimitDefaults? defaults = null)
|
||||
Guid tenantId,
|
||||
CancellationToken ct,
|
||||
TokenLimitDefaults? defaults = null)
|
||||
{
|
||||
TenantLimitEntity entity = await LoadOrCreateAsync(tenantId, defaults ?? _defaults, ct);
|
||||
return ToLimitDto(entity);
|
||||
@@ -90,7 +92,10 @@ public sealed class TenantLimitStore : ITenantLimitStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<BudgetStateDto> AddUsageAsync(Guid tenantId, long tokens, CancellationToken ct)
|
||||
public async Task<BudgetStateDto> AddUsageAsync(
|
||||
Guid tenantId,
|
||||
long tokens,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantLimitEntity entity = await LoadOrCreateAsync(tenantId, _defaults, ct);
|
||||
await ResetIfPeriodExpiredAsync(entity, ct);
|
||||
@@ -120,7 +125,11 @@ public sealed class TenantLimitStore : ITenantLimitStore
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<BudgetStateDto> UpdateBudgetAsync(Guid tenantId, long budgetTokens, string period, CancellationToken ct)
|
||||
public async Task<BudgetStateDto> UpdateBudgetAsync(
|
||||
Guid tenantId,
|
||||
long budgetTokens,
|
||||
string period,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(budgetTokens);
|
||||
if (period != TenantLimitPeriods.Month && period != TenantLimitPeriods.Day)
|
||||
@@ -175,7 +184,10 @@ public sealed class TenantLimitStore : ITenantLimitStore
|
||||
// defaults: Дефолт-параметры создаваемой строки.
|
||||
// ct: Токен отмены.
|
||||
// Возвращает: Отслеживаемая строка лимита.
|
||||
private async Task<TenantLimitEntity> LoadOrCreateAsync(Guid tenantId, TokenLimitDefaults defaults, CancellationToken ct)
|
||||
private async Task<TenantLimitEntity> LoadOrCreateAsync(
|
||||
Guid tenantId,
|
||||
TokenLimitDefaults defaults,
|
||||
CancellationToken ct)
|
||||
{
|
||||
TenantLimitEntity? entity = await _dbContext.TenantLimits
|
||||
.SingleOrDefaultAsync(x => x.TenantId == tenantId, ct);
|
||||
|
||||
@@ -49,7 +49,10 @@ public sealed class TenantRepository(DealDbContext dbContext) : ITenantRepositor
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> UpdateStatusAsync(Guid id, string status, CancellationToken ct)
|
||||
public async Task<bool> UpdateStatusAsync(
|
||||
Guid id,
|
||||
string status,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Отслеживаемая запись + SaveChanges (не ExecuteUpdateAsync): операция редкая (операторская админка),
|
||||
// зато семантика проверяема на InMemory-провайдере в unit-тестах.
|
||||
|
||||
@@ -97,8 +97,7 @@ public sealed class TokenUsageEventStore(DealDbContext dbContext) : ITokenUsageE
|
||||
// source: Отфильтрованный запрос.
|
||||
// ct: Токен отмены.
|
||||
// Возвращает: Строки агрегатов по дням.
|
||||
private static async Task<IReadOnlyList<TokenUsageAggregateDto>> AggregateByDayAsync(
|
||||
IQueryable<TokenUsageEventEntity> source, CancellationToken ct)
|
||||
private static async Task<IReadOnlyList<TokenUsageAggregateDto>> AggregateByDayAsync(IQueryable<TokenUsageEventEntity> source, CancellationToken ct)
|
||||
{
|
||||
var rows = await source
|
||||
.GroupBy(e => new { e.At.Year, e.At.Month, e.At.Day })
|
||||
@@ -131,8 +130,7 @@ public sealed class TokenUsageEventStore(DealDbContext dbContext) : ITokenUsageE
|
||||
// source: Отфильтрованный запрос.
|
||||
// ct: Токен отмены.
|
||||
// Возвращает: Строки агрегатов по тенантам.
|
||||
private static async Task<IReadOnlyList<TokenUsageAggregateDto>> AggregateByTenantAsync(
|
||||
IQueryable<TokenUsageEventEntity> source, CancellationToken ct)
|
||||
private static async Task<IReadOnlyList<TokenUsageAggregateDto>> AggregateByTenantAsync(IQueryable<TokenUsageEventEntity> source, CancellationToken ct)
|
||||
{
|
||||
var rows = await source
|
||||
.GroupBy(e => e.TenantId)
|
||||
|
||||
@@ -62,8 +62,7 @@ public static class ServiceCollectionExtensions
|
||||
/// <param name="tenantLimitDefaults">Дефолт-бюджет лениво создаваемых строк tenant_limits (Ruling 3; null — константа TokenBudgetDefaults).
|
||||
/// Передаётся из конфигурации/env DEAL_DEFAULT_AI_BUDGET в Program.cs (Task 8).</param>
|
||||
/// <returns>Коллекция сервисов для цепочки вызовов.</returns>
|
||||
public static IServiceCollection AddDealPersistence(
|
||||
this IServiceCollection services, TokenLimitDefaults? tenantLimitDefaults = null)
|
||||
public static IServiceCollection AddDealPersistence(this IServiceCollection services, TokenLimitDefaults? tenantLimitDefaults = null)
|
||||
{
|
||||
services.AddScoped<IAuthStore, AuthStore>();
|
||||
|
||||
@@ -168,8 +167,11 @@ public static class ServiceCollectionExtensions
|
||||
/// см. Program.cs (Tasks 6/8).
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddDealIntegrations(
|
||||
this IServiceCollection services, MlServiceOptions mlOptions, AiServiceOptions aiOptions,
|
||||
TelegramServiceOptions telegramOptions, MtlsCertificates? mtlsCertificates = null)
|
||||
this IServiceCollection services,
|
||||
MlServiceOptions mlOptions,
|
||||
AiServiceOptions aiOptions,
|
||||
TelegramServiceOptions telegramOptions,
|
||||
MtlsCertificates? mtlsCertificates = null)
|
||||
{
|
||||
// Сертификаты mTLS-каналов (Ruling 6, Task 13): null (флаг DEAL_MTLS_ENABLED выключен) — каналы
|
||||
// остаются plaintext + service-token (dev); при включённом флаге каждый транспорт подписывает запрос
|
||||
|
||||
@@ -26,7 +26,10 @@ public sealed class CardMover(CardsService cardsService) : ICardMover
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<CardMoveResultDto> MoveAsync(
|
||||
string cardId, string toContainerId, TransitionContext ctx, CancellationToken ct)
|
||||
string cardId,
|
||||
string toContainerId,
|
||||
TransitionContext ctx,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Контекст перехода (инициатор/обучение) учтён внутри маршрутов: дашборд-перенос обучает ML по
|
||||
// цели пользователя, переход по стадии пишет историю и сбрасывает напоминание. Отдельного
|
||||
|
||||
@@ -52,7 +52,10 @@ public static class DefaultContainerProvisioner
|
||||
/// <param name="db">Контекст схемы тенанта.</param>
|
||||
/// <param name="now">Текущее UTC-время (CreatedAt новых строк).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
public static async Task EnsureAsync(TenantDbContext db, DateTimeOffset now, CancellationToken ct)
|
||||
public static async Task EnsureAsync(
|
||||
TenantDbContext db,
|
||||
DateTimeOffset now,
|
||||
CancellationToken ct)
|
||||
{
|
||||
HashSet<string> existing = (await db.Containers
|
||||
.AsNoTracking()
|
||||
@@ -104,7 +107,12 @@ public static class DefaultContainerProvisioner
|
||||
// now: Время создания строки.
|
||||
// Возвращает: Строка контейнера.
|
||||
private static ContainerEntity BuildService(
|
||||
string id, string name, string color, int order, ContainerPolicyDto policy, DateTimeOffset now)
|
||||
string id,
|
||||
string name,
|
||||
string color,
|
||||
int order,
|
||||
ContainerPolicyDto policy,
|
||||
DateTimeOffset now)
|
||||
{
|
||||
return new ContainerEntity
|
||||
{
|
||||
@@ -125,7 +133,10 @@ public static class DefaultContainerProvisioner
|
||||
// db: Контекст тенанта.
|
||||
// existing: Множество существующих id (пополняется).
|
||||
// container: Строка контейнера.
|
||||
private static void AddIfMissing(TenantDbContext db, HashSet<string> existing, ContainerEntity container)
|
||||
private static void AddIfMissing(
|
||||
TenantDbContext db,
|
||||
HashSet<string> existing,
|
||||
ContainerEntity container)
|
||||
{
|
||||
if (existing.Add(container.Id))
|
||||
{
|
||||
|
||||
@@ -66,7 +66,10 @@ public sealed class TenantProvisioningService(ConnectionStringProvider connectio
|
||||
// tenantId: Идентификатор тенанта.
|
||||
// schemaName: Имя схемы тенанта (для MigrationsHistoryTable).
|
||||
// ct: Токен отмены.
|
||||
private async Task ApplyTenantMigrationsAsync(TenantId tenantId, string schemaName, CancellationToken ct)
|
||||
private async Task ApplyTenantMigrationsAsync(
|
||||
TenantId tenantId,
|
||||
string schemaName,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Строка уже с Search Path=tenant_<id> (ForSchemaDdl) — таблицы бессхемной модели TenantDbContext
|
||||
// лягут в схему тенанта. DDL применяется мигратор-ролью при её наличии (см. ConnectionStringProvider).
|
||||
|
||||
Reference in New Issue
Block a user