# CLAUDE.md

Инструкции для Claude Code при работе в этом репозитории.

## Что это

**TeleWave** — сервис онлайн-каналов: зритель смотрит сетку каналов, видео отдаётся из хранилища
на сервере. Админ ведёт библиотеку, каналы и эфир.

Работает: аутентификация, роли, пользователи; библиотека шоу/серий, коллекции, жанры, метаданные
(TMDb/OMDb), обработка медиа (ffmpeg → HLS), изображения; группы, шаблон сетки, стыки и заставки,
автосборка по профилям, обмен конфигурацией файлом и запросом к ИИ; генерация ленты, live-раздача,
публичный просмотр, статистика хранилища. Telegram-бот сознательно не делаем. Конвенции — зеркало
[`PnvPanel`](../PnvPanel) (тот же автор).

**Спеки в `docs/` — источник правды по домену**, читай до правок в подсистеме:
[`tv-scheduler-architecture.md`](docs/tv-scheduler-architecture.md) (группы, сетка, слоты, стыки,
заставки, планировщик, автосборка, обмен конфигурацией — главный документ),
[`media-storage-and-streaming.md`](docs/media-storage-and-streaming.md) и
[`server-storage-setup.md`](docs/server-storage-setup.md) (медиа и хранилище).

## Стек

- **Backend**: C# / .NET 10, ASP.NET Core Minimal API, Clean Architecture, CQRS через
  [`LiteCqrs.Net`](https://github.com/mrleo1nid/LiteCqrs.Net) (лёгкая альтернатива MediatR с явным
  разделением Command/Query — см. README). EF Core 10 + Npgsql, Identity + JWT, FluentValidation,
  Serilog, маппинг DTO вручную, OpenAPI + Scalar UI, тесты на xUnit + NSubstitute.
- **Frontend**: React 19 + Vite + TS, TanStack Query/Router (файловые роуты, `routeTree.gen.ts`
  генерируется сборкой), shadcn поверх Radix + Tailwind v4, Zustand (только auth+тема),
  react-hook-form + zod, i18n (ru/en), pnpm + oxlint + prettier.

## Архитектура — жёсткие правила

Зависимости строго внутрь: **Api → Infrastructure → Application → Domain**.

- **Domain** — без внешних зависимостей: `Auth`, `Library` (шоу, серии, коллекции, жанры), `Media`,
  `Images`, `Programming` (группы, сетка, слоты, стыки + **чистый планировщик** `Planning`),
  `Broadcast` (каналы, заставки, лента, live), `Settings`. Модель rich: приватные сеттеры,
  фабрики `Create`, поведенческие методы.
- **Application** — CQRS-хендлеры, DTO, валидаторы и **порты** (`Common/Interfaces/I*.cs`:
  `IAppDbContext`, `ICurrentUser`, `IMediaProcessor`, `IMediaStorage`, `IBumperRenderer`,
  `IMetadataProvider`, `IImageStore`, `IStorageInspector`, …). Зависит только от Domain: никаких
  `Npgsql`/`AspNetCore.Identity`, лишь их абстракции.
- **Infrastructure** — реализации портов: EF Core (`AppDbContext : IdentityDbContext<AppUser,
  AppRole, Guid>`, конфигурации в `Persistence/Configurations`), ffmpeg, хранилище, метаданные,
  идемпотентный `DbInitializer` (роли + админ из env `AdminSeed__*`).
- **Api** — Minimal API (`Endpoints/*Endpoints.cs`), middleware, DI composition root (`Program.cs`).

Обязательно:
- Команды меняют состояние в транзакции (`UnitOfWorkBehavior`); запросы только читают. Диспетчер —
  `ISender`/`ICommandHandler`/`IQueryHandler`, регистрация в `Application/DependencyInjection.cs`.
- Управляемые ошибки — через `Result<T>` (`Common/Models/Result.cs`), не исключениями. Валидация —
  FluentValidation через `ValidationBehavior`; хендлер не перепроверяет формат ввода.
- Всё I/O асинхронно, `CancellationToken` пробрасывается до EF/HTTP; никаких `.Result`/`.Wait()`.
  Nullable включён, `TreatWarningsAsErrors=true` — предупреждения анализаторов не игнорировать.
- Планировщик (`Domain/Programming/Planning`) — **чистая функция**: вход `PlanningInput`, никаких
  запросов и часов внутри. Данные под него собирает `Application/Programming/Planning`.

## Инварианты, которые легко нарушить

