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:
+57
-29
@@ -13,19 +13,29 @@ health checks, DI. `ThreeXui.Net` таргетит `net10.0` — совпаде
|
||||
Альтернативы: Vertical Slice (проще для мелких API, но хуже изолирует домен для растущего продукта) —
|
||||
можно комбинировать: слои + организация Application «по фичам».
|
||||
|
||||
### CQRS: собственный тонкий диспетчер ✅ (зафиксировано)
|
||||
**Решение принято**: свой `ISender` вместо MediatR (тот с v12 стал платным). ~100 строк:
|
||||
`ISender.Send()` резолвит `ICommandHandler<,>`/`IQueryHandler<,>` из DI и прогоняет через
|
||||
`IPipelineBehavior<,>` (валидация → авторизация → транзакция → логирование). Плюсы: нет лицензий и
|
||||
внешних зависимостей, полный контроль. Доменные события — свой `IDomainEventHandler<T>` +
|
||||
диспетчеризация после `SaveChanges`. Отклонены: MediatR (лицензия), FastEndpoints/Wolverine (лишняя связанность/переписывание модели).
|
||||
### CQRS: собственный тонкий диспетчер ✅ (зафиксировано и реализовано)
|
||||
**Решение принято**: свой `ISender` вместо MediatR (тот с v12 стал платным). `ISender.Send()`
|
||||
резолвит `ICommandHandler<,>`/`IQueryHandler<,>` из DI и прогоняет через `IPipelineBehavior<,>`.
|
||||
Реализованы три поведения: `ValidationBehavior` (FluentValidation), `LoggingBehavior`,
|
||||
`UnitOfWorkBehavior` (транзакция + `SaveChangesAsync` на команду). Плюсы: нет лицензий и внешних
|
||||
зависимостей, полный контроль. Отклонены: MediatR (лицензия), FastEndpoints/Wolverine (лишняя связанность/переписывание модели).
|
||||
|
||||
**Отличие от исходного плана**: отдельного диспетчера доменных событий (`IDomainEventHandler<T>`) в
|
||||
итоге не заводили — оказалось, что для текущего размера проекта прямые вызовы `IRealtimeNotifier`/
|
||||
`ITelegramNotifier` и запись `AuditLog` прямо в хендлере команды читаются проще, чем публикация
|
||||
события и поиск обработчика где-то ещё (см. [domain-model.md](domain-model.md#уведомления-и-аудит-без-диспетчера-доменных-событий)).
|
||||
Также нет отдельного `AuthorizationBehavior` — роль проверяется на уровне эндпоинта
|
||||
(`RequireAuthorization(...)`), а более тонкие проверки (владение, активация) — в самом хендлере.
|
||||
|
||||
### Валидация: FluentValidation
|
||||
Декларативные валидаторы на команды/запросы, подключаются через `ValidationBehavior`.
|
||||
Декларативные валидаторы на команды/запросы, подключаются через `ValidationBehavior`. Заводится не
|
||||
для каждой команды — только там, где есть что проверить помимo типов (например, у команд без
|
||||
пользовательского ввода валидатора нет).
|
||||
|
||||
### Маппинг: Mapster
|
||||
Быстрый, без коммерческой лицензии (в отличие от AutoMapper, тоже ставшего платным), кодогенерация.
|
||||
Для простых проекций — ручной `Select` в DTO без маппера.
|
||||
### Маппинг: вручную, без Mapster
|
||||
В исходном плане был Mapster — на практике для такого числа полей ручной статический метод
|
||||
`XxxDto.FromDomain(entity)` на самом DTO читается не хуже конфига маппера и не добавляет
|
||||
зависимость. `Mapster` в проект так и не попал.
|
||||
|
||||
### ORM: EF Core 10 + Npgsql
|
||||
Миграции, LINQ, `IEntityTypeConfiguration`. Провайдер PostgreSQL — Npgsql.
|
||||
@@ -68,16 +78,23 @@ MVP (не нужен публичный webhook, проще в одиночно
|
||||
Явные ошибки вместо исключений для управляемых сценариев; исключения — только для действительно исключительного.
|
||||
|
||||
### Логирование: Serilog ✅ (зафиксировано)
|
||||
**Решение принято**: структурное логирование — **Serilog** (`Serilog.AspNetCore`). Настройка через
|
||||
`appsettings`/env, обогащение контекста (`UserId`/`NodeId`/`ConfigId`/`CorrelationId`), секреты не
|
||||
логируются. Синки MVP: Console (JSON в проде) + rolling file; Seq/OTel-экспорт — опционально позже.
|
||||
Наблюдаемость сверх логов (OpenTelemetry-трейсинг, метрики) — вне MVP.
|
||||
**Решение принято**: структурное логирование — **Serilog** (`Serilog.AspNetCore`), настройка через
|
||||
`appsettings`/env, `UseSerilogRequestLogging()` + `Enrich.FromLogContext()`. Синк MVP — Console.
|
||||
Секреты (пароли, JWT, `BotToken`) в логи не попадают. **Не реализовано**: явное обогащение контекста
|
||||
полями `UserId`/`NodeId`/`ConfigId`, сквозной `CorrelationId`, rolling file/Seq/OTel-экспорт — было в
|
||||
исходном плане, осталось в backlog. Сегодня для расследования инцидента доступны только то, что даёт
|
||||
`Enrich.FromLogContext()` + запрос/ответ из request-логирования.
|
||||
|
||||
### API-документация: Swashbuckle (OpenAPI) + Scalar UI
|
||||
Схема OpenAPI используется фронтом для кодогенерации типов. Scalar — современный UI вместо Swagger UI.
|
||||
### API-документация: нативный OpenAPI (`Microsoft.AspNetCore.OpenApi`) + Scalar UI
|
||||
`AddOpenApi()`/`MapOpenApi()` — встроенная в ASP.NET Core (.NET 9+) генерация схемы, без Swashbuckle.
|
||||
`/openapi/v1.json` используется фронтом для `pnpm gen:api` (openapi-typescript). `/scalar` — Scalar UI
|
||||
вместо Swagger UI. Каждый эндпоинт аннотирован `.Produces<T>()`, чтобы схема полностью описывала
|
||||
и тела запросов, и тела ответов.
|
||||
|
||||
### Тесты: xUnit + FluentAssertions + NSubstitute + Testcontainers
|
||||
Юнит-тесты домена/хендлеров (моками портов), интеграционные — с реальным PostgreSQL в Testcontainers.
|
||||
### Тесты: xUnit + NSubstitute + Testcontainers
|
||||
Юнит-тесты домена/хендлеров (без FluentAssertions — обычные `Assert.*` из xUnit хватает для
|
||||
используемых проверок), интеграционные — с реальным PostgreSQL в Testcontainers
|
||||
(`Testcontainers.PostgreSql` + `WebApplicationFactory<Program>`).
|
||||
|
||||
## Frontend
|
||||
|
||||
@@ -104,11 +121,16 @@ MVP (не нужен публичный webhook, проще в одиночно
|
||||
### Realtime: @microsoft/signalr
|
||||
Официальный клиент SignalR; подписки на события хаба обновляют кэш TanStack Query.
|
||||
|
||||
### Типы API: OpenAPI codegen (openapi-typescript / orval)
|
||||
Типы (и, опц., хуки) генерируются из OpenAPI-схемы бэкенда — single source of truth, никакого дрейфа контрактов.
|
||||
### Типы API: openapi-typescript ✅ (зафиксировано)
|
||||
`pnpm gen:api` гоняет `openapi-typescript` по `/openapi/v1.json` живого бэкенда →
|
||||
`shared/api/schema.gen.ts`. На практике фичи импортируют типы из руками написанного
|
||||
`shared/api/types.ts` (см. [frontend.md](frontend.md)) — он логически совпадает со сгенерированной
|
||||
схемой (сверено), но даёт нормальные generic (`PagedList<T>`) и понятные имена, которых нет в JSON
|
||||
Schema. `orval` рассматривался как альтернатива (codegen хуков), не использовался.
|
||||
|
||||
### Графики: Recharts
|
||||
Декларативные графики трафика/статистики. QR-коды конфигов — `qrcode.react`.
|
||||
### Графики: Recharts (установлен, графики не построены)
|
||||
Библиотека в зависимостях фронта на будущее — в MVP админская статистика показана карточками с
|
||||
цифрами, без графиков. QR-коды конфигов — `qrcode.react` (реально используется).
|
||||
|
||||
### i18n: react-i18next, RU + EN ✅ (зафиксировано)
|
||||
**Решение принято**: локализация с первого дня, языки **RU + EN** (RU по умолчанию). Тексты — через
|
||||
@@ -167,16 +189,22 @@ MVP (не нужен публичный webhook, проще в одиночно
|
||||
| Ротация конфига | `Rotate()` — перевыпуск UUID/ссылки, квоту не тратит (на случай утечки) |
|
||||
| Лимит устройств | Per-config, задаёт юзер (`DeviceLimit` → `limitIp` в 3x-ui; 0 = без лимита) |
|
||||
| Метка конфига | `Label` — пользователь именует конфиг («Мой телефон») |
|
||||
| Самоудаление аккаунта | Разрешено: отзыв всех конфигов + удаление данных, аудит анонимизируется |
|
||||
| Самоудаление аккаунта | Разрешено: отзыв всех активных конфигов в 3x-ui + удаление `AppUser`. `AuditLog` уже хранит только `Guid` без PII — отдельной анонимизации задним числом нет, сам факт удаления в аудит тоже не пишется |
|
||||
| Версионирование API | Без версий в MVP (`/api` без `v1`) |
|
||||
| Подписка (заголовки) | `Subscription-Userinfo` (used/total/expire) + `profile-update-interval` |
|
||||
| Тема сайта | Светлая + тёмная (+ системная); Tailwind `dark`, выбор в localStorage |
|
||||
| Инструкции/приложения | Отдельная страница инструкций + каталог `ClientApp` (админ CRUD, юзер — по ОС); стартовый сид из `seed/client-apps.json` |
|
||||
| Реконсиляция с 3x-ui | На синхронизации сверяем проекцию с панелью, помечаем дрейф, не «воскрешаем» молча |
|
||||
| Реконсиляция с 3x-ui | Не реализована активно — `TrafficSyncService` молча пропускает ноду/клиента при недоступности или несовпадении, без пометки дрейфа (см. [architecture.md](architecture.md)) |
|
||||
|
||||
Также заложены: CSRF-защита refresh-cookie + Identity lockout; проверка квоты в транзакции; схема
|
||||
`ClientEmail = pnv_{userIdShort}_{rand}`; блокировка удаления ноды при наличии конфигов.
|
||||
Также реализовано: Identity lockout по неудачным входам; проверка квоты под `pg_advisory_xact_lock`;
|
||||
схема `ClientEmail = pnv_{userIdShort}_{rand}`. Явного анти-CSRF токена на refresh-cookie нет (см.
|
||||
[architecture.md](architecture.md#безопасность) — обоснование, почему `SameSite=Strict` + `HttpOnly`
|
||||
достаточно при мутациях только по Bearer-токену). Удаление ноды с активными конфигами **не
|
||||
блокируется** — это известный пробел, не защита: `DeleteNodeCommandHandler` каскадно удаляет
|
||||
инбаунды ноды без проверки существующих `VpnConfig`.
|
||||
|
||||
Осталось выбрать позже (не блокирует старт): значение TTL для истории трафика; конкретные синки
|
||||
Serilog для прод (файл/Seq/OTel); точные TTL токенов Telegram. Email/SMTP в проекте **не используются**
|
||||
(вход по username, восстановление — через Telegram/админа).
|
||||
Не реализовано (осталось на будущее, не блокирует текущую работу): TTL для истории трафика (сейчас
|
||||
`TrafficRetentionService` работает, но точный порог не вынесен в решение — см. код); прод-синки
|
||||
Serilog (файл/Seq/OTel) и структурное обогащение логов (`UserId`/`CorrelationId`); точные TTL
|
||||
токенов Telegram. Email/SMTP в проекте **не используются** (вход по username, восстановление — через
|
||||
Telegram/админа).
|
||||
|
||||
Reference in New Issue
Block a user