Refactor environment configuration and update documentation for MVP status
- Removed deprecated Telegram user ID configuration from `.env.example` and added a new setting for admin Telegram user IDs. - Updated `CLAUDE.md` to reflect the current MVP status, detailing completed features and testing coverage. - Enhanced `README.md` with quick start instructions for Docker setup and clarified project status. - Revised API design documentation to include updated error handling and request/response structures. - Improved frontend documentation to outline the project structure and technologies used.
This commit is contained in:
+110
-58
@@ -2,52 +2,74 @@
|
||||
|
||||
## Структура решения
|
||||
|
||||
Solution-файл — **`PnvPanel.slnx`** (новый XML-формат dotnet CLI, не классический `.sln`).
|
||||
|
||||
```
|
||||
backend/
|
||||
PnvPanel.sln
|
||||
PnvPanel.slnx
|
||||
src/
|
||||
PnvPanel.Domain/
|
||||
Common/ # Entity, AggregateRoot, IDomainEvent, ValueObject base
|
||||
Nodes/ # Node, NodeCredentials, NodeStatus, события
|
||||
Inbounds/ # Inbound, VpnProtocol
|
||||
Configs/ # VpnConfig, ConfigStatus, TrafficLimit, события
|
||||
Plans/ # Plan
|
||||
Exceptions/ # DomainException и наследники
|
||||
Common/ # Entity (единственный базовый класс — без AggregateRoot/IDomainEvent)
|
||||
Activation/ # ActivationRequest, ActivationStatus
|
||||
Apps/ # ClientApp, OsPlatform
|
||||
Audit/ # AuditLog, AuditSource
|
||||
Configs/ # VpnConfig, ConfigStatus, TrafficSample
|
||||
Inbounds/ # Inbound, VpnProtocol
|
||||
Nodes/ # Node, NodeCredentials (VO), NodeStatus
|
||||
Telegram/ # TelegramLinkToken, TelegramLoginRequest, TelegramLoginStatus
|
||||
Exceptions/ # DomainException
|
||||
PnvPanel.Application/
|
||||
Common/
|
||||
Behaviors/ # Validation, Logging, UnitOfWork, Authorization
|
||||
Interfaces/ # IAppDbContext, IXuiPanelGateway, ICurrentUser, IRealtimeNotifier, ...
|
||||
Behaviors/ # ValidationBehavior, LoggingBehavior, UnitOfWorkBehavior (нет Authorization-поведения)
|
||||
Interfaces/ # IAppDbContext, IXuiPanelGateway, ICurrentUser, IIdentityService,
|
||||
# ISecretProtector, IRealtimeNotifier, ITelegramNotifier, IRoleService
|
||||
Messaging/ # ISender, ICommand<T>, IQuery<T>, ICommandHandler<,>, IQueryHandler<,>, IPipelineBehavior<,>
|
||||
Models/ # Result<T>, Error, PagedList<T>
|
||||
Mapping/ # Mapster-конфиги
|
||||
Auth/ # Register/Login/Refresh (Commands, Handlers, Validators, DTOs)
|
||||
Configs/ # CreateVpnConfig, EditVpnConfig, RotateVpnConfig, RevokeVpnConfig, GetMyConfigs, GetConfigLink, GetSubscription, ...
|
||||
Nodes/ # RegisterNode, SyncNode, ListNodes, ...
|
||||
Inbounds/ # PublishInbound, ListInbounds, ...
|
||||
Apps/ # (admin) CRUD каталога ClientApp; GetApps (по ОС) для юзера
|
||||
Admin/ # ListUsers, BlockUser/UnblockUser, ChangeUserRole, GetStats, Audit, ...
|
||||
Models/ # Result, Result<T>, Error, PagedList<T>, RoleQuota
|
||||
Activation/ # RequestActivationCommand, GetActivationStatusQuery (пользовательские)
|
||||
Admin/
|
||||
Activation/ # ListActivationRequestsQuery, Approve/RejectActivationCommand
|
||||
Apps/ # CRUD ClientApp
|
||||
Audit/ # ListAuditLogsQuery
|
||||
Inbounds/ # ListInbounds, PublishInbound
|
||||
Nodes/ # RegisterNode, UpdateNode, DeleteNode, SyncNode, ProbeNode, ListNodes
|
||||
Roles/ # ListRoles, CreateRole, UpdateRole, DeleteRole
|
||||
Stats/ # GetStatsQuery
|
||||
Users/ # ListUsers, BlockUser/UnblockUser, ChangeUserRole, ResetUserPassword,
|
||||
# ForceRevokeConfig, GetUserConfigs
|
||||
Apps/ # ListAppsQuery (по ОС, для юзера)
|
||||
Auth/
|
||||
ChangePassword/, DeleteMyAccount/, Login/, Logout/, Me/, Refresh/, Register/
|
||||
Configs/
|
||||
Create/, Edit/, Rotate/, Revoke/, GetMyConfigs/, GetConfigLink/, GetMySubscription/,
|
||||
ListAvailableInbounds/
|
||||
Subscriptions/ # SubscriptionDto, GetUserSubscriptionQuery, GetConfigSubscriptionQuery
|
||||
Telegram/ # LinkTelegramCommand, CreateLinkTokenCommand, CreateLoginRequestCommand,
|
||||
# Approve/RejectTelegramLoginCommand, GetLoginRequestStatusQuery, ...
|
||||
# Telegram/Bot/ — контракт для бота (не сам Telegram.Bot)
|
||||
PnvPanel.Infrastructure/
|
||||
Persistence/
|
||||
AppDbContext.cs
|
||||
AppDbContext.cs # : IdentityDbContext<AppUser, AppRole, Guid>, IAppDbContext
|
||||
Configurations/ # IEntityTypeConfiguration<T>
|
||||
Migrations/
|
||||
Identity/ # AppUser, AppRole, JwtTokenService, RefreshToken
|
||||
Xui/ # XuiPanelGateway, XuiClientFactory (per-node)
|
||||
Realtime/ # SignalRRealtimeNotifier
|
||||
BackgroundJobs/ # TrafficSyncService, NodeHealthCheckService
|
||||
Identity/ # AppUser, AppRole, JwtTokenService, RefreshTokenService, RoleService,
|
||||
# DbInitializer (сидинг), IdentityService, CurrentUser
|
||||
Xui/ # XuiPanelGateway (единственный файл — кэш клиентов per-node внутри него)
|
||||
BackgroundJobs/ # TrafficSyncService, NodeHealthCheckService, TrafficRetentionService
|
||||
Security/ # DataProtectionSecretProtector
|
||||
DependencyInjection.cs
|
||||
Telegram/ # TelegramNotifier, TelegramOptions
|
||||
DependencyInjection.cs # AddInfrastructure(...)
|
||||
PnvPanel.Api/
|
||||
Endpoints/ # AuthEndpoints, ConfigEndpoints, NodeEndpoints, AdminEndpoints, SubscriptionEndpoints
|
||||
Hubs/ # PanelHub
|
||||
Middleware/ # ExceptionHandling, RequestCorrelation
|
||||
Extensions/ # AddApiServices, UseApiPipeline
|
||||
Program.cs
|
||||
Endpoints/ # 12 файлов, см. backend-conventions.md ниже и api-design.md
|
||||
Hubs/ # PanelHub, SignalRRealtimeNotifier (реализация IRealtimeNotifier — здесь,
|
||||
# не в Infrastructure, т.к. нужен IHubContext<PanelHub>)
|
||||
Telegram/ # TelegramBotHostedService, PnvBotUpdateHandler, TelegramNotifier
|
||||
Common/ # RateLimiting (константы политик), ResultExtensions (Result -> IResult)
|
||||
Program.cs # DI composition root, pipeline (нет отдельных Middleware/Extensions папок)
|
||||
appsettings*.json
|
||||
tests/
|
||||
PnvPanel.Domain.Tests/
|
||||
PnvPanel.Application.Tests/
|
||||
PnvPanel.Integration.Tests/ # Testcontainers PostgreSQL
|
||||
PnvPanel.Domain.Tests/ # 54 теста
|
||||
PnvPanel.Application.Tests/ # 71 тест
|
||||
PnvPanel.IntegrationTests/ # 9 тестов, Testcontainers.PostgreSql + WebApplicationFactory<Program>
|
||||
```
|
||||
|
||||
Организация Application — **по фичам** (feature folders), внутри слоёв Clean Architecture.
|
||||
@@ -57,27 +79,43 @@ backend/
|
||||
- Классы/методы/свойства — `PascalCase`; параметры/локальные — `camelCase`; приватные поля — `_camelCase`.
|
||||
- Команды — `<Verb><Noun>Command` (`CreateVpnConfigCommand`), запросы — `<Get/List><Noun>Query`.
|
||||
- Хендлеры — `<Command/Query>Handler`; валидаторы — `<Command/Query>Validator`.
|
||||
- DTO — суффикс `Dto` (`VpnConfigDto`); ответы эндпоинтов — `Response`, тела запросов — `Request`.
|
||||
- **DTO** (Application-слой, возвращаются из `Result<T>`) — суффикс `Dto` (`VpnConfigDto`, `NodeDto`);
|
||||
конвертация из сущности — статический `FromDomain(entity, ...)` на самом DTO.
|
||||
- **Тела запросов** (Api-слой, только для JSON-полей, которых нет в готовой команде) — суффикс `Body`
|
||||
(`CreateConfigBody`, `UpdateNodeBody`) либо сам record команды биндится напрямую как тело
|
||||
(`RegisterCommand`, `LoginCommand`).
|
||||
- **Тела ответов, которых нет как Application DTO** (например, потому что Api-слой добавляет
|
||||
вычисляемое поле — абсолютный URL из токена) — суффикс `ResponseDto` (`AuthResponseDto`,
|
||||
`ConfigLinkResponseDto`, `TelegramLoginStatusResponseDto`), определяются прямо в файле эндпоинта.
|
||||
- Async-методы — суффикс `Async`, всегда принимают `CancellationToken`.
|
||||
- Один публичный тип на файл; имя файла = имя типа.
|
||||
- Один публичный тип на файл — с исключением: Api-слой держит вспомогательные `Body`/`ResponseDto`
|
||||
records в том же файле, что и класс эндпоинтов, который их использует (не выносятся отдельно).
|
||||
|
||||
## Паттерны
|
||||
|
||||
- **Rich domain model**: инварианты в сущностях (приватные сеттеры, фабричные методы `Node.Create(...)`,
|
||||
- **Rich domain model**: инварианты в сущностях (приватные сеттеры, фабричные методы `Node.Register(...)`,
|
||||
поведенческие методы `config.Revoke()`), а не анемичные DTO-сущности.
|
||||
- **CQRS через собственный диспетчер**: хендлеры реализуют `ICommandHandler<TCommand,TResult>` /
|
||||
`IQueryHandler<,>`; `ISender` резолвит их из DI и прогоняет через `IPipelineBehavior<,>`
|
||||
(валидация, транзакция, логирование). Без внешних CQRS-библиотек.
|
||||
- **Порты в Application, адаптеры в Infrastructure**: никакого `Npgsql`/`SignalR`/`ThreeXui.Net` в Application/Domain.
|
||||
- **`IAppDbContext`** экспонирует `DbSet<>` и `SaveChangesAsync`; хендлеры пишут LINQ напрямую
|
||||
(репозитории — только для сложной агрегатной логики).
|
||||
- **Result-модель**: команды/запросы возвращают `Result<T>`; эндпоинт маппит в HTTP (`.Match(...)`).
|
||||
- **Транзакция на команду**: `UnitOfWorkBehavior` оборачивает выполнение команды в транзакцию.
|
||||
- **Валидация**: `ValidationBehavior` до хендлера; хендлер не проверяет формат ввода повторно.
|
||||
- **Идемпотентность**: команды к 3x-ui устойчивы к повторам; при частичном сбое — компенсация
|
||||
(создали клиента в панели, но упала БД → удалить клиента, вернуть ошибку).
|
||||
- **Оптимистичная блокировка**: на изменяемых сущностях (нода, конфиг) — `xmin`/rowversion, чтобы
|
||||
параллельные правки не затирали друг друга.
|
||||
(`ValidationBehavior` → `LoggingBehavior` → `UnitOfWorkBehavior`). Без внешних CQRS-библиотек, без
|
||||
доменных событий — хендлер сам вызывает нужные порты (realtime/Telegram/аудит) синхронно.
|
||||
- **Порты в Application, адаптеры в Infrastructure/Api**: никакого `Npgsql`/`SignalR`/`ThreeXui.Net` в
|
||||
Application/Domain — только интерфейсы, реализации могут жить и в `Infrastructure`, и в `Api`
|
||||
(`IRealtimeNotifier` реализован в `Api/Hubs`, т.к. завязан на `IHubContext<PanelHub>`).
|
||||
- **`IAppDbContext`** экспонирует `DbSet<>` и `SaveChangesAsync`; хендлеры пишут LINQ напрямую —
|
||||
выделенных репозиториев нет вообще.
|
||||
- **Result-модель**: команды/запросы возвращают `Result`/`Result<T>`; `ResultExtensions.ToHttpResult()`
|
||||
мапит `Error.Type` в HTTP-статус на границе Api.
|
||||
- **Транзакция на команду**: `UnitOfWorkBehavior` вызывает `SaveChangesAsync` после хендлера команды
|
||||
(не запросов) — отдельной BEGIN/COMMIT-транзакции вокруг этого в MVP нет, полагаемся на то, что
|
||||
один `SaveChanges` уже атомарен для одной единицы работы.
|
||||
- **Компенсация при частичном сбое**: если клиент успешно создан в 3x-ui, а `SaveChanges` в БД упал —
|
||||
хендлер вызывает `RemoveClientAsync`, чтобы не оставить сироту в панели.
|
||||
- **Защита от гонок на квоте — `pg_advisory_xact_lock`**, не оптимистичная блокировка: перед проверкой
|
||||
квоты роли `CreateVpnConfigCommandHandler` берёт `pg_advisory_xact_lock(hashtext(userId))` —
|
||||
сериализует параллельные попытки создать конфиг одним и тем же пользователем в рамках транзакции.
|
||||
Отдельного rowversion/`xmin` на `Node`/`VpnConfig` нет (единственный `IsConcurrencyToken` в схеме —
|
||||
штатный `ConcurrencyStamp` таблиц Identity).
|
||||
|
||||
## Работа с 3x-ui
|
||||
|
||||
@@ -92,25 +130,39 @@ backend/
|
||||
|
||||
## Ошибки и логирование
|
||||
|
||||
- Единый `ProblemDetails` для ошибок API; коды: 400 (валидация), 401/403 (auth), 404, 409 (конфликт домена), 422, 429 (rate limit), 500.
|
||||
- Serilog со структурными полями (`UserId`, `NodeId`, `ConfigId`, `CorrelationId`); секреты не логировать.
|
||||
- Единый `application/problem+json` для ошибок API (`ResultExtensions.ToProblem`); коды: 400 (валидация),
|
||||
401/403 (auth/не активирован), 404, 409 (конфликт домена — квота, дубликат), 422 (прочее), 429 (rate limit), 500.
|
||||
- Serilog + `UseSerilogRequestLogging()`; секреты (пароли, JWT, `BotToken`) не логировать. Структурного
|
||||
обогащения `UserId`/`NodeId`/`ConfigId`/`CorrelationId` пока нет — см. [tech-stack.md](tech-stack.md).
|
||||
|
||||
## Тестирование
|
||||
|
||||
- **Domain.Tests** — инварианты и поведение сущностей, без моков.
|
||||
- **Application.Tests** — хендлеры с подменёнными портами (NSubstitute), проверка веток `Result`.
|
||||
- **Integration.Tests** — реальный PostgreSQL (Testcontainers), миграции, сквозные сценарии эндпоинтов;
|
||||
3x-ui — мок гейтвея или фейковый HTTP-сервер.
|
||||
- Именование тестов: `Method_Scenario_ExpectedResult`.
|
||||
- **PnvPanel.Domain.Tests** (54 теста) — инварианты и поведение сущностей, без моков.
|
||||
- **PnvPanel.Application.Tests** (71 тест) — хендлеры на EF Core InMemory + подменённые порты
|
||||
(NSubstitute), проверка веток `Result`. InMemory, а не Sqlite — модель использует Postgres-специфичные
|
||||
типы (`uuid[]`, `jsonb`), которые Sqlite не поддерживает, а InMemory просто игнорирует.
|
||||
- **PnvPanel.IntegrationTests** (9 тестов) — реальный PostgreSQL (`Testcontainers.PostgreSql`) +
|
||||
`WebApplicationFactory<Program>`, сквозные сценарии через реальный HTTP-контракт, включая проверку
|
||||
`pg_advisory_xact_lock` под параллельной нагрузкой на квоту; 3x-ui подменён `FakeXuiPanelGateway`.
|
||||
- Именование тестов: `Method_Scenario_ExpectedResult`. Обычные `Assert.*` из xUnit — без FluentAssertions.
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- `appsettings.json` + `appsettings.{Environment}.json` + env vars (перекрывают).
|
||||
- Секреты (JWT-ключ, строка подключения, ключ шифрования) — user-secrets (dev) / env/secret-store (prod).
|
||||
- Строго типизированные `IOptions<T>` для секций конфига; валидация опций на старте.
|
||||
- `appsettings.json` + `appsettings.{Environment}.json` + env vars (перекрывают); в dev — `.env`
|
||||
через `docker-compose`'s `env_file`, локально без Docker — переменные окружения напрямую (проект
|
||||
не подключает `dotnet user-secrets` — `UserSecretsId` в `.csproj` нет).
|
||||
- Секреты (JWT-ключ, строка подключения, `Telegram:BotToken`, пароль сид-админа) — только через env/secret-store.
|
||||
- Строго типизированные `IOptions<T>` для секций конфига (`JwtOptions`, `AdminSeedOptions`,
|
||||
`TelegramOptions`, `RolesOptions`, ...); явной валидации на старте (`ValidateOnStart`/data annotations)
|
||||
нет — отсутствующий обязательный секрет обнаружится при первом обращении (например, `AddInfrastructure`
|
||||
бросит `InvalidOperationException`, если не задан `ConnectionStrings:Default`), не раньше.
|
||||
|
||||
## Стиль и качество кода
|
||||
|
||||
- `.editorconfig` + анализаторы (`Microsoft.CodeAnalysis.NetAnalyzers`), nullable reference types **включены**.
|
||||
- `dotnet format` в CI; предупреждения как ошибки для наших проектов.
|
||||
- Комментарии — по необходимости (почему, а не что); публичные контракты портов документируем XML-doc.
|
||||
- `.editorconfig`, nullable reference types **включены**; сборка в CI идёт с `-c Release` и должна
|
||||
быть без предупреждений.
|
||||
- `dotnet format` — локальная команда разработчика (см. корневой `CLAUDE.md`), **в CI не запускается**;
|
||||
CI гоняет только `dotnet build`/`dotnet test` (backend) и `pnpm lint`/`typecheck`/`build` (frontend).
|
||||
- Комментарии — по необходимости (почему, а не что, — см. примеры в коде: причина `pg_advisory_xact_lock`,
|
||||
причина `Secure = request.IsHttps`); публичные контракты портов документируем XML-doc там, где это
|
||||
не очевидно из имени.
|
||||
|
||||
Reference in New Issue
Block a user