Files
PnvPanel/docs/api-design.md
T

182 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.
# API Design
REST поверх HTTP/JSON, авторизация — `Authorization: Bearer <access-token>` (кроме публичных).
Ошибки — `application/problem+json` (`ProblemDetails`). Пагинация — `?page=&pageSize=`,
ответ `PagedList<T>` (`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 (при необходимости)|