- **Роли** динамические, у пользователя ровно одна; системные `admin`/`user` (`IsSystem=true`) не
  переименовать и не удалить, последнего админа не разжаловать, себя не заблокировать и не удалить.
- **Регистрация открытая** (создаётся `user`, токены сразу); понадобится модерация — заводи отдельный
  флаг, не переиспользуй `IsBlocked`. Вход по `UserName`, email и SMTP не используются; JWT access +
  refresh в httpOnly `tw_refresh_token` с обнаружением повторного использования отозванного.
- **Правка сетки эфир не двигает**: команда поднимает ревизию шаблона, ленту пересобирает применение.
- **Вещательные сутки** начинаются с `Channel.DayStartTime` (06:00), сетка задаётся во времени
  канала (`UtcOffsetMinutes`), в базе всё в UTC.

## Единый контейнер

- Один образ: Api раздаёт REST (`/api`) и статику SPA из `wwwroot` (fallback на `index.html`), один
  origin, база API — относительный `/api`. Multi-stage Dockerfile: node → dotnet sdk → aspnet.
  Отдельный nginx для статики не вводить — ломает требование единого контейнера.
- Работает под непривилегированным `USER $APP_UID` (uid/gid 1654) **статически**, без
  gosu/entrypoint: права на bind-mount выставляет оператор (`docs/server-storage-setup.md` § 5).
  Data Protection не заводим — шифруемых секретов на диске нет. **TLS внешний**: `app` отдаёт HTTP
  и доверяет `X-Forwarded-*`.
- docker-compose: только `app`. PostgreSQL внешний (`ConnectionStrings__Default` из `.env`), база
  и пользователь создаются заранее вручную; миграции применяются на старте (`ApplyMigrationsAsync`),
  прав `CREATEDB` не нужно.

## Соглашения по коду

- `<Verb><Noun>Command`/`<Get|List><Noun>Query` + `Handler`/`Validator` (где есть что проверить),
  папки по фичам (`Auth/Login/`, `Programming/Templates/Generate/`, …). Один публичный тип на файл =
  имя файла, кроме `Body` — тела Api-эндпоинтов, не совпадающие с командой 1:1 (`sealed record`
  внизу файла эндпоинта). DTO — суффикс `Dto`.
- Ошибки — каталоги `XErrors` (`Error.Validation/NotFound/Conflict/…`), наружу единым
  `application/problem+json` (`Api/Common/ResultExtensions.cs`). Секреты не логировать.
- **Изображения — только через реестр `Image`** (`Domain/Images`, галерея `features/admin/images`):
  картинка хранится как `ImageId` и выбирается контролом `ImageGallery`/`GalleryBrowser`, своих
  файловых полей не заводить. Файлы — `images/{id}{ext}`, отдаются по `/api/images/{id}`.
- Фронт: фича = папка в `src/features/admin/<фича>`; тексты только через i18n, ключи в `ru.ts` и `en.ts`.
- Комментарии объясняют **почему**, а не что; по-русски, как в существующем коде.

## Команды

Backend (из `backend/`):
```bash
dotnet build
dotnet csharpier format .   # только через dotnet (версия из .config/); в CI — csharpier check
dotnet test tests/TeleWave.Domain.Tests        # по одному проекту: MSBuild не берёт несколько
dotnet test tests/TeleWave.Application.Tests
dotnet test tests/TeleWave.Integration.Tests   # Testcontainers; без Docker пропускаются
dotnet run --project src/TeleWave.Api
dotnet ef migrations add <Name> --project src/TeleWave.Infrastructure --startup-project src/TeleWave.Api
```

Frontend (из `frontend/`):
```bash
pnpm install && pnpm dev
pnpm lint && pnpm typecheck && pnpm build
pnpm format && pnpm format:check   # CI падает на неотформатированном — прогоняй после правок
```

Инфраструктура: `docker compose up -d --build` (только api; postgres внешний). Окружение — Windows,
оболочка **PowerShell**; для POSIX-скриптов есть Bash-инструмент.

## Рабочие принципы

- Базовый срез реализован — правь по месту. Новые крупные направления с непринятыми решениями
  (многоэкземплярность, транскод-профили, подсистемы) — после сверки; неоднозначность — вопрос.
- Меняешь поведение подсистемы — обнови её спеку в `docs/` тем же заходом.
- Соблюдай границы слоёв — главный инвариант проекта.
- Правь файлы через Edit/Write: пользователь видит дифф, а harness не даст перезаписать файл вслепую.
  Скриптом на python — только при крайней необходимости (массовая механическая замена), и сказать явно.
- Не коммить и не пуши без явной просьбы; отвечай пользователю на русском.
