Files
PnvPanel/CLAUDE.md
T
Leonid Pershin b5630b2685
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 31s
Implement support ticket system with role request and bug report functionalities
- Introduced a new support ticket system allowing users to submit bug reports and role requests.
- Implemented endpoints for creating, updating, and managing support tickets, including file attachments.
- Enhanced Telegram bot integration to handle role requests directly within the bot, enabling admins to approve or reject requests without accessing the website.
- Updated database schema to include support ticket entities and their relationships.
- Improved API documentation to reflect new support ticket endpoints and their usage.
- Added necessary localization for support ticket features in both Russian and English.
2026-07-14 06:49:05 +03:00

181 lines
15 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-вход). Бэкенд оркестрирует панели **3x-ui** через
[`ThreeXui.Net`](https://github.com/mrleo1nid/ThreeXui.Net) и хранит проекцию домена в PostgreSQL.
Живые обновления — SignalR. Поставка — **единый Docker-образ** (фронт+бек+бот) + PostgreSQL в compose.
> Собрано и покрыто тестами, единый образ и 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, 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](docs/architecture.md#интеграция-с-3x-ui-threexuinet).
- Пароли нод **шифруются at-rest** (`ISecretProtector`), расшифровка только в Infrastructure, никогда в логах/ответах.
- Недоступность ноды → `Result.Failure`/`NodeStatus.Offline`, не 500 наружу.
- Операции идемпотентны; при частичном сбое (клиент создан в панели, упала БД) — компенсация.
## Домен: роли, активация, конфиги
Полная модель — [domain-model.md](docs/domain-model.md). Ключевые инварианты:
- **Роли динамические** (`AppRole`), квоты на роли (не на `Plan`): `MaxConfigs` (число конфигов),
`MaxIpLimit` (лимит одновременных IP клиента в 3x-ui, `limitIp`); -1 = без лимита. У пользователя
ровно одна роль; `admin` — без лимитов. Системные роли `admin`/`user` не удалять/переименовывать.
Понижение роли — грандфазеринг (лишние конфиги не отзываются, новые блокируются до входа в квоту).
- **`limitIp`** выставляется автоматически по `MaxIpLimit` роли при создании клиента (`Create`/`Rotate`);
панель не даёт настраивать его per-конфиг и не трогает уже созданных клиентов при смене роли/квоты.
- **Активация**: новый пользователь `IsActivated=false`, роль `user`; неактивированному недоступны
конфиги (создание/просмотр/редактирование/ротация/отзыв/ссылка/подписка), новости и каталог
приложений — единая проверка `RequireActivationBehavior` по маркеру `IRequiresActivation` (не
разбросанные `if` в хендлерах). На фронте до активации доступны только дашборд (форма запроса
активации) и настройки аккаунта. `ActivationRequest` одобряет админ на сайте или в Telegram —
одними командами.
- **Инбаунды по ролям** (`Inbound.AllowedRoles`, M:N): создание конфига проверяет активацию + квоту роли
(в транзакции — гонки параллельных созданий) + `AllowedRoles` + включённость ноды.
- **Поддержка** (`SupportTicket`, доступна только активированным): баг-репорт/предложение (свободная
форма + вложения-картинки, диск-хранилище `IFileStorage`) либо заявка на роль (существующая роль,
кроме `admin`, либо параметры новой). `Open → Resolved → [Reopen]`, `Closed` — финал без возврата.
Одобрение заявки на роль создаёт/назначает роль автоматически; полностью решается и в Telegram
(инлайн-кнопки), баг-репорты — только уведомление-ссылка на сайт.
- **Блокировка** (`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`](seed/client-apps.json).
Источник примера env — [`.env.example`](.env.example), обновляй при новых настройках. Секреты — только через env.
`Telegram__AdminTelegramUserIds` — отдельно от сидинга, не пишется в БД, читается напрямую из `TelegramOptions`.
## Telegram-бот
Детали флоу — [telegram-bot.md](docs/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](docs/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/`):
```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-инструмент.
## Ключевые решения
См. [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.
## Рабочие принципы
- Не начинай крупную реализацию без сверки с доками и, при неоднозначности, без вопроса пользователю.
- Соблюдай границы слоёв — главный инвариант проекта. Нарушение = ошибка ревью.
- Обновляй документацию вместе с кодом. Не коммить и не пуши без явной просьбы.
- Отвечай пользователю на русском.