improvement for the backend api
This commit is contained in:
704
FRONTEND_API.md
Normal file
704
FRONTEND_API.md
Normal file
@@ -0,0 +1,704 @@
|
||||
# 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. Нужно добавить бота в канал как администратора, и он автоматически зарегистрирует канал в системе.
|
||||
Reference in New Issue
Block a user