- Added MaxIpLimit to roles, allowing for the configuration of simultaneous IP limits for users. - Updated role creation and update commands to include MaxIpLimit, ensuring proper handling in the application logic. - Enhanced user management by introducing a DELETE endpoint for user accounts, with appropriate checks to prevent self-deletion. - Updated documentation to reflect changes in role and user management, clarifying the new IP limit functionality and user deletion process. - Adjusted related tests to cover new functionality and ensure robust validation of role and user management features.
14 KiB
CLAUDE.md
Инструкции для Claude Code при работе в этом репозитории.
Что это
PnvPanel — self-service портал для VPN-конфигураций (VLESS/VMess/Trojan/Shadowsocks): пользователи
сами создают конфиги, админ управляет серверами и пользователями. Есть Telegram-бот (ссылка на
сайт, просмотр конфигов, passwordless-вход). Бэкенд оркестрирует панели 3x-ui через
ThreeXui.Net и хранит проекцию домена в PostgreSQL.
Живые обновления — SignalR. Поставка — единый Docker-образ (фронт+бек+бот) + PostgreSQL в compose.
Собрано и покрыто тестами, единый образ и compose-стек проверены живьём. Осознанно не реализовано: тарифы, лимиты трафика/срока на конфиг, полное самообслуживание в боте — см. tech-stack.md.
Документация (single source of truth)
Прежде чем менять архитектуру или добавлять фичу — свериться с docs/:
Vision · Architecture · Domain Model ·
Tech Stack · Backend Conventions ·
Frontend · Telegram Bot · API Design
Держи доки в синхроне с кодом. Меняешь контракт/архитектуру — обнови соответствующий док в том же изменении.
Стек
- 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<T>, не исключениями (исключения только для исключительного). - Валидация — FluentValidation через
ValidationBehavior; хендлер не перепроверяет формат ввода. - Всё I/O асинхронно,
CancellationTokenпробрасывается до EF/HTTP. Никаких.Result/.Wait(). - Nullable reference types включены; предупреждения анализаторов не игнорировать.
Интеграция с 3x-ui
- Только через порт
IXuiPanelGateway. ОдинBaseAddressвThreeXui.Net, а нод много → гейтвей держит клиента per-node (кэш поNodeId) из расшифрованныхNodeCredentials. Детали — architecture.md. - Пароли нод шифруются at-rest (
ISecretProtector), расшифровка только в Infrastructure, никогда в логах/ответах. - Недоступность ноды →
Result.Failure/NodeStatus.Offline, не 500 наружу. - Операции идемпотентны; при частичном сбое (клиент создан в панели, упала БД) — компенсация.
Домен: роли, активация, конфиги
Полная модель — domain-model.md. Ключевые инварианты:
- Роли динамические (
AppRole), квоты на роли (не наPlan):MaxConfigs(число конфигов),MaxIpLimit(лимит одновременных IP клиента в 3x-ui,limitIp); -1 = без лимита. У пользователя ровно одна роль;admin— без лимитов. Системные ролиadmin/userне удалять/переименовывать. Понижение роли — грандфазеринг (лишние конфиги не отзываются, новые блокируются до входа в квоту). limitIpвыставляется автоматически поMaxIpLimitроли при создании клиента (Create/Rotate); панель не даёт настраивать его per-конфиг и не трогает уже созданных клиентов при смене роли/квоты.- Активация: новый пользователь
IsActivated=false, рольuser; конфиги создаёт только активированный.ActivationRequestодобряет админ на сайте или в Telegram — одними командами. - Инбаунды по ролям (
Inbound.AllowedRoles, M:N): создание конфига проверяет активацию + квоту роли (в транзакции — гонки параллельных созданий) +AllowedRoles+ включённость ноды. - Блокировка (
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). - Ротация конфига (
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. Источник примера env —.env.example, обновляй при новых настройках. Секреты — только через env.Telegram__AdminTelegramUserIds— отдельно от сидинга, не пишется в БД, читается напрямую изTelegramOptions.
Telegram-бот
Детали флоу — 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. Кратко:
<Verb><Noun>Command/<Get|List><Noun>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/):
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/):
pnpm install
pnpm dev
pnpm build
pnpm lint && pnpm typecheck
pnpm gen:api # типы из OpenAPI-схемы бэкенда
Инфраструктура:
docker compose up -d # api + postgres (+ web)
Окружение: Windows, основная оболочка — PowerShell. Для POSIX-скриптов есть Bash-инструмент.
Ключевые решения
См. tech-stack.md: CQRS — собственный
диспетчер (не MediatR); одна роль на пользователя; секреты нод — ASP.NET Data Protection; тарифы Plan
не реализованы; i18n — RU+EN (react-i18next); Telegram — long polling, только привязка (не signup);
история трафика — простая таблица + TTL; логирование — Serilog.
Рабочие принципы
- Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
- Соблюдай границы слоёв — главный инвариант проекта. Нарушение = ошибка ревью.
- Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
- Отвечай пользователю на русском.