157 lines
13 KiB
Markdown
157 lines
13 KiB
Markdown
# CLAUDE.md
|
||
|
||
Инструкции для Claude Code при работе в этом репозитории.
|
||
|
||
## Что это
|
||
|
||
**TeleWave** — сервис онлайн-каналов: пользователи смотрят сетку каналов, видео отдаётся из
|
||
хранилища на сервере. Админ управляет каналами и пользователями.
|
||
|
||
> Текущее состояние — **рабочий вертикальный срез**: вход/регистрация, роли, пользователи, админка;
|
||
> библиотека шоу/серий, загрузка и обработка медиа (ffmpeg → HLS-сегменты), реестр изображений,
|
||
> метаданные (TMDb/OMDb), каталог каналов, планировщик эфира (реклама, ТВ-заставки-переходы,
|
||
> weekly-override'ы) и live-раздача HLS с публичным просмотром сетки. Telegram-бот сознательно
|
||
> не делаем. Дальнейшие крупные направления (напр. многоэкземплярное развёртывание, новые доменные
|
||
> фичи) — по сверке с пользователем.
|
||
|
||
Архитектура и код-конвенции — прямое зеркало [`D:\Github\PnvPanel`](../PnvPanel) (тот же автор,
|
||
тот же стек), но домен урезан под текущий объём фичи.
|
||
|
||
## Стек
|
||
|
||
- **Backend**: C# / .NET 10, ASP.NET Core Web API (Minimal API), Clean Architecture, CQRS через
|
||
[`LiteCqrs.Net`](https://github.com/mrleo1nid/LiteCqrs.Net) (NuGet-пакет, лёгкая
|
||
CQRS-библиотека, альтернатива MediatR с явным разделением Command/Query — см. её README).
|
||
EF Core 10 + Npgsql, ASP.NET Core Identity + JWT (access + refresh), FluentValidation, Serilog.
|
||
Маппинг DTO вручную. OpenAPI — нативный `Microsoft.AspNetCore.OpenApi` + Scalar UI.
|
||
- **Frontend**: React 19 + Vite + TS, TanStack Query/Router, shadcn-стиль поверх Radix + Tailwind
|
||
v4, Zustand (только auth+тема), react-hook-form + zod. pnpm, oxlint.
|
||
- **Инфра**: единый Docker-образ (API + статика SPA); PostgreSQL — **внешний сервер**, не в
|
||
compose (в отличие от PnvPanel).
|
||
|
||
## Архитектура — жёсткие правила
|
||
|
||
Слои и направление зависимостей: **Api → Infrastructure → Application → Domain** (внутрь).
|
||
|
||
- **Domain** — без внешних зависимостей. Сейчас единственная сущность — `Auth/RefreshToken`
|
||
(rich model: приватные сеттеры, фабричный `Issue(...)`, поведенческий `Revoke(...)`).
|
||
- **Application** — CQRS-хендлеры, DTO, валидаторы, **порты** (интерфейсы:
|
||
`IAppDbContext`, `ICurrentUser`, `IIdentityService`, `IJwtTokenService`, `IRefreshTokenService`,
|
||
`IRoleService`). Зависит только от Domain — никаких `Npgsql`/`AspNetCore.Identity`, только их
|
||
абстракции.
|
||
- **Infrastructure** — реализации портов: EF Core (`AppDbContext : IdentityDbContext<AppUser,
|
||
AppRole, Guid>`), `IdentityService`/`RoleService`/`JwtTokenService`/`RefreshTokenService`,
|
||
идемпотентный `DbInitializer` (сидинг ролей + админа).
|
||
- **Api** — Minimal API эндпоинты (`Endpoints/*Endpoints.cs`), middleware, DI composition root
|
||
(`Program.cs`).
|
||
|
||
Обязательно:
|
||
- Команды меняют состояние в транзакции (`UnitOfWorkBehavior`); запросы только читают. Диспетчер —
|
||
из `LiteCqrs.Net` (`ISender`/`ICommandHandler`/`IQueryHandler`), регистрация через
|
||
`AddLiteCqrs(...)` в `TeleWave.Application/DependencyInjection.cs`.
|
||
- Управляемые ошибки — через `Result<T>` (`Common/Models/Result.cs`), не исключениями.
|
||
- Валидация — FluentValidation через `ValidationBehavior`; хендлер не перепроверяет формат ввода.
|
||
- Всё I/O асинхронно, `CancellationToken` пробрасывается до EF/HTTP. Никаких `.Result`/`.Wait()`.
|
||
- Nullable reference types включены; `Directory.Build.props` включает
|
||
`TreatWarningsAsErrors=true` — предупреждения анализаторов не игнорировать.
|
||
|
||
## Домен: роли, пользователи, аутентификация
|
||
|
||
Полноценной доменной модели (каналы/видео) пока нет — см. заметку в начале файла. Текущие
|
||
инварианты:
|
||
|
||
- **Роли** (`AppRole : IdentityRole<Guid>`, `Infrastructure/Identity/`) — динамические,
|
||
без квот (в отличие от PnvPanel: тут нет `MaxIpLimit`/`BillingEnabled` — при появлении
|
||
доменных фич квоты/лимиты добавляются на `AppRole`/`AppUser` по мере необходимости, не заранее).
|
||
У пользователя ровно одна роль. Системные роли `admin`/`user` (`IsSystem=true`) нельзя
|
||
переименовать/удалить — проверяется в `RoleService`. Смену роли (`ChangeUserRoleCommand`)
|
||
запрещено делать так, чтобы не осталось ни одного `admin` (`RoleErrors.CannotRemoveLastAdmin`).
|
||
- **Регистрация** — открытая, без гейта активации (в отличие от PnvPanel): `RegisterCommandHandler`
|
||
создаёт пользователя с ролью `user` и сразу выдаёт токены (auto-login). Если в будущем
|
||
понадобится модерация новых пользователей — добавлять отдельным полем/флагом по аналогии с
|
||
`IsActivated` в PnvPanel, не переиспользовать `IsBlocked`.
|
||
- **Блокировка** (`AppUser.IsBlocked`) — админ блокирует/разблокирует пользователя; вход
|
||
запрещён (`LoginCommandHandler`/`RefreshCommandHandler` проверяют флаг). Админ не может
|
||
заблокировать/удалить самого себя (`UserErrors.CannotBlockSelf`/`CannotDeleteSelf`).
|
||
- **Вход по `UserName`** (email не используется, SMTP не нужен). JWT — access (короткий TTL) +
|
||
refresh (httpOnly cookie `tw_refresh_token`, `SameSite=Strict`, ротация с обнаружением повторного
|
||
использования отозванного токена — см. `RefreshTokenService.RotateAsync`).
|
||
- **Сидинг**: идемпотентный `DbInitializer` создаёт системные роли + админа из env
|
||
(`AdminSeed__Username`/`Password`) при старте. Источник примера env — `.env.example`.
|
||
|
||
## Единый контейнер
|
||
|
||
- Один образ: Api раздаёт REST (`/api`) и статику SPA из `wwwroot` (fallback на `index.html`).
|
||
Один origin, база API — относительный `/api`.
|
||
- Multi-stage Dockerfile: node (фронт) → dotnet sdk (publish + копирование в `wwwroot`) → aspnet
|
||
runtime. Контейнер работает под непривилегированным пользователем образа (`USER $APP_UID`,
|
||
uid/gid 1654) — но **статически**, без gosu/entrypoint-скриптов и подгонки uid на старте: права
|
||
на bind-mount хранилища выставляет оператор на хосте (см. `docs/server-storage-setup.md` § 5).
|
||
Data Protection key-ring не заводим — секретов на диске пока нет (JWT-ключ — обычная конфигурация,
|
||
не шифруемый секрет at-rest); появятся зашифрованные данные (например API-ключи внешних
|
||
сервисов) — добавлять Data Protection по образцу PnvPanel.
|
||
- docker-compose: только `app` — PostgreSQL живёт вне compose (внешний сервер/хост, адрес и
|
||
креды — в `ConnectionStrings__Default` из `.env`; база и пользователь на нём создаются
|
||
заранее вручную, приложение их не сидит). Миграции применяются авто на старте
|
||
(`ApplyMigrationsAsync` в `Program.cs`) — прав `CREATEDB` не требуется, только DDL/DML в
|
||
уже существующей базе.
|
||
- Не вводи отдельный nginx-контейнер для статики — ломает требование единого контейнера.
|
||
- **TLS — внешний**; `app` отдаёт HTTP + доверяет `X-Forwarded-*` (`ForwardedHeaders`).
|
||
|
||
## Соглашения по коду
|
||
|
||
- `<Verb><Noun>Command`/`<Get|List><Noun>Query` + `Handler`/`Validator` (валидатор — где есть что
|
||
проверить). Папки — по фичам (`Auth/Login/`, `Admin/Roles/CreateRole/`, ...).
|
||
- DTO — суффикс `Dto`; тела запросов Api-эндпоинтов, не совпадающие с командой 1:1, — `Body`
|
||
(`sealed record` внизу файла эндпоинта).
|
||
- Один публичный тип на файл = имя файла (кроме `Body` в файле эндпоинтов).
|
||
- Ошибки — статические каталоги `XErrors` (`AuthErrors`, `RoleErrors`, `UserErrors`) с
|
||
`Error.Validation/NotFound/Conflict/Unauthorized/Forbidden(...)`.
|
||
- Секреты не логировать; логи — Serilog.
|
||
- Ошибки API — единый `application/problem+json` (см. `Api/Common/ResultExtensions.cs`).
|
||
- **Изображения — через общий реестр `Image`** (домен `Domain/Images`, галерея `features/admin/images`).
|
||
Любой новый функционал, где загружается или выбирается картинка, должен использовать контрол
|
||
`ImageGallery`/`GalleryBrowser` (пикер по категориям `ImageCategory`) и хранить ссылку на `ImageId`,
|
||
а не заводить своё файловое поле. Автоматически полученные картинки (например постер из метадаты)
|
||
тоже регистрируются как `Image`. Файлы лежат под `images/{id}{ext}`, отдаются по `/api/images/{id}`.
|
||
|
||
## Команды
|
||
|
||
Backend (из `backend/`):
|
||
```bash
|
||
dotnet build
|
||
# Юнит-тесты (по одному проекту за вызов — MSBuild не принимает несколько):
|
||
dotnet test tests/TeleWave.Domain.Tests
|
||
dotnet test tests/TeleWave.Application.Tests
|
||
# Интеграционные тесты (Testcontainers-Postgres) — нужен запущенный Docker; без него пропускаются:
|
||
dotnet test tests/TeleWave.Integration.Tests
|
||
dotnet run --project src/TeleWave.Api
|
||
dotnet ef migrations add <Name> --project src/TeleWave.Infrastructure --startup-project src/TeleWave.Api
|
||
dotnet ef database update --project src/TeleWave.Infrastructure --startup-project src/TeleWave.Api
|
||
```
|
||
|
||
Frontend (из `frontend/`):
|
||
```bash
|
||
pnpm install
|
||
pnpm dev
|
||
pnpm build
|
||
pnpm lint && pnpm typecheck
|
||
```
|
||
|
||
Инфраструктура:
|
||
```bash
|
||
docker compose up -d --build # только api; postgres — внешний, см. ConnectionStrings__Default
|
||
```
|
||
|
||
> Окружение: Windows, основная оболочка — **PowerShell**. Для POSIX-скриптов есть Bash-инструмент.
|
||
|
||
## Рабочие принципы
|
||
|
||
- Базовый вертикальный срез (каналы, планировщик, раздача HLS, обработка медиа) уже реализован —
|
||
правь его по месту. Но новые крупные направления с непринятыми архитектурными решениями (напр.
|
||
многоэкземплярное развёртывание, транскод-профили, новые доменные подсистемы) начинай только после
|
||
сверки с пользователем. При неоднозначности — вопрос пользователю, не предположение.
|
||
- Соблюдай границы слоёв — главный инвариант проекта, как и в PnvPanel.
|
||
- Не коммить и не пуши без явной просьбы.
|
||
- Отвечай пользователю на русском.
|