# API документация 3dp-manager

**Версия API:** v1  
**Базовый URL:** `http://<host>:3100/api`  
**Префикс:** Все endpoints начинаются с `/api`, кроме публичных подписок (`/bus/:uuid`)

---

## Содержание

1. [Аутентификация](#аутентификация)
2. [Подписки](#подписки)
3. [Домены](#домены)
4. [Туннели (перенаправление)](#туннели-перенаправление)
5. [Настройки](#настройки)
6. [Ротация](#ротация)
7. [Клиентские endpoints (публичные)](#клиентские-endpoints-публичные)

---

## Аутентификация

Все endpoints требуют JWT-аутентификации, кроме помеченных как **Public**.

### POST `/api/auth/login`

Получение JWT-токена.

**Body:**
```json
{
  "login": "admin",
  "password": "your_password"
}
```

**Response:**
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

---

### POST `/api/auth/change-password`

Смена пароля администратора.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "password": "newSecurePassword"
}
```

**Response:**
```json
{
  "success": true
}
```

---

### POST `/api/auth/update-profile`

Обновление профиля администратора (логин + опционально пароль).

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "login": "newAdmin",
  "password": "newPassword" 
}
```

**Response:**
```json
{
  "success": true
}
```

---

## Подписки

### GET `/api/subscriptions`

Получить все подписки.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
[
  {
    "id": 1,
    "uuid": "abc-123-def",
    "name": "My Subscription",
    "isEnabled": true,
    "isAutoRotationEnabled": true,
    "inbounds": [...],
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-01T00:00:00Z"
  }
]
```

---

### POST `/api/subscriptions`

Создать новую подписку.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "name": "My Subscription",
  "inboundsConfig": [
    {
      "type": "vless",
      "port": 443,
      "sni": "example.com"
    },
    {
      "type": "vmess",
      "port": 8443,
      "transport": "websocket"
    }
  ],
  "isAutoRotationEnabled": true
}
```

**Response:**
```json
{
  "id": 1,
  "uuid": "abc-123-def",
  "name": "My Subscription",
  ...
}
```

---

### PUT `/api/subscriptions/:id`

Обновить подписку.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "name": "Updated Name",
  "isEnabled": false,
  "isAutoRotationEnabled": true
}
```

**Response:**
```json
{
  "id": 1,
  "name": "Updated Name",
  ...
}
```

---

### PUT `/api/subscriptions/bulk-auto-rotation`

Массовое включение/отключение авто-ротации.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "subscriptionIds": ["1", "2", "3"],
  "enabled": true
}
```

**Response:**
```json
{
  "success": true,
  "message": "Обновлено 3 подписок",
  "updatedCount": 3,
  "notFound": []
}
```

---

### DELETE `/api/subscriptions/:id`

Удалить подписку.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true
}
```

---

## Домены

### GET `/api/domains/all`

Получить все домены без пагинации.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
[
  { "id": 1, "name": "ya.ru" },
  { "id": 2, "name": "vk.com" }
]
```

---

### GET `/api/domains?page=1&limit=10`

Получить домены с пагинацией.

**Headers:** `Authorization: Bearer <token>`

**Query Parameters:**
- `page` (default: 1)
- `limit` (default: 10)

**Response:**
```json
{
  "data": [
    { "id": 1, "name": "ya.ru" }
  ],
  "total": 100
}
```

---

### GET `/api/domains/:id`

Получить домен по ID.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "id": 1,
  "name": "ya.ru"
}
```

---

### POST `/api/domains`

Создать один домен.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "name": "example.com"
}
```

**Response:**
```json
{
  "id": 1,
  "name": "example.com"
}
```

---

### POST `/api/domains/upload`

Загрузить несколько доменов сразу.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "domains": ["ya.ru", "vk.com", "example.com"]
}
```

**Response:**
```json
{
  "count": 3
}
```

---

### DELETE `/api/domains/:id`

Удалить домен по ID.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true
}
```

---

### DELETE `/api/domains/all`

Удалить все домены.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true
}
```

---

### GET `/api/domains/scan/capabilities`

Получить возможности сканера доменов (наличие бинарника, timeout).

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "scannerAvailable": true,
  "scannerPath": "/usr/bin/RealiTLScanner-linux-64",
  "timeoutAvailable": true,
  "timeoutPath": "/usr/bin/timeout"
}
```

---

### GET `/api/domains/scan/status`

Получить статус текущего сканирования.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "running": false,
  "runId": null,
  "lastRunId": "scan-123"
}
```

---

### GET `/api/domains/scan/last-result`

Получить результат последнего сканирования.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "runId": "scan-123",
  "foundCount": 10,
  "domains": ["ya.ru", "vk.com", ...]
}
```

