180 lines
16 KiB
Markdown
180 lines
16 KiB
Markdown
# 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.
|
||
|
||
> **Статус: проектирование.** Код ещё не написан. Актуальны только документация и этот файл.
|
||
> При старте реализации следуй [`docs/roadmap.md`](docs/roadmap.md) (этапы M0…M6).
|
||
|
||
## Документация (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, Mapster, **Serilog** (логирование).
|
||
- **Frontend**: React 19 + Vite + TypeScript, TanStack Query/Router, shadcn/ui + Tailwind CSS v4,
|
||
Zustand, react-hook-form + zod, @microsoft/signalr, Recharts. Пакетный менеджер — pnpm.
|
||
- **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/ссылка, квоту не тратит. **Бот в 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` на старте создаёт системные роли и учётку админа
|
||
(username/пароль/Telegram id) из переменных окружения; каталог приложений `ClientApp` (если пуст) —
|
||
из [`seed/client-apps.json`](seed/client-apps.json). Единый источник примера env — [`.env.example`](.env.example);
|
||
при добавлении новой настройки обновляй и его. Секреты (пароль админа, JWT-ключ, BotToken) — только через env/secret-store.
|
||
- Telegram id админов (`AdminSeed__TelegramUserIds`) авторизуют админ-действия в боте и получают
|
||
уведомления о запросах активации.
|
||
|
||
## 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). Кратко:
|
||
|
||
- Команды `<Verb><Noun>Command`, запросы `<Get/List><Noun>Query`, + `Handler`/`Validator`. DTO — суффикс `Dto`.
|
||
- Application организована **по фичам** (feature folders) внутри слоёв.
|
||
- Один публичный тип на файл, имя файла = имя типа. Async-методы — суффикс `Async` + `CancellationToken`.
|
||
- Секреты не логировать; логи структурные (Serilog) с `UserId`/`NodeId`/`ConfigId`/`CorrelationId`.
|
||
- Ошибки API — единый `ProblemDetails`.
|
||
|
||
## Команды (ожидаемые — появятся по мере создания проектов)
|
||
|
||
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` — **backlog** (в MVP без лимитов трафика/срока);
|
||
i18n — **RU+EN** (react-i18next); Telegram — **long polling**, только **привязка** (не signup);
|
||
история трафика — **простая таблица + TTL**; логирование — **Serilog**.
|
||
|
||
## Рабочие принципы
|
||
|
||
- Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
|
||
- Соблюдай границы слоёв — это главный инвариант проекта. Нарушение = ошибка ревью.
|
||
- Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
|
||
- Отвечай пользователю на русском (язык общения в проекте — русский).
|