- Added a new endpoint for changing usernames, allowing users to update their login credentials via the API. - Integrated username change functionality into the settings page, providing a user-friendly interface for this action. - Enhanced the Telegram bot to support user registration directly through the bot, including username generation and password delivery. - Updated documentation to reflect the new username change endpoint and registration flow through the Telegram bot.
18 KiB
CLAUDE.md
Инструкции для Claude Code при работе в этом репозитории.
Что это
PnvPanel — self-service портал для VPN-конфигураций. Пользователи сами создают себе конфиги
(VLESS/VMess/Trojan/Shadowsocks), админ управляет серверами и пользователями. Есть Telegram-бот
(ссылка на сайт, просмотр конфигов, passwordless-вход через привязку Telegram). Бэкенд оркестрирует
панели 3x-ui через библиотеку ThreeXui.Net и
хранит свою проекцию домена в PostgreSQL. Живые обновления — по SignalR. Приложение (фронт + бек +
бот) поставляется единым Docker-образом; PostgreSQL — отдельным контейнером в compose.
Статус: MVP реализован и работает. Бэкенд (M0–M8) и фронтенд полностью собраны, покрыты тестами (134 бэкенд-теста), единый Docker-образ и docker-compose стек проверены живьём. История этапов —
docs/roadmap.md; там же — раздел Backlog с тем, что осознанно оставлено за рамками MVP (тарифы, лимиты трафика/срока на конфиг, полное самообслуживание в боте и т.д.).
Документация (single source of truth)
Прежде чем менять архитектуру или добавлять фичу — свериться с docs/:
- Vision · Architecture · Domain Model
- Tech Stack (ADR) · Backend Conventions
- Frontend · Telegram Bot · API Design · Roadmap
Держи доки в синхроне с кодом. Меняешь контракт/архитектуру — обнови соответствующий док в том же изменении.
Стек
- 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установлены, но не используются в MVP (статистика — карточками, таблицы — руками). - 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. - Пароли нод шифруются 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на старте создаёт системные роли и учётку админа (AdminSeed__Username/AdminSeed__Password) из переменных окружения; каталог приложенийClientApp(если пуст) — изseed/client-apps.json. Единый источник примера env —.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.
Единый контейнер
- Один образ приложения: 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. Кратко:
- Команды
<Verb><Noun>Command, запросы<Get/List><Noun>Query, +Handler/Validator(валидатор — не для каждой команды, только где есть что проверить). Application DTO — суффиксDto(FromDomain(...)конвертирует из сущности); тела запросов Api-слоя — суффиксBody; тела ответов, которых нет как Application DTO — суффиксResponseDto. - Application организована по фичам (feature folders) внутри слоёв.
- Один публичный тип на файл, имя файла = имя типа (кроме вспомогательных
Body/ResponseDtorecords — они живут в том же файле, что и класс эндпоинтов). Async-методы — суффиксAsync+CancellationToken. - Секреты не логировать; логи — Serilog (
UseSerilogRequestLogging+Enrich.FromLogContext()). Явного обогащенияUserId/NodeId/ConfigId/CorrelationIdпока нет — не полагайся на него при расследовании, пока не добавлено. - Ошибки API — единый
application/problem+json(без Swashbuckle — нативныйMicrosoft.AspNetCore.OpenApi).
Команды
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-инструмент. Пути — с учётом Windows.
Принятые решения (зафиксированы)
Ключевые развилки закрыты — см. tech-stack.md:
CQRS — собственный диспетчер (не MediatR); одна роль на пользователя; секреты нод —
ASP.NET Data Protection; тарифы Plan — backlog (в MVP без лимитов трафика/срока);
i18n — RU+EN (react-i18next); Telegram — long polling, только привязка (не signup);
история трафика — простая таблица + TTL; логирование — Serilog.
Рабочие принципы
- Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
- Соблюдай границы слоёв — это главный инвариант проекта. Нарушение = ошибка ревью.
- Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
- Отвечай пользователю на русском (язык общения в проекте — русский).