---

### POST `/api/domains/scan/start`

Запустить сканирование IP на наличие SNI.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "addr": "192.168.1.1",
  "scanSeconds": 60,
  "thread": 100,
  "timeout": 30
}
```

**Response:**
```json
{
  "success": true,
  "runId": "scan-456"
}
```

---

## Туннели (перенаправление)

### GET `/api/tunnels`

Получить все туннели.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
[
  {
    "id": 1,
    "name": "Relay Server 1",
    "ip": "192.168.1.100",
    "sshPort": 22,
    "username": "root",
    "domain": "relay.example.com",
    "isInstalled": true
  }
]
```

---

### POST `/api/tunnels`

Создать новый туннель.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "name": "Relay Server 1",
  "ip": "192.168.1.100",
  "sshPort": 22,
  "username": "root",
  "password": "ssh_password",
  "domain": "relay.example.com"
}
```

**Response:**
```json
{
  "id": 1,
  "name": "Relay Server 1",
  ...
}
```

---

### POST `/api/tunnels/:id/install`

Установить скрипт перенаправления на удалённый сервер.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true,
  "script": "#!/bin/bash ...",
  "message": "Скрипт сгенерирован. Выполните его на целевом сервере."
}
```

---

### DELETE `/api/tunnels/:id`

Удалить туннель.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true
}
```

---

## Настройки

### GET `/api/settings`

Получить все настройки.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "xui_url": "http://192.168.1.1:54321",
  "xui_login": "admin",
  "rotationIntervalMinutes": "1440",
  "xui_geo_country": "Germany",
  "xui_geo_flag": "🇩🇪"
}
```

---

### POST `/api/settings`

Обновить настройки.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "xui_url": "http://192.168.1.1:54321",
  "xui_login": "admin",
  "xui_password": "password",
  "rotationIntervalMinutes": "1440"
}
```

**Response:**
```json
{
  "success": true
}
```

*Автоматически извлекает host, IP, определяет страну по GeoIP.*

---

### POST `/api/settings/check`

Проверить подключение к 3x-ui панели.

**Headers:** `Authorization: Bearer <token>`

**Body:**
```json
{
  "xui_url": "http://192.168.1.1:54321",
  "xui_login": "admin",
  "xui_password": "password"
}
```

**Response:**
```json
{
  "success": true
}
```

---

## Ротация

### POST `/api/rotation/rotate-all`

Выполнить ротацию всех подписок.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true,
  "rotatedCount": 5
}
```

---

### POST `/api/rotation/rotate-one/:id`

Выполнить ротацию одной подписки.

**Headers:** `Authorization: Bearer <token>`

**Response:**
```json
{
  "success": true,
  "subscriptionId": "1"
}
```

---

## Клиентские endpoints (публичные)

Эти endpoints **не требуют аутентификации**.

### GET `/bus/:uuid`

Получить подписку в формате Base64 или HTML-страницу с QR-кодом.

**Public** — без токена.

**URL:**
```
GET /bus/abc-123-def
```

**Headers:**
- `User-Agent: Mozilla/...` → возвращает HTML с QR
- `User-Agent: v2rayN/...` → возвращает plain text Base64

**Response (Base64):**
```
vless://abc123...
vmess://xyz789...
...
```

**Response (HTML):**
```html
<!DOCTYPE html>
<html>
  <body>
    <h1>Subscription: My Subscription</h1>
    <img src="data:image/png;base64,..." />
    <a href="/bus/abc-123-def">Скачать подписку</a>
  </body>
</html>
```

---

### GET `/bus/:uuid/:tunnelId`

Получить подписку с заменой хоста на релей-сервер.

**Public** — без токена.

**Query Parameters:**
- `format` (опционально): `base64` | `html`

**URL:**
```
GET /bus/abc-123-def/1
```

