# CLAUDE.md Инструкции для Claude Code при работе в этом репозитории. ## Что это **PnvPanel** — self-service портал для VPN-конфигураций (VLESS/VMess/Trojan/Shadowsocks): пользователи сами создают конфиги, админ управляет серверами и пользователями. Есть **Telegram-бот** (ссылка на сайт, просмотр конфигов, passwordless-вход). Бэкенд оркестрирует панели **3x-ui** через [`ThreeXui.Net`](https://github.com/mrleo1nid/ThreeXui.Net) и хранит проекцию домена в PostgreSQL. Живые обновления — SignalR. Поставка — **единый Docker-образ** (фронт+бек+бот) + PostgreSQL в compose. > Собрано и покрыто тестами, единый образ и compose-стек проверены живьём. Есть опциональный биллинг > (подписка по сроку, per-роль). Осознанно не реализовано: лимиты трафика на конфиг, полное > самообслуживание в боте — см. [tech-stack.md](docs/tech-stack.md). ## Документация (single source of truth) Прежде чем менять архитектуру или добавлять фичу — свериться с [`docs/`](docs/README.md): [Vision](docs/vision.md) · [Architecture](docs/architecture.md) · [Domain Model](docs/domain-model.md) · [Tech Stack](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) **Держи доки в синхроне с кодом.** Меняешь контракт/архитектуру — обнови соответствующий док в том же изменении. ## Стек - **Backend**: C# / .NET 10, ASP.NET Core Web API, Clean Architecture, CQRS (**собственный тонкий диспетчер**, без MediatR), EF Core 10 + Npgsql, ASP.NET Core Identity + JWT, SignalR, FluentValidation, Serilog. Маппинг DTO вручную (`FromDomain(...)`, без Mapster). OpenAPI — нативный `Microsoft.AspNetCore.OpenApi` + Scalar UI (без Swashbuckle). - **Frontend**: React 19 + Vite + TS, TanStack Query/Router, shadcn-стиль поверх Radix + Tailwind v4, Zustand (только auth), react-hook-form + zod, @microsoft/signalr. pnpm, oxlint. - **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. Обязательно: - Команды меняют состояние в транзакции (`UnitOfWorkBehavior`); запросы только читают (`AsNoTracking` + проекция в DTO). Диспетчер — **собственный** (`ISender`/`ICommandHandler`/`IQueryHandler`, DI-регистрация). - Управляемые ошибки — через `Result`, не исключениями (исключения только для исключительного). - Валидация — FluentValidation через `ValidationBehavior`; хендлер не перепроверяет формат ввода. - Всё I/O асинхронно, `CancellationToken` пробрасывается до EF/HTTP. Никаких `.Result`/`.Wait()`. - Nullable reference types включены; предупреждения анализаторов не игнорировать. ## Интеграция с 3x-ui - Только через порт `IXuiPanelGateway`. Один `BaseAddress` в `ThreeXui.Net`, а нод много → гейтвей держит **клиента per-node** (кэш по `NodeId`) из расшифрованных `NodeCredentials`. Детали — [architecture.md](docs/architecture.md#интеграция-с-3x-ui-threexuinet). - Пароли нод **шифруются at-rest** (`ISecretProtector`), расшифровка только в Infrastructure, никогда в логах/ответах. - Недоступность ноды → `Result.Failure`/`NodeStatus.Offline`, не 500 наружу. - Операции идемпотентны; при частичном сбое (клиент создан в панели, упала БД) — компенсация. ## Домен: роли, активация, конфиги Полная модель — [domain-model.md](docs/domain-model.md). Ключевые инварианты: - **Роли динамические** (`AppRole`), квоты на роли (не на `Plan`): `MaxConfigs` (число конфигов), `MaxIpLimit` (лимит одновременных IP клиента в 3x-ui, `limitIp`); -1 = без лимита. У пользователя ровно одна роль; `admin` — без лимитов. Системные роли `admin`/`user` не удалять/переименовывать. Понижение роли — грандфазеринг (лишние конфиги не отзываются, новые блокируются до входа в квоту). - **`limitIp`** выставляется автоматически по `MaxIpLimit` роли при создании клиента (`Create`/`Rotate`); панель не даёт настраивать его per-конфиг и не трогает уже созданных клиентов при смене роли/квоты. - **Активация**: новый пользователь `IsActivated=false`, роль `user`; неактивированному недоступны конфиги (создание/просмотр/редактирование/ротация/отзыв/ссылка/подписка), новости и каталог приложений — единая проверка `RequireActivationBehavior` по маркеру `IRequiresActivation` (не разбросанные `if` в хендлерах). На фронте до активации доступны только дашборд (форма запроса активации) и настройки аккаунта. `ActivationRequest` одобряет админ на сайте или в Telegram — одними командами. - **Инбаунды по ролям** (`Inbound.AllowedRoles`, M:N): создание конфига проверяет активацию + квоту роли (в транзакции — гонки параллельных созданий) + `AllowedRoles` + включённость ноды. - **Поддержка** (`SupportTicket`, доступна только активированным): баг-репорт/предложение (свободная форма + вложения-картинки, диск-хранилище `IFileStorage`) либо заявка на роль (существующая роль, кроме `admin`, либо параметры новой). `Open → Resolved → [Reopen]`, `Closed` — финал без возврата. Одобрение заявки на роль создаёт/назначает роль автоматически; полностью решается и в Telegram (инлайн-кнопки), баг-репорты — только уведомление-ссылка на сайт. - **Блокировка** (`AppUser.IsBlocked`): вход запрещён + все конфиги `Disabled` в 3x-ui; в `AuditLog`. - **Удаление пользователя** — свой аккаунт (`DELETE /api/auth/me`) или админом (`DELETE /api/admin/users/{id}`, себя удалить нельзя): отзыв всех конфигов в 3x-ui, затем `AppUser`; админский путь дополнительно пишет `AuditLog` (`UserDeleted`) и шлёт Telegram-DM. - **Аудит**: значимые действия (активация/блок/роль/отзыв/ноды/инбаунды/удаление) — `AuditLog` (append-only, источник Web/Telegram/System). - **Биллинг** (`AppRole.BillingEnabled`, недоступен для `admin`): пользователь оформляет `PaymentRequest` на 3/6/12 мес (сумма — по `PricingSettings`, заморожена на заявке), админ подтверждает/отклоняет на сайте или в Telegram (`pay:*`). Пока заявка `AwaitingConfirmation` — конфиги не гасятся, даже если срок истёк (не по вине пользователя, что админ не успел). Просрочка без заявки → `VpnConfig.Suspend()` (статус `Expired`, отдельно от `Disable()`/блокировки админом) — см. [domain-model.md](docs/domain-model.md#billing--подписка-по-сроку). - **Ротация конфига** (`Rotate()`) — новый UUID/ссылка, квоту не тратит. **Бот read-only** по конфигам. - **Подписка**: агрегированная (`AppUser.SubscriptionToken`) + по конфигу. API без версионирования (`/api`, без `v1`); подписка отдаёт `Subscription-Userinfo`. - **Вход по `UserName`** (email не используется). Восстановление пароля — через привязанный Telegram (self-service) либо сбросом админом (`ResetUserPasswordCommand`); без привязки UI настойчиво предлагает привязать. - **Сидинг**: идемпотентный `DbInitializer` создаёт системные роли + админа из env (`AdminSeed__Username`/`Password`); каталог `ClientApp` — из [`seed/client-apps.json`](seed/client-apps.json). Источник примера env — [`.env.example`](.env.example), обновляй при новых настройках. Секреты — только через env. `Telegram__AdminTelegramUserIds` — отдельно от сидинга, не пишется в БД, читается напрямую из `TelegramOptions`. ## Telegram-бот Детали флоу — [telegram-bot.md](docs/telegram-bot.md). - Presentation-адаптер, не бизнес-слой: `TelegramBotHostedService` (long polling) вызывает **те же** CQRS-команды через `ISender`. `Telegram.Bot` не проникает в Application/Domain — только `Api/Telegram/`. - Passwordless-вход выпускает те же JWT/refresh, что и обычный логин; требует привязки Telegram (с сайта) либо регистрации прямо из бота (`RegisterViaTelegramCommand` — логин `@username`/id, пароль генерируется и приходит в чат один раз). Новый аккаунт — роль `user`, `IsActivated=false`, активация как обычно. - Токены привязки/входа — короткоживущие одноразовые. `Telegram:BotToken` — секрет, не логировать. Без токена бот просто не стартует — панель работает и без него. ## Единый контейнер - Один образ: 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 — внешний**; `app` отдаёт HTTP + доверяет `X-Forwarded-*` (`ForwardedHeaders`). Свой nginx/Caddy не добавляй. - Миграции применяются авто на старте. CI (GitHub Actions) — только build/test, без деплоя. ## Соглашения по коду Полный список — [backend-conventions.md](docs/backend-conventions.md). Кратко: - `Command`/`Query` + `Handler`/`Validator` (валидатор — где есть что проверить). DTO — суффикс `Dto`; тела запросов Api — `Body`; тела ответов без Application DTO — `ResponseDto`. Application — по фичам (feature folders). - Один публичный тип на файл = имя файла (кроме `Body`/`ResponseDto` в файле эндпоинтов). Async-методы — суффикс `Async` + `CancellationToken`. - Секреты не логировать; логи — Serilog. Явного обогащения `UserId`/`NodeId`/`ConfigId`/`CorrelationId` пока нет — не полагайся на него при расследовании. - Ошибки API — единый `application/problem+json`. ## Команды 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-инструмент. ## Ключевые решения См. [tech-stack.md](docs/tech-stack.md#ключевые-решения-по-домену-и-поведению): CQRS — собственный диспетчер (не MediatR); одна роль на пользователя; секреты нод — ASP.NET Data Protection; биллинг опционален per-роль (недоступен для `admin`); i18n — RU+EN (react-i18next); Telegram — long polling, только привязка (не signup); история трафика — простая таблица + TTL; логирование — Serilog. ## Рабочие принципы - Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю. - Соблюдай границы слоёв — главный инвариант проекта. Нарушение = ошибка ревью. - Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы. - Отвечай пользователю на русском.