# CLAUDE.md Инструкции для Claude Code при работе в этом репозитории. ## Что это **PnvPanel** — self-service портал для VPN-конфигураций. Пользователи сами создают себе конфиги (VLESS/VMess/Trojan/Shadowsocks), админ управляет серверами и пользователями. Есть **Telegram-бот** (ссылка на сайт, просмотр конфигов, passwordless-вход через привязку Telegram). Бэкенд оркестрирует панели **3x-ui** через библиотеку [`ThreeXui.Net`](https://github.com/mrleo1nid/ThreeXui.Net) и хранит свою проекцию домена в PostgreSQL. Живые обновления — по SignalR. Приложение (фронт + бек + бот) поставляется **единым Docker-образом**; PostgreSQL — отдельным контейнером в compose. > **Статус: MVP реализован и работает.** Бэкенд (M0–M8) и фронтенд полностью собраны, покрыты > тестами (134 бэкенд-теста), единый Docker-образ и docker-compose стек проверены живьём. История > этапов — [`docs/roadmap.md`](docs/roadmap.md); там же — раздел Backlog с тем, что осознанно > оставлено за рамками MVP (тарифы, лимиты трафика/срока на конфиг, полное самообслуживание в боте и т.д.). ## Документация (single source of truth) Прежде чем менять архитектуру или добавлять фичу — свериться с [`docs/`](docs/README.md): - [Vision](docs/vision.md) · [Architecture](docs/architecture.md) · [Domain Model](docs/domain-model.md) - [Tech Stack (ADR)](docs/tech-stack.md) · [Backend Conventions](docs/backend-conventions.md) - [Frontend](docs/frontend.md) · [Telegram Bot](docs/telegram-bot.md) · [API Design](docs/api-design.md) · [Roadmap](docs/roadmap.md) **Держи доки в синхроне с кодом.** Меняешь контракт/архитектуру — обнови соответствующий док в том же изменении. ## Стек - **Backend**: C# / .NET 10, ASP.NET Core Web API, Clean Architecture, CQRS (**собственный тонкий диспетчер**, без MediatR), EF Core 10 + Npgsql (PostgreSQL), ASP.NET Core Identity + JWT, SignalR, FluentValidation, **Serilog** (логирование). Маппинг DTO — вручную (`FromDomain(...)`), Mapster в проект не попал. OpenAPI — нативный `Microsoft.AspNetCore.OpenApi` + Scalar UI, без Swashbuckle. - **Frontend**: React 19 + Vite + TypeScript, TanStack Query/Router, shadcn-стиль поверх Radix + Tailwind CSS v4, Zustand (только auth-стор), react-hook-form + zod, @microsoft/signalr. Пакетный менеджер — pnpm, линтер — oxlint. `recharts`/`@tanstack/react-table` установлены, но не используются в MVP (статистика — карточками, таблицы — руками). - **Telegram**: Telegram.Bot, бот как `BackgroundService` **в процессе Api** (long polling). - **Инфра**: единый Docker-образ (API + бот + статика SPA) + PostgreSQL в docker-compose. ## Архитектура — жёсткие правила Слои и направление зависимостей: **Api → Infrastructure → Application → Domain** (внутрь). - **Domain** — без внешних зависимостей. Rich model: инварианты в сущностях (приватные сеттеры, фабричные методы, поведенческие методы). Никаких EF/HTTP/Identity здесь. - **Application** — CQRS-хендлеры, DTO, валидаторы, **порты** (интерфейсы). Зависит только от Domain. Никаких `Npgsql`/`SignalR`/`ThreeXui.Net` — только их интерфейсы (`IAppDbContext`, `IXuiPanelGateway`, `IRealtimeNotifier`, `ISecretProtector`, `ICurrentUser`, ...). - **Infrastructure** — реализации портов: EF Core, Identity/JWT, `XuiPanelGateway`, SignalR-пуш, фоновые сервисы, шифрование секретов. - **Api** — Minimal API эндпоинты (по фичам), SignalR-хабы, middleware, DI composition root. Обязательно: - CQRS: команды меняют состояние и идут в транзакции (UnitOfWorkBehavior); запросы только читают (`AsNoTracking` + проекция в DTO). Диспетчер — **собственный** (`ISender`/`ICommandHandler`/ `IQueryHandler`, регистрация хендлеров через DI), без внешних CQRS-библиотек. - Управляемые ошибки — через `Result`, не исключениями. Исключения — только для исключительного. - Валидация — FluentValidation через `ValidationBehavior`; хендлер не перепроверяет формат ввода. - Всё I/O асинхронно, `CancellationToken` пробрасывается до EF/HTTP. Никаких `.Result`/`.Wait()`. - Nullable reference types включены; предупреждения анализаторов не игнорировать. ## Интеграция с 3x-ui - Только через порт `IXuiPanelGateway`. `ThreeXui.Net` регистрируется на один `BaseAddress`, а нод много → гейтвей держит **клиента per-node** (кэш по `NodeId`), создавая его из расшифрованных `NodeCredentials`. Детали — в [architecture.md](docs/architecture.md#интеграция-с-3x-ui-threexuinet). - Пароли нод **шифруются at-rest** (`ISecretProtector`), расшифровка только внутри Infrastructure, никогда не в логах/ответах API. - Недоступность ноды → `Result.Failure`/`NodeStatus.Offline`, не 500 наружу. - Операции с 3x-ui идемпотентны; при частичном сбое (клиент создан в панели, но упала БД) — компенсация. ## Роли, активация, сидинг - **Роли динамические**: `AppRole : IdentityRole` + поле `MaxConfigs` (квота на число конфигов). Квота — **на роли, а не на `Plan`**. **У пользователя ровно одна роль**; квота = `MaxConfigs` его роли (`admin` — без лимита). Системные роли (`admin`/`user`) не удалять/переименовывать. - **Активация**: новый пользователь `IsActivated = false`, роль `user`. Конфиги может создавать только активированный. `ActivationRequest` (с комментарием заявителя) одобряет админ на сайте **или** в Telegram — одними и теми же командами (`ApproveActivationCommand`/`RejectActivationCommand`). - **Инбаунды по ролям**: `Inbound.AllowedRoles` (M:N). При создании конфига доменный инвариант проверяет: активирован + под квотой роли + роль входит в `AllowedRoles` инбаунда + нода включена. Проверку квоты делать **в транзакции** (гонки параллельных созданий). - **Понижение роли — грандфазеринг**: смена на меньшую квоту разрешена; лишние конфиги не отзываем, но новые нельзя до входа в квоту. - **Блокировка** (`AppUser.IsBlocked`): вход запрещён + все конфиги `Disabled` (отключить клиентов в 3x-ui); разблокировка — обратно. Действие в `AuditLog`. - **Аудит**: значимые действия (активация, блок, смена роли, отзыв, ноды/инбаунды) писать в `AuditLog` (append-only, источник Web/Telegram/System). - **Подписка**: агрегированная на юзера (`AppUser.SubscriptionToken`, все активные конфиги) + по конфигу. - **Ротация конфига** (`Rotate()`): новый UUID/ссылка, квоту не тратит. **Бот в MVP — read-only** по конфигам. - **Конфиг**: пользователь задаёт метку (`Label`) и лимит устройств (`DeviceLimit` → `limitIp` в 3x-ui, 0=без лимита), может редактировать. - **Самоудаление аккаунта** (`DELETE /api/auth/me`): отзыв всех конфигов + удаление данных, аудит анонимизируется. - **API без версионирования** в MVP (`/api` без `v1`). Подписка отдаёт `Subscription-Userinfo`. - **Тема**: светлая/тёмная/системная (Tailwind `dark`, выбор в localStorage). - **Инструкции + приложения**: отдельная страница инструкций; каталог `ClientApp` (админ CRUD: название/ссылка/ОС/порядок/вкл), пользователю `GET /api/apps` отдаётся сгруппированным по ОС. - **Вход — по `UserName`** (email в системе не используется вовсе; SMTP не нужен). Восстановление пароля: через привязанный Telegram (self-service), без привязки — сброс админом (`ResetUserPasswordCommand`). Пока Telegram не привязан — UI настойчиво предлагает его привязать. - **Сидинг из env**: идемпотентный `DbInitializer` на старте создаёт системные роли и учётку админа (`AdminSeed__Username`/`AdminSeed__Password`) из переменных окружения; каталог приложений `ClientApp` (если пуст) — из [`seed/client-apps.json`](seed/client-apps.json). Единый источник примера env — [`.env.example`](.env.example); при добавлении новой настройки обновляй и его. Секреты (пароль админа, JWT-ключ, `Telegram__BotToken`) — только через env/secret-store. - Telegram id админов — **отдельно от сидинга**, `Telegram__AdminTelegramUserIds` (через запятую), читается `TelegramOptions` напрямую при каждой проверке, не пишется в БД. Именно он авторизует админ-кнопки в боте и адресует уведомления о запросах активации. Seed-админ **не** привязывается к Telegram автоматически — привязка делается вручную в UI, как у любого пользователя. ## Telegram-бот - Бот — **presentation-адаптер**, не бизнес-слой. Хостится в процессе Api (`TelegramBotHostedService`, long polling). Хендлеры апдейтов вызывают **те же** CQRS-команды/запросы через собственный `ISender` (`GetMyConfigsQuery`, `LinkTelegramCommand`, `ApproveTelegramLoginCommand`, ...). - `Telegram.Bot` не проникает в Application/Domain — только в `Api/Telegram/`. - Passwordless-вход выпускает **те же** JWT/refresh, что и обычный логин. Требует привязки Telegram (в MVP — только привязка существующего аккаунта, регистрация из бота — backlog). - Токены привязки/входа: короткоживущие, одноразовые, высокоэнтропийные. `Telegram:BotToken` — секрет, не логировать. Панель должна работать и без бота (если токен не задан — бот просто не стартует). - Детали флоу — [telegram-bot.md](docs/telegram-bot.md). ## Единый контейнер - Один образ приложения: Api раздаёт REST (`/api`), SignalR (`/hubs`), хостит бота **и** статику SPA из `wwwroot` (fallback на `index.html`). Фронт и бек — один origin, база API — относительный `/api`. - Multi-stage Dockerfile: node (сборка фронта) → dotnet sdk (publish + копирование в `wwwroot`) → aspnet runtime. - docker-compose: `app` (единый образ) + `db` (PostgreSQL). В dev — Vite-прокси `/api`,`/hubs` на бэк. - Не вводи отдельный nginx-контейнер для статики без явной просьбы — это ломает требование единого контейнера. - **TLS — внешний** (прокси/шлюз вне compose); `app` отдаёт HTTP + доверяет `X-Forwarded-*` через `ForwardedHeaders` (иначе Secure-cookie/схема за прокси сломаются). Свой nginx/Caddy не добавляй. - **Миграции** применяются авто на старте (MVP). **CI** (GitHub Actions) — только build/test, без деплоя. ## Соглашения по коду Полный список — в [backend-conventions.md](docs/backend-conventions.md). Кратко: - Команды `Command`, запросы `Query`, + `Handler`/`Validator` (валидатор — не для каждой команды, только где есть что проверить). Application DTO — суффикс `Dto` (`FromDomain(...)` конвертирует из сущности); тела запросов Api-слоя — суффикс `Body`; тела ответов, которых нет как Application DTO — суффикс `ResponseDto`. - Application организована **по фичам** (feature folders) внутри слоёв. - Один публичный тип на файл, имя файла = имя типа (кроме вспомогательных `Body`/`ResponseDto` records — они живут в том же файле, что и класс эндпоинтов). Async-методы — суффикс `Async` + `CancellationToken`. - Секреты не логировать; логи — Serilog (`UseSerilogRequestLogging` + `Enrich.FromLogContext()`). Явного обогащения `UserId`/`NodeId`/`ConfigId`/`CorrelationId` пока нет — не полагайся на него при расследовании, пока не добавлено. - Ошибки API — единый `application/problem+json` (без Swashbuckle — нативный `Microsoft.AspNetCore.OpenApi`). ## Команды Backend (из `backend/`): ```bash dotnet build dotnet test dotnet run --project src/PnvPanel.Api dotnet ef migrations add --project src/PnvPanel.Infrastructure --startup-project src/PnvPanel.Api dotnet ef database update --project src/PnvPanel.Infrastructure --startup-project src/PnvPanel.Api dotnet format ``` Frontend (из `frontend/`): ```bash pnpm install pnpm dev pnpm build pnpm lint && pnpm typecheck pnpm gen:api # типы из OpenAPI-схемы бэкенда ``` Инфраструктура: ```bash docker compose up -d # api + postgres (+ web) ``` > Окружение: Windows, основная оболочка — **PowerShell**. Для POSIX-скриптов есть Bash-инструмент. > Пути — с учётом Windows. ## Принятые решения (зафиксированы) Ключевые развилки закрыты — см. [tech-stack.md](docs/tech-stack.md#принятые-решения-по-открытым-вопросам): CQRS — **собственный диспетчер** (не MediatR); **одна роль** на пользователя; секреты нод — **ASP.NET Data Protection**; тарифы `Plan` — **backlog** (в MVP без лимитов трафика/срока); i18n — **RU+EN** (react-i18next); Telegram — **long polling**, только **привязка** (не signup); история трафика — **простая таблица + TTL**; логирование — **Serilog**. ## Рабочие принципы - Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю. - Соблюдай границы слоёв — это главный инвариант проекта. Нарушение = ошибка ревью. - Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы. - Отвечай пользователю на русском (язык общения в проекте — русский).