Files
PnvPanel/CLAUDE.md
T
Leonid Pershin 24d9ea1099
CI / Backend (build + test) (push) Successful in 1m14s
CI / Frontend (lint + typecheck + build) (push) Successful in 30s
Implement role and user management enhancements
- 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.
2026-07-13 07:18:13 +03:00

14 KiB
Raw Blame History

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.

Рабочие принципы

  • Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
  • Соблюдай границы слоёв — главный инвариант проекта. Нарушение = ошибка ревью.
  • Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
  • Отвечай пользователю на русском.