# API Design REST поверх HTTP/JSON, авторизация — `Authorization: Bearer ` (кроме публичных). Ошибки — `application/problem+json` (`ProblemDetails`). Пагинация — `?page=&pageSize=`, ответ `PagedList` (`items`, `total`, `page`, `pageSize`). Все даты — ISO-8601 UTC. Базовый префикс: `/api` (**без версионирования в MVP** — единый фронт+бек; версии введём при необходимости). Ниже — контракт MVP (может уточняться при реализации). ## Auth | Метод | Путь | Роль | Описание | | ----- | --------------------------- | ------ | ---------------------------------------------------- | | POST | `/api/auth/register` | — | Регистрация `{ username, password }` | | POST | `/api/auth/login` | — | Вход `{ username, password }` → access (body) + refresh (httpOnly cookie) | | POST | `/api/auth/refresh` | — | Обновление access по refresh-cookie (ротация) | | POST | `/api/auth/logout` | user | Отзыв refresh-токена | | POST | `/api/auth/change-password` | user | Смена пароля `{ currentPassword, newPassword }` | | GET | `/api/auth/me` | user | Текущий профиль + роль + `isActivated` + `telegramLinked` | | DELETE| `/api/auth/me` | user | Самоудаление аккаунта (отзыв всех конфигов + удаление данных; аудит анонимизируется) | > **Вход по username.** Email в системе не используется. Забыт пароль: > при привязанном Telegram — восстановление через бота; иначе — сброс админом (см. Admin). ## Auth — Telegram (привязка и passwordless-вход) | Метод | Путь | Роль | Описание | | ----- | --------------------------------------------- | ---- | -------------------------------------------------------------- | | POST | `/api/auth/telegram/link-token` | user | Создать токен привязки → `{ deepLink, qr, expiresAt }` | | POST | `/api/auth/telegram/unlink` | user | Отвязать Telegram от аккаунта | | POST | `/api/auth/telegram/login-request` | — | Инициировать вход → `{ requestId, deepLink, qr, expiresAt }` | | GET | `/api/auth/telegram/login-request/{id}` | — | Статус запроса; при `Approved` выдаёт access + refresh-cookie | `GET …/login-request/{id}` (поллинг; альтернатива — событие SignalR) → варианты ответа: ```json // ожидание { "status": "Pending" } // подтверждено — выпуск токенов (refresh уходит в httpOnly cookie), запрос → Consumed { "status": "Approved", "accessToken": "…", "expiresAt": "…", "user": { "id": "…", "roles": ["User"] } } // отклонено / истекло { "status": "Rejected" } // | "Expired" ``` > Сами апдейты Telegram (`/start`, кнопки) обрабатывает in-process бот (long polling), а не HTTP-эндпоинты. > Контракты команд бота — в [telegram-bot.md](telegram-bot.md). ## Configs (пользователь) | Метод | Путь | Роль | Описание | | ------ | --------------------------------- | ---- | ------------------------------------------------- | | GET | `/api/inbounds/available` | user | Инбаунды, доступные роли пользователя (для выбора при создании) | | GET | `/api/configs` | user | Список своих конфигов (пагинация) | | POST | `/api/configs` | user | Создать конфиг `{ inboundId, label?, deviceLimit? }` (проверки: активирован, квота роли, доступ роли к инбаунду) | | PATCH | `/api/configs/{id}` | user | Изменить `{ label?, deviceLimit? }` (deviceLimit → `limitIp` в 3x-ui) | | GET | `/api/configs/{id}` | user | Детали конфига (метка, трафик, устройства, статус) | | GET | `/api/configs/{id}/link` | user | Connection string + subscriptionUrl + QR-payload | | POST | `/api/configs/{id}/rotate` | user | Перевыпустить конфиг (новый UUID/ссылка; квоту не тратит) | | DELETE | `/api/configs/{id}` | user | Отозвать конфиг (удаляет клиента в 3x-ui) | | GET | `/api/subscription` | user | URL агрегированной подписки пользователя (все активные конфиги) | `POST /api/configs` → `201 Created`: ```json { "id": "…", "protocol": "Vless", "location": "DE", "link": "vless://…", "subscriptionUrl": "https://…/sub/…", "trafficLimitBytes": 53687091200, "expiresAt": "2026-08-01T00:00:00Z", "status": "Active" } ``` ## Apps — каталог приложений | Метод | Путь | Роль | Описание | | ------ | -------------------------- | ----- | ---------------------------------------------------- | | GET | `/api/apps` | user | Включённые приложения, **сгруппированы по ОС** (для страницы инструкций) | | GET | `/api/admin/apps` | admin | Все приложения (вкл. выключенные) | | POST | `/api/admin/apps` | admin | Добавить `{ name, downloadUrl, operatingSystem, description?, iconUrl?, sortOrder? }` | | PUT | `/api/admin/apps/{id}` | admin | Изменить приложение (в т.ч. `isEnabled`) | | DELETE | `/api/admin/apps/{id}` | admin | Удалить приложение | `GET /api/apps` → пример: ```json { "Android": [ { "id": "…", "name": "v2rayNG", "downloadUrl": "https://…", "iconUrl": null } ], "iOS": [ { "id": "…", "name": "Hiddify", "downloadUrl": "https://…", "iconUrl": null } ] } ``` ## Activation (пользователь) | Метод | Путь | Роль | Описание | | ----- | --------------------------- | ---- | ------------------------------------------------------ | | GET | `/api/activation/status` | user | Статус активации + текущий `Pending`-запрос (если есть)| | POST | `/api/activation/request` | user | Запросить активацию `{ comment? }` (напр. «я Никита») | `GET /api/configs` для неактивированного пользователя вернёт пустой список; `POST /api/configs` до активации → `403` (или `409` с кодом `NotActivated`). ## Admin — Activation, Roles | Метод | Путь | Роль | Описание | | ------ | ----------------------------------------------- | ----- | --------------------------------------------------- | | GET | `/api/admin/activation-requests` | admin | Список запросов активации (фильтр по статусу) | | POST | `/api/admin/activation-requests/{id}/approve` | admin | Одобрить → пользователь активирован | | POST | `/api/admin/activation-requests/{id}/reject` | admin | Отклонить `{ reason? }` | | GET | `/api/admin/roles` | admin | Список ролей с квотами | | POST | `/api/admin/roles` | admin | Создать роль `{ name, maxConfigs }` | | PUT | `/api/admin/roles/{id}` | admin | Изменить роль (напр. `maxConfigs`) | | DELETE | `/api/admin/roles/{id}` | admin | Удалить роль (нельзя системные `admin`/`user`) | | PATCH | `/api/admin/users/{id}/role` | admin | Сменить роль пользователю `{ roleId }` (ровно одна) | | PATCH | `/api/admin/users/{id}/activation` | admin | Активировать/деактивировать напрямую `{ isActivated }`| ## Admin — Nodes | Метод | Путь | Роль | Описание | | ------ | ----------------------------- | ----- | ----------------------------------------- | | GET | `/api/admin/nodes` | admin | Список нод + статусы | | POST | `/api/admin/nodes` | admin | Подключить ноду `{ name, baseAddress, username, password, location }` | | PUT | `/api/admin/nodes/{id}` | admin | Изменить ноду (в т.ч. `isEnabled`) | | DELETE | `/api/admin/nodes/{id}` | admin | Удалить ноду | | POST | `/api/admin/nodes/{id}/sync` | admin | Пересинхронизировать inbounds с 3x-ui | | POST | `/api/admin/nodes/{id}/probe` | admin | Проверить доступность | ## Admin — Inbounds | Метод | Путь | Роль | Описание | | ----- | -------------------------------------- | ----- | ---------------------------------------- | | GET | `/api/admin/inbounds` | admin | Список inbounds (по нодам) + `allowedRoleIds`, `displayName` | | PUT | `/api/admin/inbounds/{id}/publish` | admin | Опубликовать/снять `{ isPublished, displayName?, allowedRoleIds[], maxClients? }` | ## Admin — Users & Stats | Метод | Путь | Роль | Описание | | ----- | --------------------------------- | ----- | ----------------------------------------- | | GET | `/api/admin/users` | admin | Пользователи (пагинация, поиск) | | PATCH | `/api/admin/users/{id}/block` | admin | Блокировать/разблокировать `{ isBlocked }` (при блоке — отключить конфиги в 3x-ui) | | POST | `/api/admin/users/{id}/reset-password` | admin | Сбросить пароль пользователю без привязки Telegram (выдать временный/задать новый) | | GET | `/api/admin/users/{id}/configs` | admin | Конфиги пользователя | | DELETE| `/api/admin/configs/{id}` | admin | Принудительно отозвать любой конфиг | | GET | `/api/admin/stats` | admin | Сводная статистика (пользователи, конфиги, трафик) | | GET | `/api/admin/audit` | admin | Журнал действий (`AuditLog`, пагинация, фильтры) | ## Public — Subscription | Метод | Путь | Роль | Описание | | ----- | ----------------- | ---- | ---------------------------------------------------------------- | | GET | `/sub/{token}` | — | Подписка (base64-список ссылок). Токен — либо `AppUser.SubscriptionToken` (**все активные конфиги юзера**), либо `VpnConfig.SubscriptionToken` (**один конфиг**). Без `/api`. | Rate-limited; отключённые/отозванные конфиги в выдачу не попадают; неизвестный/погашенный токен → 404. Ответ отдаёт заголовок **`Subscription-Userinfo`** (`upload`/`download`/`total`/`expire`) — клиенты (v2rayN/Nekoray и т.п.) показывают остаток трафика/срок. Также `profile-update-interval`. ## SignalR — Hub `/hubs/panel` Авторизация — тем же JWT (query `access_token` или заголовок). Группы: `user:{userId}`, `admins`. ### Server → Client | Событие | Payload | Кому | | ---------------------- | ------------------------------------------------------------- | ------------ | | `configTrafficUpdated` | `{ configId, usedUpBytes, usedDownBytes, limitBytes }` | владельцу | | `configStatusChanged` | `{ configId, status }` | владельцу | | `nodeStatusChanged` | `{ nodeId, status, lastSyncAt }` | `admins` | | `activationRequested` | `{ requestId, userId, username, comment, createdAt }` | `admins` | | `userActivated` | `{ userId }` | владельцу | ### Client → Server MVP — клиент только слушает (группировка по пользователю на сервере при подключении по `UserId` из JWT). ## Коды ошибок | Код | Когда | | --- | -------------------------------------------------- | | 400 | Ошибка валидации (`errors` в ProblemDetails) | | 401 | Нет/просрочен токен | | 403 | Нет прав (роль/владение/не активирован/роль без доступа к инбаунду) | | 404 | Ресурс не найден | | 409 | Конфликт домена (превышена квота роли, дубликат, уже есть Pending-запрос активации) | | 422 | Нарушение инварианта домена | | 429 | Rate limit | | 502 | Ошибка/недоступность ноды 3x-ui (при необходимости)|