Update .gitignore to include local environment files and expand README with project details, tech stack, documentation links, and project status.

This commit is contained in:
Leonid Pershin
2026-07-01 18:37:54 +03:00
parent 3b364cf8c4
commit d8930409fe
14 changed files with 1780 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
# Tech Stack — решения и обоснование (ADR-lite)
Формат: **Решение** → короткое обоснование → альтернативы. Отклонения фиксировать здесь же.
## Backend
### Платформа: .NET 10 + ASP.NET Core Web API
Долгосрочная (LTS-класса) современная платформа, нативная поддержка Minimal API, rate limiting,
health checks, DI. `ThreeXui.Net` таргетит `net10.0` — совпадение целевого фреймворка.
### Архитектура: Clean Architecture (4 проекта)
`Domain / Application / Infrastructure / Api`. Тестируемость, изоляция домена, заменяемость инфраструктуры.
Альтернативы: Vertical Slice (проще для мелких API, но хуже изолирует домен для растущего продукта) —
можно комбинировать: слои + организация Application «по фичам».
### CQRS: собственный тонкий диспетчер ✅ (зафиксировано)
**Решение принято**: свой `ISender` вместо MediatR (тот с v12 стал платным). ~100 строк:
`ISender.Send()` резолвит `ICommandHandler<,>`/`IQueryHandler<,>` из DI и прогоняет через
`IPipelineBehavior<,>` (валидация → авторизация → транзакция → логирование). Плюсы: нет лицензий и
внешних зависимостей, полный контроль. Доменные события — свой `IDomainEventHandler<T>` +
диспетчеризация после `SaveChanges`. Отклонены: MediatR (лицензия), FastEndpoints/Wolverine (лишняя связанность/переписывание модели).
### Валидация: FluentValidation
Декларативные валидаторы на команды/запросы, подключаются через `ValidationBehavior`.
### Маппинг: Mapster
Быстрый, без коммерческой лицензии (в отличие от AutoMapper, тоже ставшего платным), кодогенерация.
Для простых проекций — ручной `Select` в DTO без маппера.
### ORM: EF Core 10 + Npgsql
Миграции, LINQ, `IEntityTypeConfiguration`. Провайдер PostgreSQL — Npgsql.
Запросы-чтения — проекции в DTO (`AsNoTracking` + `Select`).
### БД: PostgreSQL
Надёжная, богатая по типам (jsonb, массивы), бесплатная. Для истории трафика в будущем —
TimescaleDB-расширение.
### Auth: ASP.NET Core Identity + JWT
Identity для пользователей/ролей/хэширования; JWT access (короткий TTL) + refresh (httpOnly cookie, ротация).
Альтернатива — внешний OIDC (Keycloak/Auth0); отклонено на этом этапе в пользу полного контроля.
### RBAC: динамические роли с квотой (`AppRole.MaxConfigs`)
Роли — стандартный Identity, но `AppRole` расширен `MaxConfigs`. Админ создаёт/назначает роли;
доступ к инбаундам — по ролям (`Inbound.AllowedRoles`). Квота на число конфигов — на роли, а не на `Plan`.
### Активация пользователей
`AppUser.IsActivated` + `ActivationRequest` (с комментарием). Неактивированный не создаёт конфиги.
Решение принимает админ на сайте или в Telegram — одними и теми же CQRS-командами.
### Сидинг из env
Идемпотентный `DbInitializer` на старте: системные роли (`admin`/`user`), учётка админа и Telegram id
админов — из переменных окружения. Пример — [`.env.example`](../.env.example). Строго типизированные
`IOptions<T>` с валидацией на старте.
### Realtime: SignalR
Нативно для ASP.NET Core, авто-транспорт (WebSocket→SSE→long-poll), группы/пользователи, JWT-авторизация хабов.
### Telegram-бот: Telegram.Bot (in-process hosted service)
Де-факто стандартная C#-библиотека. Бот хостится в процессе Api как `BackgroundService` (условие
единого контейнера) и вызывает те же CQRS-хендлеры, что и REST. Транспорт — **long polling** для
MVP (не нужен публичный webhook, проще в одиночном контейнере); webhook — опция для прод (с секретным
заголовком). Passwordless-вход выпускает те же JWT/refresh, что и веб. Детали — [telegram-bot.md](telegram-bot.md).
### Фоновые задачи: BackgroundService + PeriodicTimer (MVP)
Без внешних зависимостей для MVP. При росте (ретраи, расписания, дашборд) — **Hangfire** или **Quartz.NET**.
### Result-модель: собственный `Result<T>` (или ErrorOr)
Явные ошибки вместо исключений для управляемых сценариев; исключения — только для действительно исключительного.
### Логирование: Serilog ✅ (зафиксировано)
**Решение принято**: структурное логирование — **Serilog** (`Serilog.AspNetCore`). Настройка через
`appsettings`/env, обогащение контекста (`UserId`/`NodeId`/`ConfigId`/`CorrelationId`), секреты не
логируются. Синки MVP: Console (JSON в проде) + rolling file; Seq/OTel-экспорт — опционально позже.
Наблюдаемость сверх логов (OpenTelemetry-трейсинг, метрики) — вне MVP.
### API-документация: Swashbuckle (OpenAPI) + Scalar UI
Схема OpenAPI используется фронтом для кодогенерации типов. Scalar — современный UI вместо Swagger UI.
### Тесты: xUnit + FluentAssertions + NSubstitute + Testcontainers
Юнит-тесты домена/хендлеров (моками портов), интеграционные — с реальным PostgreSQL в Testcontainers.
## Frontend
### React 19 + Vite + TypeScript
Максимальная экосистема, быстрый dev-сервер и сборка Vite, строгая типизация. SPA (не SSR) —
для внутренней панели SSR избыточен и усложняет деплой рядом с C# API.
### Данные с сервера: TanStack Query
Кэш, инвалидация, фоновые рефетчи, статусы загрузки/ошибок. Идеально для CRUD-панели.
### Роутинг: TanStack Router
Типобезопасный роутинг, интеграция с TanStack Query. Альтернатива — React Router 7.
### UI: shadcn/ui + Tailwind CSS v4
Копируемые в проект, полностью кастомизируемые компоненты (Radix под капотом), современный вид,
тёмная тема из коробки. Иконки — `lucide-react`.
### Клиентский стейт: Zustand
Лёгкий стор для глобального (авторизация, тема). Серверный стейт — только в TanStack Query.
### Формы: react-hook-form + zod
Производительные формы + схемная валидация; те же zod-схемы для типобезопасности API-ответов.
### Realtime: @microsoft/signalr
Официальный клиент SignalR; подписки на события хаба обновляют кэш TanStack Query.
### Типы API: OpenAPI codegen (openapi-typescript / orval)
Типы (и, опц., хуки) генерируются из OpenAPI-схемы бэкенда — single source of truth, никакого дрейфа контрактов.
### Графики: Recharts
Декларативные графики трафика/статистики. QR-коды конфигов — `qrcode.react`.
### i18n: react-i18next, RU + EN ✅ (зафиксировано)
**Решение принято**: локализация с первого дня, языки **RU + EN** (RU по умолчанию). Тексты — через
ключи (`react-i18next`), не хардкод строк в компонентах.
## Инфраструктура
### Упаковка: единый образ приложения + PostgreSQL
По требованию — **один контейнер на всё приложение** (REST + SignalR + Telegram-бот + статика SPA)
и отдельный контейнер БД.
- **Multi-stage Dockerfile**: (1) `node` собирает фронт → `dist/`; (2) `dotnet sdk` публикует Api и
копирует статику в `wwwroot`; (3) `aspnet` runtime запускает Api. Api раздаёт SPA (`UseStaticFiles`
+ fallback на `index.html`), фронт и бек — один origin.
- **docker-compose**: `app` (единый образ) + `db` (PostgreSQL) с томом.
- Почему не отдельный nginx: единый origin упрощает CORS/куки/деплой и укладывается в требование
«фронт+бек в одном контейнере». Nginx/reverse-proxy — опция для прод (TLS-терминация) поверх, но не обязателен.
- **CI**: сборка/тесты бэка (`dotnet test`), линт/сборка фронта (`pnpm build`), сборка единого образа.
- **Пакетный менеджер фронта**: pnpm (быстрый, экономный по диску).
## Принятые решения (по открытым вопросам)
Все ключевые развилки закрыты:
| # | Вопрос | Решение |
| - | ------------------------------ | ------------------------------------------------------------------- |
| 1 | CQRS-медиатор | **Собственный тонкий диспетчер** (не MediatR) |
| 2 | Ролей у пользователя | **Ровно одна роль** (квота = `MaxConfigs` роли) |
| 3 | Секреты нод | **ASP.NET Core Data Protection** (шифрование at-rest, key-ring на томе) |
| 4 | Тарифы `Plan` в MVP | **Backlog** — в MVP конфиги без лимитов трафика/срока |
| 5 | i18n | **RU + EN** с первого дня (react-i18next) |
| 6 | Telegram-транспорт | **Long polling** |
| 7 | Регистрация через Telegram | **Только привязка** существующего аккаунта (signup из бота — backlog) |
| 8 | История трафика `TrafficSample`| **Простая таблица PostgreSQL + TTL** (фоновая чистка старше N дней) |
| 9 | Логирование | **Serilog** (Console + rolling file) |
### Продуктовые решения (поведение)
| Тема | Решение |
| ------------------------ | ------------------------------------------------------------------------------- |
| Вход | **По username** (email в системе не используется; SMTP не нужен) |
| Восстановление пароля | Через привязанный Telegram (self-service); без привязки — сброс админом |
| Побуждение привязать TG | Настойчивый баннер/уведомления в UI, пока Telegram не привязан |
| Регистрация | Открытая + гейт активации админом |
| Конфиги в одном инбаунде | Разрешено несколько (ограничение — только общая квота роли) |
| Данные ноды пользователю | Показываем только `DisplayName` + протокол; адрес/хост/порт скрыты |
| Блокировка пользователя | Отключать все его конфиги в 3x-ui (`Disabled`); разблокировка — включить обратно |
| Понижение роли | **Грандфазеринг**: существующие конфиги живут, новые нельзя до входа в квоту |
| Скоуп Telegram-бота (MVP)| **Read-only** по конфигам (создание/отзыв — на сайте); DM-уведомления юзеру |
| Подписка | Агрегированная на юзера (`AppUser.SubscriptionToken`) + по конфигу |
| Аудит | `AuditLog` (append-only): активация, блокировка, смена роли, отзыв, ноды/инбаунды |
| Ротация конфига | `Rotate()` — перевыпуск UUID/ссылки, квоту не тратит (на случай утечки) |
| Лимит устройств | Per-config, задаёт юзер (`DeviceLimit``limitIp` в 3x-ui; 0 = без лимита) |
| Метка конфига | `Label` — пользователь именует конфиг («Мой телефон») |
| Самоудаление аккаунта | Разрешено: отзыв всех конфигов + удаление данных, аудит анонимизируется |
| Версионирование API | Без версий в MVP (`/api` без `v1`) |
| Подписка (заголовки) | `Subscription-Userinfo` (used/total/expire) + `profile-update-interval` |
| Реконсиляция с 3x-ui | На синхронизации сверяем проекцию с панелью, помечаем дрейф, не «воскрешаем» молча |
Также заложены: CSRF-защита refresh-cookie + Identity lockout; проверка квоты в транзакции; схема
`ClientEmail = pnv_{userIdShort}_{rand}`; блокировка удаления ноды при наличии конфигов.
Осталось выбрать позже (не блокирует старт): значение TTL для истории трафика; конкретные синки
Serilog для прод (файл/Seq/OTel); точные TTL токенов Telegram. Email/SMTP в проекте **не используются**
(вход по username, восстановление — через Telegram/админа).