705 lines
18 KiB
Markdown
705 lines
18 KiB
Markdown
# API Документация для Frontend
|
||
|
||
## Базовая информация
|
||
|
||
- **Base URL**: `/api/v1` (для всех эндпоинтов)
|
||
- **Формат данных**: JSON
|
||
- **Аутентификация**: JWT Bearer Token (в заголовке `Authorization: Bearer <token>`)
|
||
|
||
## Авторизация
|
||
|
||
### Процесс авторизации через Telegram
|
||
|
||
1. Пользователь запускает Telegram бота командой `/start` с параметром `login`
|
||
2. Бот создает одноразовый токен (живет 10 минут) и отправляет ссылку на веб-приложение
|
||
3. Фронтенд получает `token` из URL параметра (`/auth/complete?token=...`)
|
||
4. Фронтенд отправляет запрос на `GET /api/v1/auth/complete?token=<token>`
|
||
5. Бэкенд возвращает JWT токен, который нужно сохранить и использовать для всех последующих запросов
|
||
|
||
### GET `/api/v1/auth/complete`
|
||
|
||
Обмен одноразового токена на JWT access token.
|
||
|
||
**Query параметры:**
|
||
|
||
- `token` (string, обязательный) - одноразовый токен из Telegram бота
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||
}
|
||
```
|
||
|
||
**JWT токен содержит:**
|
||
|
||
- `user_id` (UUID) - ID пользователя
|
||
- `telegram_id` (integer) - Telegram ID пользователя
|
||
- `username` (string | null) - Telegram username
|
||
- `exp` - время истечения токена (7 дней по умолчанию)
|
||
|
||
**Ошибки:**
|
||
|
||
- `404` - токен не найден
|
||
- `400` - токен уже использован или истек
|
||
|
||
### GET `/api/v1/auth/me`
|
||
|
||
Получить информацию о текущем пользователе.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"telegram_id": 123456789,
|
||
"username": "user_name"
|
||
}
|
||
```
|
||
|
||
**Поля:**
|
||
|
||
- `id` (UUID) - ID пользователя в системе
|
||
- `telegram_id` (integer) - Telegram ID
|
||
- `username` (string | null) - Telegram username (может быть null)
|
||
|
||
---
|
||
|
||
## Целевые каналы (Target Channels)
|
||
|
||
Каналы пользователя, в которых установлен бот.
|
||
|
||
### GET `/api/v1/target_channels`
|
||
|
||
Получить список всех целевых каналов пользователя.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"target_channels": [
|
||
{
|
||
"id": "uuid",
|
||
"telegram_id": 123456789,
|
||
"title": "Название канала",
|
||
"username": "channel_username",
|
||
"is_active": true
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### DELETE `/api/v1/target_channels/{channel_id}`
|
||
|
||
Отключить целевой канал (удалить из системы).
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `channel_id` (UUID) - ID канала
|
||
|
||
**Response 200:** пустой ответ
|
||
|
||
---
|
||
|
||
## Внешние каналы (External Channels)
|
||
|
||
Каналы, в которых размещается реклама.
|
||
|
||
### GET `/api/v1/external_channels/target/{target_channel_id}`
|
||
|
||
Получить все внешние каналы, связанные с конкретным целевым каналом.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `target_channel_id` (UUID) - ID целевого канала
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"external_channels": [
|
||
{
|
||
"id": "uuid",
|
||
"telegram_id": 123456789,
|
||
"title": "Внешний канал",
|
||
"username": "external_channel",
|
||
"description": "Описание канала",
|
||
"subscribers_count": 10000
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### POST `/api/v1/external_channels`
|
||
|
||
Создать новый внешний канал.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"telegram_id": 123456789,
|
||
"title": "Название канала",
|
||
"username": "channel_username",
|
||
"description": "Описание",
|
||
"subscribers_count": 10000,
|
||
"target_channel_ids": ["uuid1", "uuid2"]
|
||
}
|
||
```
|
||
|
||
**Поля:**
|
||
|
||
- `telegram_id` (integer, обязательный) - Telegram ID канала
|
||
- `title` (string, обязательный) - название канала
|
||
- `username` (string, optional) - username канала (без @)
|
||
- `description` (string, optional) - описание канала
|
||
- `subscribers_count` (integer, optional) - количество подписчиков
|
||
- `target_channel_ids` (array of UUID, обязательный) - список ID целевых каналов для связи
|
||
|
||
**Response 200:** объект `ExternalChannelOutput`
|
||
|
||
### PATCH `/api/v1/external_channels/{channel_id}`
|
||
|
||
Обновить информацию о внешнем канале.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `channel_id` (UUID) - ID канала
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"title": "Новое название",
|
||
"username": "new_username",
|
||
"description": "Новое описание",
|
||
"subscribers_count": 15000
|
||
}
|
||
```
|
||
|
||
**Все поля опциональны.** Отправляйте только те, которые нужно изменить.
|
||
|
||
**Response 200:** объект `ExternalChannelOutput`
|
||
|
||
### PATCH `/api/v1/external_channels/{channel_id}/links`
|
||
|
||
Обновить связи внешнего канала с целевыми каналами.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `channel_id` (UUID) - ID канала
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"add_target_channel_ids": ["uuid1", "uuid2"],
|
||
"remove_target_channel_ids": ["uuid3"]
|
||
}
|
||
```
|
||
|
||
**Поля:**
|
||
|
||
- `add_target_channel_ids` (array of UUID, optional, default=[]) - добавить связи
|
||
- `remove_target_channel_ids` (array of UUID, optional, default=[]) - удалить связи
|
||
|
||
**Response 200:** объект `ExternalChannelOutput`
|
||
|
||
### DELETE `/api/v1/external_channels/{channel_id}`
|
||
|
||
Удалить внешний канал.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `channel_id` (UUID) - ID канала
|
||
|
||
**Response 200:** пустой ответ
|
||
|
||
---
|
||
|
||
## Креативы (Creatives)
|
||
|
||
Рекламные сообщения для размещения.
|
||
|
||
### GET `/api/v1/creatives`
|
||
|
||
Получить список креативов.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Query параметры:**
|
||
|
||
- `target_channel_id` (UUID, optional) - фильтр по целевому каналу
|
||
- `include_archived` (boolean, optional, default=false) - включить архивные
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"creatives": [
|
||
{
|
||
"id": "uuid",
|
||
"name": "Название креатива",
|
||
"text": "Текст рекламного сообщения",
|
||
"target_channel_id": "uuid",
|
||
"target_channel_title": "Название канала",
|
||
"created_at": "2024-01-01T12:00:00Z",
|
||
"status": "active",
|
||
"placements_count": 5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Статусы креатива:**
|
||
|
||
- `active` - активный
|
||
- `archived` - архивный
|
||
|
||
### GET `/api/v1/creatives/{creative_id}`
|
||
|
||
Получить конкретный креатив.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `creative_id` (UUID) - ID креатива
|
||
|
||
**Response 200:** объект `CreativeOutput`
|
||
|
||
### POST `/api/v1/creatives`
|
||
|
||
Создать новый креатив.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"name": "Название креатива",
|
||
"text": "Текст рекламного сообщения",
|
||
"target_channel_id": "uuid"
|
||
}
|
||
```
|
||
|
||
**Поля:**
|
||
|
||
- `name` (string, обязательный) - название креатива
|
||
- `text` (string, обязательный) - текст сообщения
|
||
- `target_channel_id` (UUID, обязательный) - ID целевого канала
|
||
|
||
**Response 200:** объект `CreativeOutput`
|
||
|
||
### PATCH `/api/v1/creatives/{creative_id}`
|
||
|
||
Обновить креатив.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `creative_id` (UUID) - ID креатива
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"name": "Новое название",
|
||
"text": "Новый текст",
|
||
"status": "archived"
|
||
}
|
||
```
|
||
|
||
**Все поля опциональны.**
|
||
|
||
**Response 200:** объект `CreativeOutput`
|
||
|
||
### DELETE `/api/v1/creatives/{creative_id}`
|
||
|
||
Удалить креатив.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `creative_id` (UUID) - ID креатива
|
||
|
||
**Response 200:** пустой ответ
|
||
|
||
---
|
||
|
||
## Размещения (Placements)
|
||
|
||
Размещения рекламы во внешних каналах.
|
||
|
||
### GET `/api/v1/placements`
|
||
|
||
Получить список размещений.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Query параметры:**
|
||
|
||
- `target_channel_id` (UUID, optional) - фильтр по целевому каналу
|
||
- `external_channel_id` (UUID, optional) - фильтр по внешнему каналу
|
||
- `creative_id` (UUID, optional) - фильтр по креативу
|
||
- `include_archived` (boolean, optional, default=false) - включить архивные
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"placements": [
|
||
{
|
||
"id": "uuid",
|
||
"target_channel_id": "uuid",
|
||
"target_channel_title": "Целевой канал",
|
||
"external_channel_id": "uuid",
|
||
"external_channel_title": "Внешний канал",
|
||
"creative_id": "uuid",
|
||
"creative_name": "Название креатива",
|
||
"placement_date": "2024-01-01T12:00:00Z",
|
||
"cost": 1000.5,
|
||
"comment": "Комментарий",
|
||
"ad_post_url": "https://t.me/channel/123",
|
||
"invite_link_type": "approval",
|
||
"invite_link": "https://t.me/+unique_invite_link",
|
||
"status": "active",
|
||
"subscriptions_count": 42,
|
||
"views_count": 1500,
|
||
"views_availability": "available",
|
||
"last_views_fetch_at": "2024-01-02T10:00:00Z",
|
||
"created_at": "2024-01-01T12:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Типы пригласительных ссылок (invite_link_type):**
|
||
|
||
- `public` - обычная открытая ссылка
|
||
- `approval` - ссылка с одобрением бота (отслеживание подписчиков)
|
||
|
||
**Статусы размещения (status):**
|
||
|
||
- `active` - активное
|
||
- `archived` - архивное
|
||
|
||
**Доступность просмотров (views_availability):**
|
||
|
||
- `unknown` - ещё не проверялось
|
||
- `available` - просмотры доступны через API
|
||
- `unavailable` - нет доступа к просмотрам
|
||
- `manual` - просмотры вводятся вручную
|
||
|
||
### GET `/api/v1/placements/{placement_id}`
|
||
|
||
Получить конкретное размещение.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Response 200:** объект `PlacementOutput`
|
||
|
||
### POST `/api/v1/placements`
|
||
|
||
Создать новое размещение.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"target_channel_id": "uuid",
|
||
"external_channel_id": "uuid",
|
||
"creative_id": "uuid",
|
||
"placement_date": "2024-01-01T12:00:00Z",
|
||
"cost": 1000.5,
|
||
"comment": "Комментарий",
|
||
"ad_post_url": "https://t.me/channel/123",
|
||
"invite_link_type": "approval"
|
||
}
|
||
```
|
||
|
||
**Поля:**
|
||
|
||
- `target_channel_id` (UUID, обязательный)
|
||
- `external_channel_id` (UUID, обязательный)
|
||
- `creative_id` (UUID, обязательный)
|
||
- `placement_date` (datetime, обязательный) - дата размещения
|
||
- `cost` (float, optional) - стоимость размещения
|
||
- `comment` (string, optional) - комментарий
|
||
- `ad_post_url` (string, optional) - ссылка на рекламный пост
|
||
- `invite_link_type` (enum, optional, default="public") - тип пригласительной ссылки
|
||
|
||
**Response 200:** объект `PlacementOutput`
|
||
|
||
### PATCH `/api/v1/placements/{placement_id}`
|
||
|
||
Обновить размещение.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Request body:**
|
||
|
||
```json
|
||
{
|
||
"placement_date": "2024-01-02T12:00:00Z",
|
||
"cost": 1500.0,
|
||
"comment": "Новый комментарий",
|
||
"ad_post_url": "https://t.me/channel/124",
|
||
"status": "archived"
|
||
}
|
||
```
|
||
|
||
**Все поля опциональны.**
|
||
|
||
**Response 200:** объект `PlacementOutput`
|
||
|
||
### DELETE `/api/v1/placements/{placement_id}`
|
||
|
||
Удалить размещение.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Response 200:** пустой ответ
|
||
|
||
---
|
||
|
||
## Просмотры (Views)
|
||
|
||
Работа с просмотрами рекламных постов.
|
||
|
||
### GET `/api/v1/placements/{placement_id}/views/history`
|
||
|
||
Получить историю просмотров размещения.
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Query параметры:**
|
||
|
||
- `from_date` (datetime, optional) - начало периода
|
||
- `to_date` (datetime, optional) - конец периода
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"histories": [
|
||
{
|
||
"id": "uuid",
|
||
"placement_id": "uuid",
|
||
"views_count": 1500,
|
||
"fetched_at": "2024-01-02T10:00:00Z",
|
||
"created_at": "2024-01-02T10:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### POST `/api/v1/placements/{placement_id}/views/fetch`
|
||
|
||
Получить актуальные просмотры через Telegram API (запросить обновление).
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"placement_id": "uuid",
|
||
"views_count": 1500,
|
||
"views_availability": "available",
|
||
"fetched_at": "2024-01-02T10:00:00Z",
|
||
"error_message": null
|
||
}
|
||
```
|
||
|
||
**При ошибке получения просмотров:**
|
||
|
||
```json
|
||
{
|
||
"placement_id": "uuid",
|
||
"views_count": null,
|
||
"views_availability": "unavailable",
|
||
"fetched_at": "2024-01-02T10:00:00Z",
|
||
"error_message": "Не удалось получить просмотры: нет доступа к каналу"
|
||
}
|
||
```
|
||
|
||
### POST `/api/v1/placements/{placement_id}/views/manual`
|
||
|
||
Обновить просмотры вручную (когда автоматическое получение недоступно).
|
||
|
||
**Headers:**
|
||
|
||
- `Authorization: Bearer <access_token>`
|
||
|
||
**Path параметры:**
|
||
|
||
- `placement_id` (UUID) - ID размещения
|
||
|
||
**Query параметры:**
|
||
|
||
- `views_count` (integer, обязательный) - количество просмотров
|
||
|
||
**Response 200:** объект `PlacementOutput` (обновленное размещение)
|
||
|
||
---
|
||
|
||
## Типы данных и enum
|
||
|
||
### DateTime
|
||
|
||
Все даты в формате ISO 8601 с timezone (UTC): `2024-01-01T12:00:00Z`
|
||
|
||
### UUID
|
||
|
||
Строковое представление UUID: `"550e8400-e29b-41d4-a716-446655440000"`
|
||
|
||
### CreativeStatus (enum)
|
||
|
||
- `"active"` - активный
|
||
- `"archived"` - архивный
|
||
|
||
### PlacementStatus (enum)
|
||
|
||
- `"active"` - активное
|
||
- `"archived"` - архивное
|
||
|
||
### InviteLinkType (enum)
|
||
|
||
- `"public"` - открытая публичная ссылка
|
||
- `"approval"` - ссылка с одобрением ботом
|
||
|
||
### PostViewsAvailability (enum)
|
||
|
||
- `"unknown"` - статус не определен
|
||
- `"available"` - просмотры доступны через API
|
||
- `"unavailable"` - просмотры недоступны
|
||
- `"manual"` - просмотры вводятся вручную
|
||
|
||
---
|
||
|
||
## Обработка ошибок
|
||
|
||
### Формат ошибки
|
||
|
||
Все ошибки возвращаются в стандартном формате FastAPI:
|
||
|
||
```json
|
||
{
|
||
"detail": "Описание ошибки"
|
||
}
|
||
```
|
||
|
||
### HTTP коды ответов
|
||
|
||
- `200` - успешный запрос
|
||
- `401` - не авторизован (невалидный/истекший токен)
|
||
- `403` - доступ запрещен (нет прав на ресурс)
|
||
- `404` - ресурс не найден
|
||
- `422` - ошибка валидации входных данных
|
||
- `500` - внутренняя ошибка сервера
|
||
|
||
### Авторизация
|
||
|
||
Для всех защищенных эндпоинтов (все, кроме `/api/v1/auth/complete`) необходимо передавать JWT токен:
|
||
|
||
```
|
||
Authorization: Bearer <access_token>
|
||
```
|
||
|
||
При отсутствии или невалидном токене вернется `401 Unauthorized`.
|
||
|
||
---
|
||
|
||
## Примечания
|
||
|
||
1. **Soft delete**: При удалении сущностей (креативы, размещения, внешние каналы) они помечаются как удаленные (`deleted_at`), но не удаляются физически из БД.
|
||
|
||
2. **Отслеживание подписчиков**: Работает только для размещений с `invite_link_type: "approval"`. Бот автоматически одобряет запросы на вступление и увеличивает счетчик подписок.
|
||
|
||
3. **Автоматическое обновление просмотров**: Фоновый worker периодически обновляет просмотры для активных размещений. Можно также запросить обновление вручную через `/api/v1/placements/{id}/views/fetch`.
|
||
|
||
4. **Целевые каналы подключаются только через Telegram бота**: Нельзя создать целевой канал через API. Нужно добавить бота в канал как администратора, и он автоматически зарегистрирует канал в системе.
|