Update configuration and enhance media processing: add stream token TTL and timeout settings in .env.example, improve error handling in media endpoints, and refactor command handlers for asynchronous operations. Update documentation to reflect current application state and features.
This commit is contained in:
@@ -31,42 +31,42 @@
|
||||
### Безопасность
|
||||
- [x] **M1. Cookie без `Secure` за TLS-прокси** — `AuthEndpoints.cs:207`, `StreamingEndpoints.cs:63`.
|
||||
- [x] **M2. Ключи TMDb/OMDb в логах** — `TmdbMetadataProvider.cs:27`, `OmdbMetadataProvider.cs:25` (`System.Net.Http` не приглушён в Serilog).
|
||||
- [ ] **M3. Stream-токен неотзывной, TTL 6ч, игнорирует userId/блокировку** — `StreamTokenService.cs:15`, `StreamingEndpoints.cs:98,123`. _(дизайн-решение — отложено)_
|
||||
- [x] **M3. Stream-токен неотзывной, TTL 6ч, игнорирует userId/блокировку** — `StreamTokenService.cs:15`, `StreamingEndpoints.cs:98,123`. _(TTL 30 мин + проверка блокировки на плейлисте; фронт перевыпускает cookie)_
|
||||
- [x] **M4. Rate-limiter глобальный (не партиционирован), только на `/api/auth`** — `Program.cs:66`.
|
||||
|
||||
### Архитектура / транзакции
|
||||
- [ ] **M5. `ExecuteDeleteAsync` ломает границу UnitOfWork + файлы удаляются до коммита** — `DeleteShowMediaCommandHandler.cs:36`, `ClearAllMediaCommandHandler.cs`, `DeleteAllShowsCommandHandler.cs`. _(дизайн-решение — отложено)_
|
||||
- [ ] **M6. Query с побочным эффектом на ФС** — `RenderBumperPreviewQueryHandler.cs:18` (рендер файлов под видом запроса). _(дизайн-решение — отложено)_
|
||||
- [ ] **M7. Файловый/HTTP I/O до коммита** — Delete/Register/RefreshEpisodes хендлеры (orphan-файлы при откате). _(дизайн-решение — отложено)_
|
||||
- [x] **M5. `ExecuteDeleteAsync` ломает границу UnitOfWork + файлы удаляются до коммита** — `DeleteShowMediaCommandHandler.cs`, `ClearAllMediaCommandHandler.cs`. _(явная транзакция вокруг ExecuteDelete; файлы после коммита. DeleteAllShows — единичный ExecuteDelete, уже атомарен)_
|
||||
- [x] **M6. Query с побочным эффектом на ФС** — переведён в `RenderBumperPreviewCommand` (command, не query).
|
||||
- [x] **M7. Файловый/HTTP I/O до коммита** — Delete/Register хендлеры: БД коммитится раньше файлового I/O; Register откатывает регистрацию при сбое переноса файла.
|
||||
|
||||
### Планировщик / медиа
|
||||
- [ ] **M8. Нет таймаута на ffmpeg/ffprobe** — `ProcessRunner.cs:63` (зависший процесс держит слот). _(нужен разумный таймаут — отложено на согласование)_
|
||||
- [ ] **M9. Гонка при конкурентной генерации расписания канала** — `ScheduleGenerator.cs:62` (тик + regenerate → дубли записей). _(дизайн-решение — отложено)_
|
||||
- [ ] **M10. Рендер заставок синхронно внутри тика планировщика** — `ScheduleBumperResolver.cs:174`. _(дизайн-решение — отложено)_
|
||||
- [x] **M8. Нет таймаута на ffmpeg/ffprobe** — `ProcessRunner.cs` + `MediaOptions` (ffprobe 120с, транскод/рендер 1800с, конфигурируемо).
|
||||
- [x] **M9. Гонка при конкурентной генерации расписания канала** — `ScheduleGenerator.cs` (транзакция + `pg_advisory_xact_lock` по каналу).
|
||||
- [~] **M10. Рендер заставок синхронно внутри тика планировщика** — `ScheduleBumperResolver.cs:174`. _(частично: M8 ограничивает худший случай, кэш делает рендеры редкими, M9 сериализует канал. Полный вынос рендера из тика — отдельная архитектурная задача, флажок ниже.)_
|
||||
- [x] **M11. Override через полночь не работает** — `SchedulePlannerModels.cs:40` (`EndMinute <= StartMinute` → пустое окно).
|
||||
|
||||
### Фронтенд
|
||||
- [x] **M12. Молчаливое проглатывание ошибки → вечный скелетон** — `AirPage.tsx:43`.
|
||||
- [x] **M13. Повторный 401 после refresh не разлогинивает** — `client.ts:80`.
|
||||
- [ ] **M14. Клиентская пагинация поверх усечённого ответа** — `ShowDetail.tsx:42` (`pageSize:500`), `ChannelDetail.tsx:32` (`pageSize:100`). _(нужна серверная пагинация — отложено)_
|
||||
- [ ] **M15. Дедуп загрузок только по имени файла** — `upload-store.ts:143`. _(зависит от контракта серверной уникальности — отложено)_
|
||||
- [x] **M14. Клиентская пагинация поверх усечённого ответа** — `ShowDetail.tsx`, `ChannelDetail.tsx`. _(пикеры дозагружают все страницы через `listAllMedia` с safety-cap; при упоре в cap — видимое предупреждение. True server-side pagination неуместна: пикеры делают клиентские исключение/парсинг/сортировку/bulk-select)_
|
||||
- [x] **M15. Дедуп загрузок только по имени файла** — `upload-store.ts:143`. _(разрешено анализом: сервер сам дедупит по `OriginalFileName` — клиент это зеркалит, а `skipped` показывается. Имя+размер разошлось бы с сервером → failed-загрузки. Уникальность по имени — доменное решение, кода не менял)_
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Низкое / прочее
|
||||
|
||||
- [ ] **L1. Синхронный I/O без CancellationToken в портах удаления** (`IMediaStorage`/`IImageStore`/`IBumperTemplateStorage`). _(меняет интерфейсы портов — отложено)_
|
||||
- [x] **L1. Синхронный I/O без CancellationToken в портах удаления** — все 5 delete-методов портов переведены на `Task`+`CancellationToken`; рекурсивные удаления каталогов офлоадятся (`Task.Run`).
|
||||
- [x] **L2. `ValidationBehavior` вызывает `Validate` вместо `ValidateAsync`** — `ValidationBehavior.cs:24`.
|
||||
- [ ] **L3. Инвариант «Single = 1 серия» проверяется в хендлере, а не в агрегате** — `Show.AddEpisode`. _(рефактор агрегата — отложено)_
|
||||
- [ ] **L4. Двойной `SaveChanges` в командном пути ScheduleGenerator** — `ScheduleGenerator.cs:79,132`. _(связано с M9 — отложено)_
|
||||
- [x] **L3. Инвариант «Single = 1 серия» проверяется в хендлере, а не в агрегате** — `Show.AddEpisode` кидает при обходе (+тест).
|
||||
- [x] **L4. Двойной `SaveChanges` в командном пути ScheduleGenerator** — снят: генератор коммитит свою транзакцию, пост-save UnitOfWork стал no-op.
|
||||
- [x] **L5. HLS-плеер не восстанавливается после fatal network/media error** — `ChannelPlayer.tsx:106` (+ симметричная очистка слушателей нативного HLS).
|
||||
- [x] **L6. Бесконечная повторная регистрация «падающего» файла из inbox** — `InboxScannerBackgroundService.cs:92`.
|
||||
- [x] **L7. Возможное переполнение int в весах шоу** — `SchedulePlanner.cs:256,265`.
|
||||
- [ ] **L8. OpenAPI/Scalar мапятся всегда, без гейта по окружению** — `Program.cs:108`. _(возможно намеренно для внутреннего инструмента — решение за владельцем)_
|
||||
- [ ] **L9. `AllowedHosts: "*"` и dev-креды БД в appsettings.json**. _(dev-дефолты; решение за владельцем)_
|
||||
- [x] **L8. OpenAPI/Scalar мапятся всегда, без гейта по окружению** — `Program.cs` (только Development либо флаг `Api:EnableOpenApi`).
|
||||
- [x] **L9. `AllowedHosts: "*"` и dev-креды БД в appsettings.json** — осознанные dev-дефолты, переопределяются env в проде; C2 теперь форсит реальный JWT-ключ. Оставлено как задокументированное.
|
||||
- [x] **L10. SSRF-поверхность в ImageDownloader** (без allowlist схемы/хоста) — `ImageDownloader.cs:16` (allowlist http/https).
|
||||
- [ ] **L11. Документация (CLAUDE.md/README) отстала**: заявлено «каталог каналов и раздача видео не реализованы», хотя реализованы. _(нужна сверка с владельцем по объёму)_
|
||||
- [ ] **L12. Пробелы в тестах**: фоновые сервисы, ScheduleGenerator-оркестрация, media-конвейер, эндпоинты без тестов. _(частично: добавлены тесты на M11)_
|
||||
- [x] **L11. Документация (CLAUDE.md/README) отстала** — обновлены разделы «текущее состояние» и рабочий принцип под реальный объём.
|
||||
- [~] **L12. Пробелы в тестах** — частично: добавлены тесты на M11 (override через полночь), L3 (Single). Полное покрытие фоновых сервисов/оркестрации/эндпоинтов — отдельная задача.
|
||||
|
||||
---
|
||||
|
||||
@@ -126,3 +126,53 @@ Frontend:
|
||||
|
||||
Проверка: `dotnet build` 0/0; тесты 97 + 49 зелёные; frontend `tsc` чистый; `oxlint` без новых
|
||||
предупреждений.
|
||||
|
||||
### 2026-07-25 — остальные средние/низкие (M3, M5–M9, M14, L1, L3, L4, L8, L11 + разбор M10, M15, L9, L12)
|
||||
|
||||
По согласованным решениям (короткий TTL+проверка блокировки; конфиг-таймауты ffmpeg; Postgres
|
||||
advisory-lock; явные транзакции + I/O после коммита):
|
||||
|
||||
- **M3** — `StreamingOptions.StreamTokenMinutes` (30), `StreamTokenService.Validate` возвращает id
|
||||
зрителя; `LivePlaylist` сверяет блокировку через `IIdentityService` (1 запрос на перезагрузку
|
||||
плейлиста, не на сегмент). Фронт (`AirPage`) перевыпускает cookie каждые 20 мин. Dev-ключ JWT
|
||||
добавлен в `appsettings.Development.json`, чтобы C2 не ломал локальный запуск.
|
||||
- **M5/M7** — `IAppDbContext.BeginTransactionAsync`; `ClearAllMedia`/`DeleteShowMedia` — транзакция
|
||||
вокруг ExecuteDelete, файлы после коммита; `DeleteMediaAsset`/`DeleteImage`/`ClearBumperAudio`/
|
||||
`RemoveBumperTemplate` — БД раньше файлов; `RegisterMediaAsset` — save→перенос с откатом при сбое.
|
||||
- **M6** — `RenderBumperPreviewQuery` → `RenderBumperPreviewCommand` (+ файл/класс переименованы).
|
||||
- **M8** — `ProcessRunner` принимает таймаут; `MediaOptions.ProbeTimeoutSeconds`/`TranscodeTimeoutSeconds`;
|
||||
по таймауту процесс убивается, `TimeoutException` → ассет уходит в Failed, слот освобождается.
|
||||
- **M9** — `IAppDbContext.AcquireChannelLockAsync` (`pg_advisory_xact_lock` по int64 из GUID канала);
|
||||
`ScheduleGenerator.GenerateAsync` обёрнут транзакцией + lock, снял и **L4** (двойной save).
|
||||
- **M14** — `listAllMedia` (дозагрузка всех страниц, safety-cap 5000) в пикерах ShowDetail/ChannelDetail
|
||||
+ предупреждение при усечении (i18n ru/en). True server-pagination неуместна (клиентские
|
||||
исключение/парсинг/сортировка/bulk).
|
||||
- **L1** — 5 delete-методов портов → async с `CancellationToken`; рекурсивные удаления офлоадятся.
|
||||
- **L3** — `Show.AddEpisode` кидает при второй серии для Single (+тест). **L8** — OpenAPI/Scalar только
|
||||
вне прода/по флагу. **L11** — CLAUDE.md/README обновлены.
|
||||
|
||||
Разбор без правок кода:
|
||||
- **M10** — частично закрыт (M8 ограничивает худший случай, кэш делает рендеры редкими, M9 сериализует
|
||||
канал); полный вынос рендера из тика — отдельная архитектурная задача (флажок).
|
||||
- **M15** — сервер сам дедупит по имени файла; клиент это зеркалит и показывает `skipped`. Смена на
|
||||
имя+размер разошлась бы с сервером. Уникальность по имени — доменное решение.
|
||||
- **L9** — dev-дефолты, переопределяются env в проде; C2 форсит JWT-ключ.
|
||||
|
||||
Новые/обновлённые опции: `.env.example` (Storage__StreamTokenMinutes, Media__Probe/TranscodeTimeoutSeconds,
|
||||
Api:EnableOpenApi неявно). Тесты: +2 domain (override через полночь), +1 domain (Single).
|
||||
|
||||
Проверка: `dotnet build` 0/0 (TreatWarningsAsErrors); тесты **98 + 49** зелёные; frontend `tsc` чистый,
|
||||
`oxlint` без новых предупреждений.
|
||||
|
||||
---
|
||||
|
||||
## Осталось (флажки на будущее — требуют отдельного решения)
|
||||
|
||||
- **M10 (полностью)** — вынести рендер ТВ-заставок из тика планировщика: генерировать расписание с
|
||||
плейсхолдерами и рендерить ассеты асинхронно, чтобы один канал не задерживал достройку остальных и
|
||||
чтобы не держать транзакцию/advisory-lock во время ffmpeg.
|
||||
- **L12 (полностью)** — интеграционные тесты фоновых сервисов (media-конвейер, планировщик),
|
||||
оркестрации `ScheduleGenerator`, эндпоинтов (нужен реальный/контейнерный Postgres — InMemory не
|
||||
тянет транзакции/advisory-lock/raw SQL).
|
||||
- **M15 (доменно)** — если нужны разные файлы с одинаковым именем: пересмотреть уникальность
|
||||
`MediaAsset` (сейчас по `OriginalFileName`).
|
||||
|
||||
Reference in New Issue
Block a user