**Response:**
- Автоматически заменяет IP/домен в ссылках на `tunnel.domain` или `tunnel.ip`
- Возвращает Base64 или HTML с QR

---

## Сущности

### Subscription
```typescript
{
  id: number;
  uuid: string;
  name: string;
  isEnabled: boolean;
  isAutoRotationEnabled: boolean;
  inbounds: Inbound[];
  createdAt: Date;
  updatedAt: Date;
}
```

### Inbound
```typescript
{
  id: number;
  type: 'vless' | 'vmess' | 'shadowsocks' | 'hysteria2' | 'trojan' | 'custom';
  port: number | string;
  sni?: string;
  link: string;
  transport?: 'tcp' | 'websocket' | 'grpc' | 'xhttp';
}
```

### Domain
```typescript
{
  id: number;
  name: string;
}
```

### Tunnel
```typescript
{
  id: number;
  name: string;
  ip: string;
  sshPort: number;
  username: string;
  domain?: string;
  isInstalled: boolean;
}
```

### Setting
```typescript
{
  key: string;
  value: string;
}
```

---

## Коды ошибок

| Код | Описание |
|-----|----------|
| 200 | Успех |
| 400 | Неверный запрос |
| 401 | Неавторизован (нет токена или неверный) |
| 403 | Доступ запрещён |
| 404 | Ресурс не найден |
| 500 | Внутренняя ошибка сервера |

---

## Примеры использования

### JavaScript (Fetch API)

```javascript
// Логин
const loginResponse = await fetch('http://localhost:3100/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ login: 'admin', password: 'admin' })
});
const { access_token } = await loginResponse.json();

// Получить все подписки
const subsResponse = await fetch('http://localhost:3100/api/subscriptions', {
  headers: { 'Authorization': `Bearer ${access_token}` }
});
const subscriptions = await subsResponse.json();

// Создать подписку
const createResponse = await fetch('http://localhost:3100/api/subscriptions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${access_token}`
  },
  body: JSON.stringify({
    name: 'My Subscription',
    inboundsConfig: [
      { type: 'vless', port: 443, sni: 'example.com' }
    ],
    isAutoRotationEnabled: true
  })
});
const subscription = await createResponse.json();
```

### Python (requests)

```python
import requests

# Логин
response = requests.post('http://localhost:3100/api/auth/login', json={
    'login': 'admin',
    'password': 'admin'
})
token = response.json()['access_token']

# Получить все домены
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('http://localhost:3100/api/domains/all', headers=headers)
domains = response.json()

# Запустить сканирование
response = requests.post('http://localhost:3100/api/domains/scan/start', 
    headers=headers,
    json={
        'addr': '192.168.1.1',
        'scanSeconds': 60,
        'thread': 100,
        'timeout': 30
    }
)
```

### cURL

```bash
# Логин
curl -X POST http://localhost:3100/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"login":"admin","password":"admin"}'

# Получить подписку (Base64)
curl http://localhost:3100/bus/abc-123-def

# Получить подписку (HTML с QR)
curl -H "User-Agent: Mozilla/5.0" \
  http://localhost:3100/bus/abc-123-def
```

---

## Интеграция

### Ссылка на подписку в клиенте

Формат:
```
http://<host>:<port>/bus/<uuid>
```

Пример для v2rayN, Clash, Surge:
```
http://192.168.1.100:3100/bus/abc-123-def
```

### QR-код

Откройте в браузере:
```
http://<host>:<port>/bus/<uuid>
```

Сканируйте QR-код приложением.

---

## Примечания

- **JWT-токен** передаётся в заголовке `Authorization: Bearer <token>`
- **Публичные endpoints** (`/bus/*`) не требуют токена
- **Base64-кодировка** используется для подписок (стандарт v2ray)
- **Авто-ротация** выполняется по интервалу из настроек (`rotationIntervalMinutes`)
- **Scanner** (RealiTLScanner) — Go-бинарник, встраивается в backend-контейнер

---

## Контакты

- Telegram: [@denpiligrim_web](https://t.me/denpiligrim_web)
- YouTube: [DenPiligrim](https://www.youtube.com/@denpiligrim)
- GitHub: [denpiligrim/3dp-manager](https://github.com/denpiligrim/3dp-manager)
