12 KiB
CLAUDE.md
Инструкции для Claude Code при работе в этом репозитории.
Что это
TeleWave — сервис онлайн-каналов: пользователи смотрят сетку каналов, видео отдаётся из хранилища на сервере. Админ управляет каналами и пользователями.
Текущее состояние — рабочий вертикальный срез: вход/регистрация, роли, пользователи, админка; библиотека шоу/серий, загрузка и обработка медиа (ffmpeg → HLS-сегменты), реестр изображений, метаданные (TMDb/OMDb), каталог каналов, планировщик эфира (реклама, ТВ-заставки-переходы, weekly-override'ы) и live-раздача HLS с публичным просмотром сетки. Telegram-бот сознательно не делаем. Дальнейшие крупные направления (напр. многоэкземплярное развёртывание, новые доменные фичи) — по сверке с пользователем.
Архитектура и код-конвенции — прямое зеркало D:\Github\PnvPanel (тот же автор,
тот же стек), но домен урезан под текущий объём фичи.
Стек
- Backend: C# / .NET 10, ASP.NET Core Web API (Minimal API), Clean Architecture, CQRS через
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 cookietw_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. Никакого gosu/root-drop и 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/):
dotnet build
dotnet test tests/TeleWave.Domain.Tests 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/):
pnpm install
pnpm dev
pnpm build
pnpm lint && pnpm typecheck
Инфраструктура:
docker compose up -d --build # только api; postgres — внешний, см. ConnectionStrings__Default
Окружение: Windows, основная оболочка — PowerShell. Для POSIX-скриптов есть Bash-инструмент.
Рабочие принципы
- Базовый вертикальный срез (каналы, планировщик, раздача HLS, обработка медиа) уже реализован — правь его по месту. Но новые крупные направления с непринятыми архитектурными решениями (напр. многоэкземплярное развёртывание, транскод-профили, новые доменные подсистемы) начинай только после сверки с пользователем. При неоднозначности — вопрос пользователю, не предположение.
- Соблюдай границы слоёв — главный инвариант проекта, как и в PnvPanel.
- Не коммить и не пуши без явной просьбы.
- Отвечай пользователю на русском.