From d7680357afaa5f1b6e71adf4ac031806034b4483 Mon Sep 17 00:00:00 2001 From: NW Date: Sun, 9 Aug 2026 00:22:22 +0100 Subject: [PATCH] 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 --- .env.example | 22 +- .gitignore | 8 +- README.md | 77 ++- VERSION.md | 9 +- admin-next/.zscripts/dev.pid | 1 - docker/docker-compose.web-testing.yml | 12 +- docs/API.md | 207 +++++++ docs/DATABASE.md | 220 ++++++++ docs/admin-frontend-spec.md | 750 -------------------------- package.json | 3 +- scripts/sync-agents.cjs | 391 -------------- templates/.dockerignore | 3 - tests/package.json | 6 +- 13 files changed, 517 insertions(+), 1192 deletions(-) delete mode 100755 admin-next/.zscripts/dev.pid create mode 100644 docs/API.md create mode 100644 docs/DATABASE.md delete mode 100644 docs/admin-frontend-spec.md delete mode 100644 scripts/sync-agents.cjs delete mode 100644 templates/.dockerignore diff --git a/.env.example b/.env.example index 386ac99..c006720 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ SUPER_ADMIN_IDS=123456789 SUPPORT_LINK=https://t.me/your_support # --- Catalog --- +# Путь к каталогу (используется ботом; по умолчанию не требуется) CATALOG_PATH=./catalog # --- Encryption (ОБЯЗАТЕЛЬНО! Без этого приложение упадёт) --- @@ -46,13 +47,26 @@ WG_ALLOWED_IPS=0.0.0.0/0,::/0 # --- Tor Proxy --- # SSH backend: куда Tor перенаправляет SSH (по умолчанию хост-машина) SSH_HOST_IP=host.docker.internal -# Имя контейнера магазина (для проброса админки через Tor) -SHOP_CONTAINER=telegram_shop_prod +# Имя контейнера админки (onion target). Жёстко задано в docker-compose.yml как tg_shop_admin. +SHOP_CONTAINER=tg_shop_admin -# --- Admin Panel --- +# --- Admin Panel (Next.js, admin-next/) --- +# Секрет входа в админку (токен, вводится на /login) ADMIN_SECRET=your_admin_token_here # SUPER_ADMIN_SECRET: If set to a different value than ADMIN_SECRET, users logging in # with this token get super_admin role (seed phrase access, commission management). # If not set or same as ADMIN_SECRET, all admins are super admins. SUPER_ADMIN_SECRET= -ADMIN_PORT=3001 \ No newline at end of file +# Публичный URL админки (для фото товаров и внешних ссылок) +ADMIN_URL=http://your-host:3000 +# Порт health-сервера бота (Docker healthcheck) +HEALTH_PORT=3001 + +# --- Bot i18n --- +# Язык по умолчанию: en / es / de +DEFAULT_LANGUAGE=en + +# --- AI Chatbot (admin-next) --- +# OpenAI-совместимый endpoint и ключ (используются, если не заданы в site_settings) +CHATBOT_API_ENDPOINT=https://api.openai.com/v1 +CHATBOT_API_KEY= diff --git a/.gitignore b/.gitignore index bd9d4d8..ce7d14d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,11 +21,8 @@ db/*.pre-merge-* # Root-level throwaway scripts /*.mjs -/*.mjs - # Production backups (contain secrets + DB snapshots — never commit) production-backup/ -screenshot-dash.cjs # Kilo agent configuration (managed locally, not committed) .kilo/ @@ -33,3 +30,8 @@ kilo-meta.json kilo.jsonc AGENTS.md admin-next/.env + +# Admin dev artifacts +admin-next/.zscripts/*.pid +admin-next/.next/ +admin-next/node_modules/ diff --git a/README.md b/README.md index 8e90ea4..bebd173 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,11 @@ curl -o /dev/null -w "%{http_code}\n" http://localhost:3000/login # новая | `SUPER_ADMIN_IDS` | — | ID супер-админов | | `SUPPORT_LINK` | — | Ссылка на поддержку | | `DEFAULT_LANGUAGE` | — | Язык по умолчанию (`en`, `es`, `de`; по умолчанию `en`) | +| `HEALTH_PORT` | — | Порт health-сервера бота (по умолчанию 3001) | | `SSH_HOST_IP` | — | Куда Tor перенаправляет SSH (по умолчанию host.docker.internal) | -| `SHOP_CONTAINER` | — | Имя контейнера магазина (по умолчанию telegram_shop_prod) | +| `SHOP_CONTAINER` | — | Имя контейнера админки (onion target; по умолчанию tg_shop_admin) | +| `CHATBOT_API_ENDPOINT` | — | OpenAI-совместимый endpoint чатбота (fallback) | +| `CHATBOT_API_KEY` | — | API-ключ чатбота (fallback) | | `WG_ENABLED` | — | `true` / `false` (по умолчанию `false`) | | `WG_PRIVATE_KEY` | — | Приватный ключ WireGuard | | `WG_PUBLIC_KEY` | — | Публичный ключ WireGuard | @@ -287,37 +290,45 @@ src/i18n/ ## Структура проекта ``` -├── src/ -│ ├── config/ # Конфигурация (БД, крипто) -│ ├── context/ # Контекст и состояния бота -│ ├── handlers/ # Обработчики команд +├── src/ # Telegram-бот (Node.js) +│ ├── config/ # Конфигурация (БД, крипто) +│ ├── context/ # Контекст и состояния бота +│ ├── handlers/ # Обработчики команд │ │ ├── adminHandlers/ # Обработчики админа (Telegram) -│ │ └── userHandlers/ # Обработчики пользователя -│ ├── i18n/ # Интернационализация -│ │ ├── index.js # tForUser(), tForLang(), LANGUAGE_NAMES -│ │ └── locales/ # en.json, es.json, de.json (201 ключ) -│ ├── middleware/ # Промежуточные обработчики -│ ├── migrations/ # Миграции БД -│ ├── models/ # Модели данных -│ ├── router/ # Роутинг callback/text бота -│ ├── services/ # Бизнес-логика (вкл. chatbotService, leadService) -│ ├── utils/ # Утилиты (логирование, валидация, ошибки) -│ ├── healthServer.js # Минимальный HTTP /health (Docker healthcheck) -│ └── index.js # Точка входа -├── admin-next/ # НОВАЯ админ-панель (Next.js 16 + Prisma + shadcn/ui) +│ │ └── userHandlers/ # Обработчики пользователя +│ ├── i18n/ # Интернационализация +│ │ ├── index.js # tForUser(), tForLang(), LANGUAGE_NAMES +│ │ └── locales/ # en.json, es.json, de.json +│ ├── middleware/ # Промежуточные обработчики +│ ├── migrations/ # Миграции БД (001–014) +│ ├── models/ # Модели данных +│ ├── router/ # Роутинг callback/text бота +│ ├── services/ # Бизнес-логика (вкл. chatbotService, leadService) +│ ├── utils/ # Утилиты (логирование, валидация, ошибки) +│ ├── __tests__/ # Юнит-тесты (vitest) +│ ├── healthServer.js # Минимальный HTTP /health (Docker healthcheck) +│ └── index.js # Точка входа +├── admin-next/ # Админ-панель (Next.js 16 + Prisma + shadcn/ui) │ ├── prisma/schema.prisma # Схема БД (та же SQLite, что у бота) -│ ├── src/app/api/ # 45 API-роутов (каталог, заказы, лиды, чатбот) -│ ├── src/components/ # 76 UI-компонентов (dashboard, wallets, leads...) +│ ├── src/app/api/ # 45 API-роутов (см. docs/API.md) +│ ├── src/components/ # UI-компоненты (dashboard, wallets, leads...) │ └── Dockerfile # Multi-stage Next.js standalone -├── tor-proxy/ # Tor прокси для SSH и админки -│ ├── Dockerfile # Alpine + Tor образ -│ ├── entrypoint.sh # Генерация torrc, валидация env vars -│ ├── get-onions.sh # Скрипт обновления .env с onion-адресами -│ └── hosts/ # Директория для onion-hosts.txt -├── wg/ # WireGuard конфигурация -│ └── start.sh # Скрипт запуска контейнера -├── db/ # SQLite база данных (volume) -├── uploads/ # Загруженные фото (volume) +├── docs/ # Документация +│ ├── API.md # Справочник REST API админки +│ └── DATABASE.md # Структура БД (схема, связи, примечания) +├── tor-proxy/ # Tor прокси для SSH и админки +│ ├── Dockerfile # Alpine + Tor образ +│ ├── entrypoint.sh # Генерация torrc, валидация env vars +│ ├── get-onions.sh # Скрипт обновления .env с onion-адресами +│ └── hosts/ # Директория для onion-hosts.txt +├── wg/ # WireGuard конфигурация +│ └── start.sh # Скрипт запуска контейнера +├── tests/ # Web-тестирование (visual regression, E2E) +│ ├── scripts/ # Скрипты тестов (Playwright, pixelmatch) +│ └── visual/ # baseline/current/diff скриншоты +├── docker/ # Дополнительные compose-конфиги (web-testing) +├── db/ # SQLite база данных (volume) +├── uploads/ # Загруженные фото (volume) ├── Dockerfile # Multi-stage сборка магазина ├── docker-compose.yml # Конфигурация трёх контейнеров (бот, админка, tor) ├── install.sh # Установщик (POSIX sh) @@ -326,6 +337,14 @@ src/i18n/ └── package.json ``` +## Документация + +| Документ | Содержание | +|---|---| +| [`docs/API.md`](docs/API.md) | Справочник REST API админ-панели: все эндпоинты, методы, query-параметры, коды ошибок | +| [`docs/DATABASE.md`](docs/DATABASE.md) | Структура БД: таблицы, колонки, связи, примечания | +| [`VERSION.md`](VERSION.md) | История версий и changelog | + ## Разработка ```bash diff --git a/VERSION.md b/VERSION.md index 46f0004..a6ebce2 100644 --- a/VERSION.md +++ b/VERSION.md @@ -8,10 +8,17 @@ ## Current Version -**v1.2.7** — 2026-08-08 +**v1.2.8** — 2026-08-09 ## Changelog +### v1.2.8 — 2026-08-09 +- **chore**: repo cleanup — removed stale `docs/admin-frontend-spec.md` (old Express/EJS admin), unused `templates/` (SmartAdmin copy), dead `scripts/sync-agents.cjs`, committed `admin-next/.zscripts/dev.pid` +- **docs**: added `docs/API.md` (admin REST API reference) and `docs/DATABASE.md` (DB schema); README structure updated +- **env**: `.env.example` refreshed — removed stale `ADMIN_PORT`/`SHOP_CONTAINER` defaults, added `ADMIN_URL`, `HEALTH_PORT`, `DEFAULT_LANGUAGE`, `CHATBOT_API_ENDPOINT`, `CHATBOT_API_KEY` +- **ci**: added `npm run lint` script (syntax check of `src/`) so Gitea workflows no longer fail +- **chore**: rebranded web-testing suite from APAW to telegram-shop (package name, container names) + ### v1.2.7 — 2026-08-08 - **refactor**: Remove old Express/EJS admin panel (replaced by Next.js admin in admin-next/); bot now has standalone health server; SUPER_ADMIN_SECRET for admin auth diff --git a/admin-next/.zscripts/dev.pid b/admin-next/.zscripts/dev.pid deleted file mode 100755 index 4872852..0000000 --- a/admin-next/.zscripts/dev.pid +++ /dev/null @@ -1 +0,0 @@ -1119 diff --git a/docker/docker-compose.web-testing.yml b/docker/docker-compose.web-testing.yml index 6796d07..aa4c87f 100644 --- a/docker/docker-compose.web-testing.yml +++ b/docker/docker-compose.web-testing.yml @@ -18,7 +18,7 @@ services: # ─── Screenshot Capture: Create Baselines ───────────────────────── screenshot-baseline: image: mcr.microsoft.com/playwright:v1.52.0-noble - container_name: apaw-screenshot-baseline + container_name: tgshop-screenshot-baseline working_dir: /app volumes: - ../tests:/app/tests @@ -38,7 +38,7 @@ services: # ─── Screenshot Capture: Create Current ────────────────────────── screenshot-current: image: mcr.microsoft.com/playwright:v1.52.0-noble - container_name: apaw-screenshot-current + container_name: tgshop-screenshot-current working_dir: /app volumes: - ../tests:/app/tests @@ -58,7 +58,7 @@ services: # ─── Visual Regression: Compare Screenshots ────────────────────── visual-compare: image: node:24-alpine - container_name: apaw-visual-compare + container_name: tgshop-visual-compare working_dir: /app volumes: - ../tests:/app/tests @@ -76,7 +76,7 @@ services: # Captures current screenshots and compares against baselines visual-tester: image: mcr.microsoft.com/playwright:v1.52.0-noble - container_name: apaw-visual-tester + container_name: tgshop-visual-tester working_dir: /app volumes: - ../tests:/app/tests @@ -106,7 +106,7 @@ services: # ─── Console Error Monitor ────────────────────────────────────── console-monitor: image: mcr.microsoft.com/playwright:v1.52.0-noble - container_name: apaw-console-monitor + container_name: tgshop-console-monitor working_dir: /app volumes: - ../tests:/app/tests @@ -131,7 +131,7 @@ services: # Uses @mizchi/vlmkit for AI-powered visual regression testing vlmkit: image: node:24-alpine - container_name: apaw-vlmkit + container_name: tgshop-vlmkit working_dir: /app volumes: - ../tests:/app/tests diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..b97f403 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,207 @@ +# 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` diff --git a/docs/DATABASE.md b/docs/DATABASE.md new file mode 100644 index 0000000..34372cf --- /dev/null +++ b/docs/DATABASE.md @@ -0,0 +1,220 @@ +# Telegram Shop — Database Schema + +Единая SQLite-база (`db/shop.db`), используемая **одновременно** ботом (`src/`, better-sqlite3) и админ-панелью (`admin-next/`, Prisma). Источник истины схемы: `admin-next/prisma/schema.prisma`. + +Миграции бота: `src/migrations/` (001–014, runner: `src/migrations/runner.js`). + +## Схема + +### users — пользователи Telegram + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| telegram_id | TEXT UNIQUE | ID в Telegram | +| username | TEXT? | | +| country / city / district | TEXT? | География | +| status | INTEGER (0) | 0=active, 2=blocked | +| total_balance | REAL (0) | Основной баланс | +| bonus_balance | REAL (0) | Бонусный баланс | +| language | TEXT ('en') | en / es / de | +| language_set | INTEGER (0) | Флаг выбора языка | +| notes | TEXT? | Заметки админа (миграция 014) | +| created_at | DATETIME | | + +Связи: `wallets`, `transactions`, `purchases`. + +### crypto_wallets — криптокошельки + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| user_id | INTEGER FK → users | | +| wallet_type | TEXT | BTC / LTC / ETH / USDT / USDC | +| address | TEXT | | +| derivation_path | TEXT? | | +| mnemonic | TEXT? | Seed-фраза (зашифрована) | +| balance | REAL (0) | | +| created_at | DATETIME | | + +Уникальность: `(user_id, wallet_type)` — один кошелёк каждого типа на пользователя. + +### transactions — транзакции + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| user_id | INTEGER FK → users | | +| wallet_type | TEXT | | +| tx_hash | TEXT? | | +| amount | REAL | | +| created_at | DATETIME | | + +### locations — локации + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| country | TEXT | | +| city | TEXT | | +| district | TEXT ('') | | +| is_active | INTEGER (1) | 0/1 | +| created_at | DATETIME | | + +Уникальность: `(country, city, district)`. + +### categories — категории + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| location_id | INTEGER FK → locations | | +| name | TEXT | | +| is_active | INTEGER (1) | | +| created_at | DATETIME | | + +Уникальность: `(location_id, name)`. + +### subcategories — подкатегории + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| category_id | INTEGER FK → categories | | +| name | TEXT | | +| is_active | INTEGER (1) | | +| created_at | DATETIME | | + +Уникальность: `(category_id, name)`. + +### products — товары + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| location_id | INTEGER FK → locations | | +| category_id | INTEGER FK → categories | | +| subcategory_id | INTEGER? FK → subcategories | | +| name | TEXT | | +| description | TEXT? | | +| private_data | TEXT? | Скрытый контент (выдаётся после покупки) | +| price | REAL | | +| quantity_in_stock | INTEGER (0) | Для mono-товаров = 999999 | +| photo_url | TEXT? | Публичное фото | +| hidden_photo_url | TEXT? | Скрытое фото | +| hidden_coordinates | TEXT? | Скрытые координаты | +| hidden_description | TEXT? | Скрытое описание | +| is_mono | INTEGER (0) | 1 = неограниченный товар | +| created_at | DATETIME | | + +### purchases — покупки + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| user_id | INTEGER FK → users | | +| product_id | INTEGER FK → products | | +| wallet_type | TEXT? | | +| tx_hash | TEXT? | | +| quantity | INTEGER | | +| total_price | REAL | | +| purchase_date | DATETIME | | +| status | TEXT ('pending') | pending / completed / cancelled | + +### commission_payments — комиссионные выплаты + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| total_balance_usd | REAL | | +| commission_rate | REAL | | +| commission_amount_usd | REAL | | +| paid_amount_usd | REAL | | +| wallet_count | INTEGER | | +| note | TEXT? | | +| created_at | DATETIME | | + +### audit_log — журнал аудита + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| action | TEXT | `balance_adjust`, `operator_connect`, `seed_phrase_viewed`, `clear_all` и др. | +| admin_id | TEXT | Роль или telegram_id | +| details | TEXT? | JSON-строка с контекстом | +| created_at | DATETIME | | + +### chat_sessions — чат-сессии (чатбот) + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| session_id | TEXT UNIQUE | | +| telegram_id | TEXT? | | +| lead_id | INTEGER? FK → leads | | +| messages | TEXT | JSON-массив `{role, content, timestamp}` | +| language | TEXT ('en') | | +| device / ip / country | TEXT? | | +| customer_profile | TEXT? | AI-профиль клиента (JSON) | +| is_active | BOOLEAN (true) | | +| operator_name | TEXT? | | +| auto_reply_disabled | BOOLEAN (false) | | +| operator_connected_at | DATETIME? | | +| created_at / updated_at | DATETIME | | + +### leads — лиды + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| telegram_id | TEXT? UNIQUE | | +| name / phone / email / telegram | TEXT? | | +| status | TEXT ('new') | new / contacted / qualified / lost / spam | +| verification | TEXT ('pending') | | +| notes | TEXT? | | +| custom_fields | TEXT ('{}') | JSON | +| geo_address | TEXT? | | +| ai_lead_score | REAL? | | +| created_at / updated_at | DATETIME | | + +Связь: `chatSessions`. + +### site_settings — настройки (ключ-значение) + +| Колонка | Тип | Описание | +|---|---|---| +| id | INTEGER PK | | +| key | TEXT UNIQUE | Например `chatbot_*` | +| value | TEXT | | +| created_at / updated_at | DATETIME | | + +### user_states — состояния пользователей бота + +| Колонка | Тип | Описание | +|---|---|---| +| chat_id | TEXT PK | | +| state_data | TEXT? | JSON состояния | +| updated_at | INTEGER | Unix-время | + +## Связи (ER-сводка) + +``` +users 1──N crypto_wallets +users 1──N transactions +users 1──N purchases +locations 1──N categories 1──N subcategories +locations 1──N products +categories 1──N products +subcategories 1──N products +products 1──N purchases +leads 1──N chat_sessions +leads 1──1 users (по telegram_id, не FK) +``` + +## Примечания + +- **Бот и админка работают с одной БД**: любые изменения мгновенно видны обеим сторонам +- `users` и `leads` — **отдельные таблицы**, связываются по `telegram_id` (не внешний ключ) +- `mnemonic` хранится зашифрованным (ключ `ENCRYPTION_KEY`) +- Каскадное удаление: wallets/transactions/purchases удаляются вместе с пользователем; products — вместе с location/category +- Миграции бота нумеруются `NNN_*.js`; Prisma-схема админки должна отражать ту же структуру diff --git a/docs/admin-frontend-spec.md b/docs/admin-frontend-spec.md deleted file mode 100644 index f5f8af8..0000000 --- a/docs/admin-frontend-spec.md +++ /dev/null @@ -1,750 +0,0 @@ -# Админ-панель Telegram Shop — Техническое описание фронтенда и API - -> **Назначение документа**: комплексное ТЗ для отдела фронтенда по реализации полноценного админ-кабинета поверх существующего бэкенда. -> **Версия бэкенда**: v1.2.4 (2026-08-05) -> **Дата**: 2026-08-05 -> **Статус**: актуально на момент передачи - ---- - -## Оглавление - -1. [Обзор системы](#1-обзор-системы) -2. [Технологический стек](#2-технологический-стек) -3. [Архитектура развёртывания](#3-архитектура-развёртывания) -4. [Доступ и безопасность](#4-доступ-и-безопасность) -5. [Схема базы данных](#5-схема-базы-данных) -6. [Карта эндпоинтов (полная)](#6-карта-эндпоинтов-полная) -7. [Спецификация экранов](#7-спецификация-экранов) -8. [Роли и права](#8-роли-и-права) -9. [Формат данных](#9-формат-данных) -10. [Существующие ограничения и подводные камни](#10-существующие-ограничения-и-подводные-камни) -11. [Требования к новому фронтенду](#11-требования-к-новому-фронтенду) -12. [API-контракты JSON (дополнить бэкенду)](#12-api-контракты-json-дополнить-бэкенду) -13. [Чек-лист приёмки](#13-чек-лист-приёмки) - ---- - -## 1. Обзор системы - -Telegram Shop — это **Telegram-бот-магазин** (Node.js) с **встроенной админ-панелью** на Express + EJS (шаблон SmartAdmin). Продажи идут через Telegram-бота, админка управляет всем магазином через браузер. - -Бизнес-сущности: - -| Сущность | Описание | -|---|---| -| **Users** | Покупатели из Telegram (id, ник, локация, статус, балансы, язык) | -| **Locations** | Гео-иерархия: Страна → Город → Район (округа) | -| **Categories / Subcategories** | Категории товаров, привязанные к локации; подкатегории внутри категории | -| **Products** | Товары: цена, остаток, публичное фото, скрытый контент (фото/координаты/описание), private-заметки, цифровые (mono) | -| **Purchases** | Заказы: пользователь, товар, валюта, tx_hash, количество, сумма, статус | -| **Crypto Wallets** | Криптокошельки пользователей (BTC/LTC/ETH/USDT/USDC), с зашифрованными seed-фразами | -| **Transactions** | Ончейн-транзакции пользователей | -| **Commission payments** | Платежи комиссии владельцу платформы (SaaS-модель) | -| **Audit log** | Журнал действий админов | -| **Locales (i18n)** | JSON-файлы переводов бота (en/es/de), редактируются из админки | - -**Ключевой факт**: бот и админка живут в **одном процессе** и используют **одну SQLite-базу** (`db/shop.db`) — поэтому любые изменения в админке мгновенно видны боту и наоборот. Отдельного REST API **нет** — админка рендерит EJS на сервере и ходит в БД напрямую. - ---- - -## 2. Технологический стек - -### Текущий (серверная часть — НЕ трогать без отдельной задачи) - -| Слой | Технология | -|---|---| -| Язык | Node.js ≥ 18 (ES Modules, `"type": "module"`) | -| Web-фреймворк | Express 4 | -| Шаблонизация | EJS + `express-ejs-layouts` (layout: `views/layout.ejs`) | -| Шаблон UI | SmartAdmin (собственные партиалы в `views/partials/`, SCSS в `public/sass/`, сборка `smartapp.min.css`) | -| БД | SQLite через `better-sqlite3` (обёртка `db.runAsync/allAsync/getAsync` в `src/config/database.js`) | -| Загрузка файлов | Multer (только картинки jpeg/png/webp/gif, ≤ 10 МБ) в `uploads/` | -| QR | `qrcode` (для seed-фраз) | -| CDN-ресурсы | Bootstrap 5.3.2, FontAwesome 6.4.2 с jsDelivr (только в legacy-`layout.js`) | - -### Текущий фронтенд (что есть) - -- **Два параллельных набора вьюх**: - 1. **EJS-шаблоны** (`views/*.ejs`) — используются роутами (dashboard, users, wallets, purchases, audit, settings, categories, locations, payment-wallets, seed, locales, catalog, products, product-edit, user-detail). - 2. **JS-рендер-функции** (`views/*.js`) — legacy/дублирующий слой, возвращающий HTML-строки (`layout()`, `renderCatalog()`, `renderWalletLayout()` и т.д.). **На практике роуты рендерят EJS; JS-слой частично устарел** (дублирует catalog и wallets). Новый фронтенд их не использует. -- Статика: Bootstrap 5 (SmartAdmin-тема), ApexCharts, jQuery, FontAwesome, собственные скрипты `public/scripts/`. -- AJAX-вызовы есть только в нескольких местах (catalog-модалка, wallets-обновление балансов, locales). - ---- - -## 3. Архитектура развёртывания - -``` -Internet - │ - ├── HTTPS/LAN ───────────────► telegram_shop_prod :3001 (Express: бот + админка) - │ - └── Tor Network ──► tor-proxy ──► HiddenService :80 ──► telegram_shop_prod :3001 - (admin onion-адрес, работа без HTTPS) -``` - -- Единственный контейнер `telegram_shop_prod` (node:22-alpine) слушает порт `3001` (`ADMIN_PORT`). -- Доступ к админке: напрямую `http://host:3001` или через `.onion` (Tor Browser). -- Volume: `db/` (SQLite), `uploads/` (фото товаров, отдаются через `/uploads`), `.env` (только чтение). -- WireGuard опционален (`WG_ENABLED`). - -**Следствие для фронтенда**: админка доступна по **HTTP без HTTPS** (в т.ч. через Tor). Это накладывает жёсткие ограничения: -- нельзя использовать Secure-куки; -- нельзя полагаться на Origin-проверку (Tor) — CSRF реализован вручную (см. §10); -- весь JS/CSS должен быть либо локальным, либо закешированным (CDN может быть недоступен из Tor). - ---- - -## 4. Доступ и безопасность - -### 4.1. Аутентификация (важно для фронта) - -Реализована в `src/admin/auth.js`. **Не JWT, а самописный HMAC-токен**: - -| Параметр | Значение | -|---|---| -| Кука | `admin_token` (httpOnly, `sameSite=false`, maxAge 24ч) | -| Формат токена | `base64(payload).hmac_sha256(payload)` | -| Payload | `{ role, jti, iat, exp }` | -| Секрет | `ADMIN_SECRET` (env) | -| Вход | POST `/login` с полем `token` (пароль = админ-токен) | -| Выход | GET `/logout` | - -Роли (определяются **каким секретом залогинились**): - -| Роль | Условие | Права | -|---|---|---| -| `admin` | токен = `ADMIN_SECRET` | Все страницы, кроме seed-выгрузки | -| `super_admin` | токен = `SUPER_ADMIN_SECRET` (если задан и ≠ ADMIN_SECRET) | + просмотр/экспорт seed-фраз, комиссии | - -При `SUPER_ADMIN_SECRET` не заданном — **все админы считаются супер-админами** (`config.SUPER_ADMIN_IDS` по умолчанию = `ADMIN_IDS`, но роли в админке задаются именно через секреты, не через `ADMIN_IDS`). - -Rate-limit логина: 5 попыток / 15 минут с IP. - -**Деструктивные действия** (`/seed/*`) требуют повторного ввода токена в поле `reauth_token` (middleware `requireReAuth`). - -### 4.2. Защита статики и загрузок - -- `/uploads/*` — только после `requireAuth`, заголовки `X-Content-Type-Options: nosniff` + `Content-Disposition: attachment` (фото не рендерятся inline из uploads, только скачивание). -- Файлы uploads именуются `{timestamp}-{hex}.{ext}`, MIME-белый список. -- CSRF: заглушка (`csrf.js` возвращает пустой токен и no-op) — **сознательно отключено ради совместимости с Tor**. См. §10. - -### 4.3. Существующие уязвимости, которые фронтенд не должен усугублять - -1. Нет реальной CSRF-защиты — все POST-формы уязвимы к cross-site-запросам. В новом фронте использовать токены, если бэкенд их вернёт, иначе как минимум не убирать подтверждения на деструктив. -2. Некоторые вьюхи выводят данные с ручным `esc()`/`escapeHtml()`; часть EJS использует `<%= %>` (экранируется) — но есть места с `JSON.stringify` прямо в `