# Telegram Shop — Admin API Reference Актуальная документация по REST API админ-панели (`admin-next/`, Next.js 16 + Prisma). - **Базовый URL**: `http://:3000` - **Формат**: JSON (`Content-Type: application/json`) - **Аутентификация**: cookie `admin_token` (httpOnly, 24ч), устанавливается через `POST /api/auth/login` ## Аутентификация | Метод | Путь | Описание | |---|---|---| | POST | `/api/auth/login` | Вход по токену. Body: `{ "token": "..." }`. Rate-limit: 5 попыток / 15 мин на IP (429). Устанавливает cookie `admin_token` | | GET | `/api/auth/session` | Проверка сессии. Ответ: `{ "role": "admin" \| "super_admin" }` (401 если нет/просрочен) | | POST | `/api/auth/logout` | Выход, удаляет cookie | ### Роли - `admin` — обычный администратор - `super_admin` — доступ к seed-фразам, экспорту, импорту, очистке БД Роль определяется при входе: если `SUPER_ADMIN_SECRET` не задан или равен `ADMIN_SECRET` — все админы получают `super_admin`. Иначе роль `super_admin` даёт только вход по `SUPER_ADMIN_SECRET`. **Все эндпоинты ниже требуют cookie `admin_token`** (иначе 401). Эндпоинты с пометкой 🔒 требуют роль `super_admin` (иначе 403). ## Дашборд и статистика ### GET `/api/stats/dashboard` Сводная статистика для дашборда: - counts: `totalUsers`, `totalProducts`, `totalPurchases`, `totalSubcategories`, `bannedUsers`, `activeWallets` - статусы покупок: `completedPurchases`, `pendingPurchases`, `cancelledPurchases` - метрики: `totalRevenue` (сумма completed), `aov`, `conversionRate` - временные ряды: `revenueByDay`, `purchasesByDay`, `newUsersByDay` (последние 30 дней) - `topProducts`, `recentPurchases`, `recentUsers` ## Каталог ### GET `/api/catalog/tree` Полное дерево каталога: локации, категории, подкатегории с `_count` (вложенные сущности). ### Локации | Метод | Путь | Описание | |---|---|---| | GET | `/api/locations/bulk` | Список локаций (с `_count` категорий/товаров) | | POST | `/api/locations/bulk` | Создать. Body: `{ country, city, district }` | | PUT | `/api/locations/[id]` | Обновить. Body: `{ country?, city?, district? }` | | PATCH | `/api/locations/[id]` | Переключить `is_active` (0/1) | | DELETE | `/api/locations/[id]` | Удалить (409 если есть связанные сущности) | ### Категории | Метод | Путь | Описание | |---|---|---| | GET | `/api/categories/bulk` | Список категорий (с локацией и `_count`) | | POST | `/api/categories/bulk` | Создать. Body: `{ name, locationId }` | | PUT | `/api/categories/[id]` | Обновить. Body: `{ name?, locationId? }` (409 при дубликате в локации) | | PATCH | `/api/categories/[id]` | Переключить `is_active` | | DELETE | `/api/categories/[id]` | Удалить (409 если есть связанные сущности) | ### Подкатегории | Метод | Путь | Описание | |---|---|---| | GET | `/api/subcategories/bulk` | Список подкатегорий | | POST | `/api/subcategories/bulk` | Создать. Body: `{ name, categoryId }` | | PUT | `/api/subcategories/[id]` | Обновить. Body: `{ name?, categoryId? }` | | PATCH | `/api/subcategories/[id]` | Переключить `is_active` | | DELETE | `/api/subcategories/[id]` | Удалить | ### Товары | Метод | Путь | Описание | |---|---|---| | GET | `/api/products/bulk` | Список. Query: `loc`, `cat`, `sub`, `search`, `page`, `limit` (≤100). Ответ: `{ data, total, page, limit }` | | POST | `/api/products/add` | Создать. Body: `{ locationId, categoryId, subcategoryId?, name, description?, privateData?, price, quantityInStock?, photoUrl?, hiddenPhotoUrl?, hiddenCoordinates?, hiddenDescription?, isMono? }`. Обязательны: `locationId, categoryId, name, price`. `isMono=1` → `quantityInStock=999999` | | GET | `/api/products/[id]` | Детали товара (с category, subcategory, location) | | PUT | `/api/products/[id]` | Обновить (частично) | | DELETE | `/api/products/[id]` | Удалить | | POST | `/api/products/[id]/clone` | Клонировать (имя получает суффикс `(Copy)`) | ## Пользователи | Метод | Путь | Описание | |---|---|---| | GET | `/api/users/bulk` | Список. Query: `search` (username/telegramId), `status` (0/2), `page`, `limit` (≤100). Ответ: `{ data, total, page, limit }` | | GET | `/api/users/[id]` | Детали: пользователь + кошельки + последние 20 покупок + связанный лид | | POST | `/api/users/[id]` | Обновить (username, country, city, district, notes и т.д.) | | PATCH | `/api/users/[id]` | Переключить `status` (0=active, 2=blocked) | | POST | `/api/users/[id]/adjust-balance` | Изменить баланс. Body: `{ amount: number, currency: "total_balance" \| "bonus_balance" }`. Пишет audit-запись | | POST | `/api/users/batch-status` | Массово сменить статус. Body: `{ userIds: number[], newStatus: 0 \| 2 }` | ## Кошельки | Метод | Путь | Описание | |---|---|---| | GET | `/api/wallets/bulk` | Пользователи, у которых есть кошельки. Query: `search`. Ответ: `{ users, totalWallets, totalBalance }` | | GET | `/api/wallets/[userId]` | Кошельки конкретного пользователя | | GET | `/api/wallets/overview` | Сводка: суммы и количество по типам (BTC/LTC/ETH/USDT/USDC), последние 20 комиссионных выплат | | GET | `/api/wallets/seeds` | 🔒 Seed-фразы всех кошельков (пишет audit `seed_phrase_viewed`) | | GET | `/api/wallets/export-seeds` | 🔒 Экспорт seed-фраз в CSV (Content-Disposition: attachment) | | POST | `/api/wallets/record-payment` | Записать комиссионную выплату. Body: `{ paidAmount: number, note? }` | ## Покупки | Метод | Путь | Описание | |---|---|---| | GET | `/api/purchases/bulk` | Список. Query: `status` (pending/completed/cancelled), `from`, `to` (YYYY-MM-DD), `page`, `limit` (≤100). Ответ: `{ data, total, page, limit }` | | PATCH | `/api/purchases/[id]` | Сменить статус. Body: `{ status: "completed" \| "cancelled" }`. Только для `pending` | | POST | `/api/purchases/batch-status` | Массово. Body: `{ purchaseIds: number[], status: "completed" \| "cancelled" }` (обновляет только pending) | ## Транзакции | Метод | Путь | Описание | |---|---|---| | GET | `/api/transactions/bulk` | Список. Query: `userId`, `page`, `limit` (≤100, по умолчанию 20). Ответ: `{ data, total, page, limit }` | ## Лиды и чат ### Лиды | Метод | Путь | Описание | |---|---|---| | GET | `/api/leads/bulk` | Список. Query: `search` (name/phone/email/telegram/telegramId), `status`, `page`, `limit` (≤100). Ответ: `{ data, total, page, limit }` | | GET | `/api/leads/[id]` | Детали лида + связанный пользователь (баланс, покупки, кошельки) | | PUT | `/api/leads/[id]` | Обновить (name, phone, email, telegram, status, notes, customFields и т.д.) | | GET | `/api/leads/[id]/activity` | Активность: `{ hourly: number[24], yearly: { "YYYY-MM-DD": count }, total }` (по audit_log) | | GET | `/api/leads/[id]/sessions` | Чат-сессии лида с распарсенными сообщениями | ### Чат (публичный виджет) | Метод | Путь | Описание | |---|---|---| | POST | `/api/chat` | Отправить сообщение в чат-виджет. Body: `{ sessionId?, message, language?, device?, ip?, country? }`. Создаёт/обновляет `ChatSession`, извлекает данные лида (имя/телефон/email/telegram), вызывает ИИ-провайдера, при `chatbot_sleep_mode` отвечает sleep-сообщением | ### Оператор | Метод | Путь | Описание | |---|---|---| | POST | `/api/operator` | Подключение/отключение оператора. Body: `{ sessionId, action: "connect" \| "disconnect", operatorName }`. Пишет audit `operator_connect` | ## Чатбот (настройки) | Метод | Путь | Описание | |---|---|---| | GET | `/api/admin/chatbot` | Настройки чатбота из `site_settings` (ключи `chatbot_*`), API-ключ маскируется | | PUT | `/api/admin/chatbot` | Обновить настройки. Валидация: `chatbot_temperature` 0–2, `chatbot_max_tokens` 50–4000, `chatbot_max_history` 1–50 | | GET | `/api/chatbot/models?endpoint=&apiKey=` | Список моделей провайдера через OpenAI-совместимый `/models`. Без query — берёт сохранённые настройки. Поддерживает OpenAI (`data[]`) и Ollama (`models[]`) | Ключи настроек чатбота (defaults): `chatbot_enabled`, `chatbot_sleep_mode`, `chatbot_sleep_message`, `chatbot_system_prompt`, `chatbot_temperature` (0.7), `chatbot_max_tokens` (1024), `chatbot_max_history` (20), `chatbot_knowledge_base`, `chatbot_provider` (ollama), `chatbot_api_endpoint`, `chatbot_api_key`, `chatbot_model`. ## Аудит | Метод | Путь | Описание | |---|---|---| | GET | `/api/audit/bulk` | Журнал аудита. Query: `page`, `limit` (≤200, по умолчанию 100), `userId`, `from`, `to`, `search`, `action`. Ответ: `{ data, total, page, limit }` | ## Локализация (i18n) | Метод | Путь | Описание | |---|---|---| | GET | `/api/locales` | Все переводы: `{ en: {...}, es: {...}, de: {...} }` | | PUT | `/api/locales` | Обновить ключ. Body: `{ lang, key, value }` | ## Настройки | Метод | Путь | Описание | |---|---|---| | GET | `/api/settings` | Список настроек (секреты маскированы, список в `_masked`) | | PUT | `/api/settings` | Обновить. Body: `{ key, value }`. Ответ: «Settings saved. Restart required.» | | GET | `/api/settings/export` | Полный экспорт БД в JSON (users, wallets, purchases, categories, subcategories, locations, products, auditLogs, commissionPayments, userStates) | | POST | `/api/settings/import` | 🔒 Импорт (заглушка: «Import not yet implemented») | ## Seed / Demo | Метод | Путь | Описание | |---|---|---| | GET | `/api/seed/data` | Проверка: `{ seeded: boolean }` (есть ли пользователи) | | POST | `/api/seed/demo` | Заполнить демо-данными. Body: `{ reauthToken }` (повторный ввод секрета) | | POST | `/api/seed/clear` | 🔒 Очистить все данные. Body: `{ reauthToken }`. Пишет audit `clear_all` | ## Корневой | Метод | Путь | Описание | |---|---|---| | GET | `/api` | Служебный ответ (health API) | ## Коды ошибок | Код | Значение | |---|---| | 400 | Невалидный запрос / отсутствуют обязательные поля | | 401 | Нет cookie `admin_token` или токен невалиден/просрочен | | 403 | Недостаточно прав (нужен `super_admin`) | | 404 | Сущность не найдена | | 409 | Конфликт (дубликат, есть связанные записи) | | 429 | Слишком много попыток входа | | 500 | Внутренняя ошибка | ## Соглашения - Пагинация: `page` (с 1), `limit` (кап зависит от эндпоинта: 50/100/200) - Даты: `from`/`to` в формате `YYYY-MM-DD` (to инклюзивно до конца дня) - `is_active`/`status` пользователей: `0` = активно, `2` = заблокировано - Статусы покупок: `pending` / `completed` / `cancelled` - Статусы лидов: `new` / `contacted` / `qualified` / `lost` / `spam`