using Deal.Api.Http;
using Deal.Infrastructure.Services;
using Deal.Api.Services;
using Deal.Api.Dtos;
namespace Deal.Api.Endpoints;
///
/// Служебные storage-эндпоинты: POST /api/admin/tick и POST /api/admin/fts/rebuild (план Tasks 10–11,
/// Rulings 6/8/11; прототип dashboard_routes.py L261–264, L327–337).
///
///
/// Контракт 1:1 с прототипом и api-map §3.2 L103–112: POST /admin/tick = тик правил хранения текущего
/// тенанта + очистка отсева пайплайна (3 суток) + проверка напоминаний «Отложено» (план Task 11, Ruling 3/8)
/// + один проход pump очереди входящих (этап 4, Ruling 8/9);
/// ответ {storage, reminders, pipeline: {…}, queue: N} (dashboard_routes.py L327–337; storage.purgedRejected
/// объединяет очистку отсева — Ruling 9; reminders — «выстрелившие» напоминания {id,title,stage}, пусто —
/// сработавших нет). SSE-публикации (тосты статистики notify_tick_stats L496–504, new_card по созданным
/// карточкам и reminder_due по «выстрелившим» напоминаниям) выполняет из
/// Api-слоя — модули остаются чистыми (Ruling 5/8); без подписчиков публикация — no-op. Сбой проверки
/// напоминаний/pump не роняет тик: reminders/pipeline ответа пусты, очередь ждёт следующего тика/фонового
/// цикла (Task 11). POST /admin/fts/rebuild —
/// реальная идемпотентная пересборка FTS-индексов (CREATE INDEX IF NOT EXISTS +
/// ANALYZE, Ruling 6), ответ {ok:true, ready:true} (при сбое {ok:false, ready:false} — 1:1 с fts_rebuild L261–264,
/// кнопка Settings «Пересобрать индекс» store.js L1883–1889). Оба эндпоинта требуют сессию: 401 {detail} без
/// куки (Ruling 10); сервисы резолвятся из RequestServices ПОСЛЕ проверки сессии (паттерн BoardsEndpoints).
///
public static class StorageEndpoints
{
// Префикс группы (роутер dashboard, prefix="/api"; admin-пути прототипа L261/L327).
private const string AdminGroupPrefix = "/api";
// Путь ручного тика правил хранения (dashboard_routes.py L327).
private const string TickPath = "/admin/tick";
// Путь пересборки поискового индекса (dashboard_routes.py L261; Ruling 6).
private const string FtsRebuildPath = "/admin/fts/rebuild";
// OpenAPI-тег группы (в прототипе роутер dashboard — dashboard_routes.py).
private const string OpenApiTag = "dashboard";
///
/// Регистрирует POST /api/admin/tick и POST /api/admin/fts/rebuild.
///
/// Построитель маршрутов приложения.
/// Построитель маршрутов для цепочки вызовов.
public static IEndpointRouteBuilder MapStorageEndpoints(this IEndpointRouteBuilder app)
{
var admin = app.MapGroup(AdminGroupPrefix).WithTags(OpenApiTag);
admin.MapPost(TickPath, AdminTickAsync);
admin.MapPost(FtsRebuildPath, FtsRebuildAsync);
return app;
}
// POST /api/admin/tick: правила хранения + очистка отсева + напоминания + pump + SSE-тосты/new_card/reminder_due (admin_tick L327–337).
// Весь состав тика — AdminTickOrchestrator (вынесен из эндпоинта для unit-тестов логики и
// переиспользования): тик StorageTickService (Kanban) → PurgeExpiredAsync (отсев 3 суток, merge в
// storage.purgedRejected) → тосты статистики (включая «Отсев очищен: N записей (3 дн.)») → CheckDueAsync
// (напоминания «Отложено»: reminders ответа + SSE reminder_due, план Task 11; сбой не роняет тик) →
// PumpOnceAsync (сбой не роняет тик) → SSE new_card по созданным карточкам → queue. Формы — 1:1 с прототипом.
private static async Task AdminTickAsync(HttpContext context, CancellationToken ct)
{
if (!context.HasUser())
{
return EndpointResults.Unauthorized(AuthHelpers.UnauthorizedDetail);
}
AdminTickOrchestrator orchestrator = context.RequestServices.GetRequiredService();
Guid tenantId = context.GetCurrentUser()!.TenantId;
return Results.Ok(await orchestrator.TickAsync(tenantId, ct));
}
// POST /api/admin/fts/rebuild: пересборка FTS-индексов тенанта; ответ {ok:true, ready:true} (Ruling 6).
// SearchTsv — генерируемые STORED-колонки Cards/RejectedItems: авто-актуальны, «пересборка» = создание
// отсутствующих GIN-индексов (CREATE INDEX IF NOT EXISTS) + ANALYZE таблиц (FtsMaintenance.RebuildAsync).
// Сбой обслуживания возвращает {ok:false, ready:false} (прототип fts_rebuild L261–264: rebuild() → ok,
// is_ready() → ready) — кнопка Settings фронта показывает ошибку по ready (store.js L1883–1889).
private static async Task FtsRebuildAsync(HttpContext context, CancellationToken ct)
{
if (!context.HasUser())
{
return EndpointResults.Unauthorized(AuthHelpers.UnauthorizedDetail);
}
FtsMaintenance fts = context.RequestServices.GetRequiredService();
bool ok = await fts.RebuildAsync(ct);
return Results.Ok(new { ok, ready = ok });
}
}