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:
NW
2026-08-09 00:22:22 +01:00
parent 338a700b3a
commit d7680357af
13 changed files with 517 additions and 1192 deletions

207
docs/API.md Normal file
View 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` 02, `chatbot_max_tokens` 504000, `chatbot_max_history` 150 |
| 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`