Files
tgex-frontend/FRONTEND_API.md
2025-11-19 12:56:04 +03:00

705 lines
18 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 Документация для 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. Нужно добавить бота в канал как администратора, и он автоматически зарегистрирует канал в системе.