chore: repo cleanup + docs for admin API and DB schema (v1.2.8)
- remove stale docs/admin-frontend-spec.md (old Express/EJS admin), unused templates/ (SmartAdmin copy), dead scripts/sync-agents.cjs, committed dev.pid - add docs/API.md (admin REST API reference) and docs/DATABASE.md (DB schema) - refresh .env.example: drop stale ADMIN_PORT/SHOP_CONTAINER, add ADMIN_URL, HEALTH_PORT, DEFAULT_LANGUAGE, CHATBOT_API_* - add npm run lint so Gitea workflows pass - rebrand web-testing suite from APAW to telegram-shop
This commit is contained in:
207
docs/API.md
Normal file
207
docs/API.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Telegram Shop — Admin API Reference
|
||||
|
||||
Актуальная документация по REST API админ-панели (`admin-next/`, Next.js 16 + Prisma).
|
||||
|
||||
- **Базовый URL**: `http://<host>: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`
|
||||
Reference in New Issue
Block a user