Files
PnvPanel/CLAUDE.md
T
Leonid Pershin bea2b5fcf7
CI / Backend (build + test) (push) Successful in 1m24s
CI / Frontend (lint + typecheck + build) (push) Successful in 30s
Refactor VPN configuration handling to remove device limit management
- Updated the VPN configuration commands and handlers to eliminate the device limit parameter, simplifying the configuration process.
- Adjusted related API documentation to reflect the removal of device limit management, clarifying that this setting is now handled directly in the 3x-ui by node administrators.
- Enhanced the overall codebase by removing unnecessary device limit references across various components, ensuring a cleaner and more maintainable code structure.
2026-07-02 23:21:26 +03:00

196 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
> Бэкенд и фронтенд полностью собраны и покрыты тестами (134 бэкенд-теста), единый Docker-образ и
> docker-compose стек проверены живьём. Осознанно не реализовано: тарифы, лимиты трафика/срока на
> конфиг, полное самообслуживание в боте — см. [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 (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` установлены, но не
используются (статистика — карточками, таблицы — руками).
- **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<T>`, не исключениями. Исключения — только для исключительного.
- Валидация — 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<Guid>` + поле `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/ссылка, квоту не тратит. **Бот — read-only** по конфигам.
- **Конфиг**: пользователь задаёт метку (`Label`), может редактировать. Лимит устройств (`limitIp` в 3x-ui) панелью не управляется — более сложная настройка, задаётся при необходимости администратором ноды напрямую в 3x-ui.
- **Самоудаление аккаунта** (`DELETE /api/auth/me`): отзыв всех конфигов + удаление данных, аудит анонимизируется.
- **API без версионирования** (`/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 —
либо привязка существующего аккаунта (с сайта), либо регистрация прямо из бота (`RegisterViaTelegramCommand`):
логин — Telegram `@username`, при отсутствии/занятости — Telegram id; пароль генерируется и
присылается в чат один раз (логин можно сменить позже в Настройках, `ChangeUserNameCommand`).
Новый аккаунт получает роль `user` и `IsActivated = false` — активация нужна как обычно.
- Токены привязки/входа: короткоживущие, одноразовые, высокоэнтропийные. `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 не добавляй.
- **Миграции** применяются авто на старте. **CI** (GitHub Actions) — только build/test, без деплоя.
## Соглашения по коду
Полный список — в [backend-conventions.md](docs/backend-conventions.md). Кратко:
- Команды `<Verb><Noun>Command`, запросы `<Get/List><Noun>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 <Name> --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` не реализованы (нет лимитов трафика/срока на конфиг);
i18n — **RU+EN** (react-i18next); Telegram — **long polling**, только **привязка** (не signup);
история трафика — **простая таблица + TTL**; логирование — **Serilog**.
## Рабочие принципы
- Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
- Соблюдай границы слоёв — это главный инвариант проекта. Нарушение = ошибка ревью.
- Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
- Отвечай пользователю на русском (язык общения в проекте — русский).