Files
telegram-shop/docs/admin-frontend-spec.md

52 KiB
Raw Blame History

Админ-панель Telegram Shop — Техническое описание фронтенда и API

Назначение документа: комплексное ТЗ для отдела фронтенда по реализации полноценного админ-кабинета поверх существующего бэкенда. Версия бэкенда: v1.2.4 (2026-08-05) Дата: 2026-08-05 Статус: актуально на момент передачи


Оглавление

  1. Обзор системы
  2. Технологический стек
  3. Архитектура развёртывания
  4. Доступ и безопасность
  5. Схема базы данных
  6. Карта эндпоинтов (полная)
  7. Спецификация экранов
  8. Роли и права
  9. Формат данных
  10. Существующие ограничения и подводные камни
  11. Требования к новому фронтенду
  12. API-контракты JSON (дополнить бэкенду)
  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 прямо в <script> (инъекция через данные). Новому фронту: все данные из API экранировать на клиенте.
  3. Пароль админа (ADMIN_SECRET) отправляется как обычное поле формы.
  4. Seed-фразы — сверхчувствительные данные; показывать только super_admin, с аудитом (logAudit: seed_phrase_viewed, seed_phrase_qr_viewed, csv_seed_export), желательно с подтверждением.

5. Схема базы данных

Источник истины: src/migrations/*.js (001012). SQLite, FK включены, WAL-режим.

5.1. users

Колонка Тип Описание
id INTEGER PK AUTOINCREMENT Внутренний ID
telegram_id TEXT UNIQUE NOT NULL ID в Telegram (строка!)
username TEXT Ник
country / city / district TEXT Гео из бота (снимок)
status INTEGER DEFAULT 0 0=активен, 2=заблокирован, прочее=удалён
total_balance REAL DEFAULT 0 Основной баланс в USD
bonus_balance REAL DEFAULT 0 Бонусный баланс в USD
language TEXT DEFAULT 'en' Язык бота (en/es/de)
language_set INTEGER DEFAULT 0 Флаг выбора языка
created_at DATETIME Регистрация

5.2. crypto_wallets

Колонка Тип Описание
id INTEGER PK
user_id INTEGER FK→users (CASCADE)
wallet_type TEXT BTC/LTC/ETH/USDT/USDC (+ архивные с суффиксом #N, отфильтровываются NOT LIKE '%#_%')
address TEXT Адрес
derivation_path TEXT Derivation path
mnemonic TEXT Зашифрована (encrypt(mnemonic, user_id))
balance REAL DEFAULT 0 Кэш баланса (обновляется с блокчейна)
created_at DATETIME
UNIQUE (user_id, wallet_type)

5.3. transactions

Колонка Тип Описание
id INTEGER PK
user_id INTEGER FK
wallet_type TEXT
tx_hash TEXT
amount REAL
created_at DATETIME

5.4. locations

Колонка Тип Описание
id INTEGER PK
country TEXT NOT NULL
city TEXT NOT NULL
district TEXT NOT NULL Район (может быть пустой строкой)
is_active INTEGER NOT NULL DEFAULT 1 Отключённые локации скрыты из бота
created_at DATETIME
UNIQUE (country, city, district)

5.5. categories

Колонка Тип Описание
id INTEGER PK
location_id INTEGER FK→locations (CASCADE)
name TEXT NOT NULL
is_active INTEGER NOT NULL DEFAULT 1
created_at DATETIME
UNIQUE (location_id, name)

5.6. subcategories

Колонка Тип Описание
id INTEGER PK
category_id INTEGER FK→categories (CASCADE)
name TEXT NOT NULL
is_active INTEGER NOT NULL DEFAULT 1
created_at DATETIME
UNIQUE (category_id, name)

5.7. products

Колонка Тип Описание
id INTEGER PK
location_id INTEGER FK→locations (CASCADE)
category_id INTEGER FK→categories (CASCADE)
subcategory_id INTEGER FK→subcategories (SET NULL)
name TEXT NOT NULL
description TEXT Публичное описание
private_data TEXT Внутренние заметки (не показывать)
price REAL NOT NULL CHECK (price > 0) USD
quantity_in_stock INTEGER DEFAULT 0 Остаток
photo_url TEXT Публичное фото (URL или /uploads/...)
hidden_photo_url TEXT Фото после покупки
hidden_coordinates TEXT Координаты после покупки (lat,lng)
hidden_description TEXT Текст после покупки
is_mono INTEGER DEFAULT 0 Цифровой товар: бесконечный остаток (1 ⇒ stock=999999, в боте продаётся без складских проверок)
created_at DATETIME

5.8. purchases

Колонка Тип Описание
id INTEGER PK
user_id INTEGER FK→users (CASCADE)
product_id INTEGER FK→products (CASCADE)
wallet_type TEXT Валюта оплаты
tx_hash TEXT Хэш транзакции
quantity INTEGER CHECK (quantity > 0)
total_price REAL CHECK (total_price > 0) USD
purchase_date DATETIME
status TEXT DEFAULT 'pending' pending / completed / cancelled

5.9. commission_payments

Колонка Тип Описание
id INTEGER PK
total_balance_usd REAL NOT NULL Снимок баланса на момент платежа
commission_rate REAL NOT NULL % комиссии
commission_amount_usd REAL NOT NULL Начисленная комиссия
paid_amount_usd REAL NOT NULL Фактически уплачено
wallet_count INTEGER NOT NULL Кошельков на момент
note TEXT
created_at DATETIME

5.10. audit_log

Колонка Тип Описание
id INTEGER PK
action TEXT NOT NULL Слаг действия: balance_adjust, csv_seed_export, seed_phrase_viewed, seed_phrase_qr_viewed, login и т.д.
admin_id TEXT NOT NULL Роль или ID
details TEXT JSON-строка с контекстом
created_at DATETIME

5.11. user_states

Колонка Тип Описание
chat_id TEXT PK
state_data TEXT JSON состояния диалога бота
updated_at INTEGER

6. Карта эндпоинтов (полная)

Базовый URL: http://<host>:3001. Все маршруты, кроме /login, /logout, /health, требуют куки admin_token (redirect на /login).

Легенда: 🔓 — только super_admin · ⚠️ — деструктивно, требует requireReAuth · 📁 — загрузка файла · 🔁 — редирект после POST.

6.1. Служебные

Метод Путь Назначение
GET /health Health-check → { status:'ok', uptime }
GET /login Страница логина (поле token)
POST /login Вход; body { token }; 429 при rate-limit; 401 неверный токен
GET /logout Выход + инвалидация jti
GET /error (рендер) Страница ошибки (глобальный обработчик)

6.2. Dashboard (роут: routes/dashboard.js)

Метод Путь Ответ
GET / EJS dashboard со статами и данными графиков

Query-параметры-флеши: ?seeded=1, ?cleared=1 (показывают сообщения).

Данные, передаваемые в шаблон (это и есть «API» дашборда):

Поле Тип Содержимое
stats.totalUsers int Всего пользователей
stats.totalProducts int Всего товаров
stats.totalPurchases int Всего покупок
stats.totalRevenue float Выручка по status='completed'
stats.totalSubcategories int Подкатегорий
stats.aov float Средний чек (completed)
stats.conversionRate float % (1 знак) Купившие / все пользователи × 100
stats.completedPurchases / pendingPurchases / cancelledPurchases int Сплит по статусам
chartData.days string[] Последние 7 дат YYYY-MM-DD
chartData.days30 string[] Последние 30 дат
chartData.revenueData number[] Выручка по дням (7д)
chartData.revenueData30 number[] Выручка по дням (30д)
chartData.usersData number[] Новые пользователи по дням (7д)
chartData.topProducts {name, qty, revenue}[] Топ-5 товаров за 30д (по кол-ву)
chartData.topSpenders {username, spent}[] Топ-5 покупателей за 30д
chartData.revenueByCategory {name, revenue}[] Выручка по категориям за 30д
chartData.topCountries {country, count}[] Топ-5 стран из locations
activities {type, id, username, item, total_price, date} (или {type:'audit', ...}) Последние 10 completed-покупок; если пусто — последние 10 audit_log
wallets {wallet_type, count, total}[] Сводка кошельков по валютам (верхний регистр)
commission {enabled, rate, due, currentCommission, lastPaidAmount, totalBalance} SaaS-комиссия: начислено/оплачено/к оплате

6.3. Catalog (роут: routes/catalog.js — дерево; routes/catalogProducts.js — товары)

Дерево: Страна → Город → Район(location) → Категории → Подкатегории.

Метод Путь Body / Param Назначение
GET /catalog query: ?loc=, ?cat=, ?sub= (фильтры), ?msg=&msg_type= (флеш) EJS catalog + treeHtml + products
POST /catalog/locations country, city, district? + локация
POST /catalog/locations/add-city country, city + город (district='')
POST /catalog/locations/add-district country, city, district + район
POST /catalog/locations/:id/update country, city, district Переименование
POST /catalog/locations/:id/toggle Вкл/выкл is_active
POST /catalog/locations/:id/delete Удаление (запрещено при наличии категорий/товаров)
POST /catalog/categories name, location_id + категория
POST /catalog/categories/json JSON {name, location_id} + категория (AJAX, возвращает объект категории)
POST /catalog/categories/:id/update name, location_id? Переименование/перенос
POST /catalog/categories/:id/toggle Вкл/выкл
POST /catalog/categories/:id/delete Удаление (блокируется при товарах)
POST /catalog/categories/:id/subcategories name + подкатегория
POST /catalog/categories/:id/subcategories/json JSON {name} + подкатегория (AJAX)
POST /catalog/subcategories/:id/update name Переименование
POST /catalog/subcategories/:id/toggle Вкл/выкл
POST /catalog/subcategories/:id/delete Удаление (блокируется при товарах)
POST /catalog/products multipart: поля товара + photo_file, hidden_photo_file + товар
POST /catalog/products/:id/edit multipart: поля товара + файлы Обновление
POST /catalog/products/:id/delete Удаление товара
GET /catalog/products/:id/json AJAX: товар с country, city, district, category_name, subcategory_name

Поля формы товара (обязательные: name, price>0, description, photo):

name, price, quantity_in_stock, description, photo_url, hidden_photo_url, hidden_coordinates, hidden_description, private_data, category_id, subcategory_id?, location_id?, is_mono

Логика:

  • is_mono=1quantity_in_stock принудительно 999999.
  • Если location_id не передан — берётся location_id категории.
  • Файл заменяет URL (photo_url), если загружен.
  • JSON-ответы: {error: '...'} при 400.

6.4. Products (роут: routes/products.js — упрощённый, дублирует catalog)

Метод Путь Назначение
GET /products EJS products (таблица до 100 товаров)
POST /products + товар (без файлов; photo_url обязателен)
GET /products/:id/edit EJS product-edit
POST /products/:id/update Обновление
POST /products/:id/delete Удаление

Рекомендация фронту: считать catalog основным экраном товаров; /products — legacy-дубль.

6.5. Categories (роут: routes/categories.js — плоские таблицы)

Метод Путь Назначение
GET /categories EJS categories + subcategoriesByCategory, флеши ?error=, ?success=
POST /categories + категория (name, location_id)
POST /categories/:id/update name, location_id
POST /categories/:id/toggle Вкл/выкл
POST /categories/:id/delete Удаление (блок при товарах; удаляет и подкатегории)
POST /categories/:id/subcategories + подкатегория
POST /categories/subcategories/:id/update name
POST /categories/subcategories/:id/toggle Вкл/выкл
POST /categories/subcategories/:id/delete Удаление (блок при товарах)

6.6. Locations (роут: routes/locations.js)

Метод Путь Назначение
GET /locations EJS locations с category_count, product_count
POST /locations + локация
POST /locations/:id/update country, city, district
POST /locations/:id/toggle Вкл/выкл
POST /locations/:id/delete Блок при категориях/товарах

6.7. Users (роут: routes/users.js)

Метод Путь Назначение
GET /users EJS users (последние 100, ORDER BY id DESC)
GET /users/:id EJS user-detail + последние 20 покупок с product_name
POST /users/:id/toggle-status Бан/разбан (status 0↔2)
POST /users/:id/adjust-balance amount, currency (total_balance

6.8. Wallets (роут: routes/wallets.js) 🔓 для seed-части

Метод Путь Назначение
GET /wallets EJS wallets; query ?user=, ?seeds=1; полный срез: users (с wallet_count), wallets выбранного юзера, stats (см. ниже)
POST /wallets/record-payment paid_amount, note → запись в commission_payments; редирект /wallets?payment=recorded
POST /wallets/export-seeds 🔓 CSV всех кошельков с расшифрованными seed-фразами; audit csv_seed_export
GET /wallets/refresh-balances/:userId AJAX: обновить балансы с блокчейна; ответ JSON см. ниже
GET /wallets/seed/:walletId 🔓 AJAX: расшифрованная seed-фраза + derivation; audit seed_phrase_viewed
GET /wallets/seed-qr/:walletId 🔓 AJAX: PNG QR-код seed-фразы (image/png); audit seed_phrase_qr_viewed

Формат stats (передаётся в шаблон wallets):

Поле Тип Описание
totals {btc,ltc,eth,usdt,usdc} Суммы балансов по валютам
walletCounts {...} Кол-во кошельков по валютам
usdValues {...} USD-оценка по валютам (курсы с CoinGecko)
totalUsd float Итог в USD
prices {btc,ltc,eth} Текущие курсы
totalWallets int Активных кошельков
commissionRate float %
currentCommission float Ставка × totalUsd
lastPaidAmount float Последняя оплата
commissionDue float max(0, current last)
commissionEnabled bool
commissionWallets {BTC,LTC,USDT,USDC,ETH} Адреса для оплаты комиссии
totalUsers int
payments commission_payments[] Последние 20 платежей
seedsPaid bool lastPaid ≥ current

Формат refresh-balances (JSON):

{
  "wallets": [{ "id", "wallet_type", "address", "balance", "usdValue", "created_at" }],
  "totalBalance": 123.45,
  "balances": { "BTC": { "amount", "usdValue" }, ... },
  "timestamp": "ISO",
  "error": "Blockchain API unavailable — showing cached balances"   // опционально
}

Формат seed/:walletId (JSON): { walletId, walletType, address, derivationPath, mnemonic, userId, username } (404 {error} при отсутствии).

Логика разблокировки seed: кнопка «Unlock» доступна, только если seedsPaid (комиссия оплачена). Параметр ?seeds=1 открывает таблицу всех seed-фраз.

6.9. Purchases (роут: routes/purchases.js)

Метод Путь Назначение
GET /purchases EJS purchases (последние 200, JOIN product_name)

6.10. Audit (роут: routes/audit.js)

Метод Путь Назначение
GET /audit EJS audit (последние 200 записей audit_log)

6.11. Settings (роут: routes/settings.js)

Метод Путь Назначение
GET /settings EJS settings; флеши ?saved=1, ?error=1
POST /settings Обновление .env

Редактируемые ключи (только они; остальные строки .env не трогаются):

BOT_TOKEN, SUPPORT_LINK, ADMIN_IDS, SUPER_ADMIN_IDS, WG_ENABLED, WG_ENDPOINT, WG_ADDRESS, WG_PUBLIC_KEY, WG_DNS, ADMIN_PORT, ADMIN_URL, CATALOG_PATH, GITEA_API_URL

Никогда не перезаписываются (показываются как •••••••): ENCRYPTION_KEY, ADMIN_SECRET, GITEA_TOKEN, WG_PRIVATE_KEY, WG_PRESHARED_KEY, COMMISSION_*.

⚠️ Изменения применяются только после рестарта контейнера (сообщение об этом на странице).

6.12. Payment Wallets (роут: routes/paymentWallets.js)

Метод Путь Назначение
GET /payment-wallets EJS payment-walletsтолько чтение конфига COMMISSION_WALLETS (адреса для уплаты комиссии владельцу)

6.13. Seed & Reset (роут: routes/seed.js) ⚠️ requireReAuth

Метод Путь Назначение
GET /seed EJS seed (форма с подтверждением токеном)
POST /seed/seed-demo Полная очистка БД + заливка демо-данных. Body: reauth_token
POST /seed/clear-all Полная очистка всех таблиц. Body: reauth_token

Очищаемые таблицы: purchases, transactions, crypto_wallets, audit_log, user_states, products, subcategories, categories, users, locations, commission_payments + сброс sqlite_sequence.

6.14. Locales (роут: routes/locales.js)

Метод Путь Назначение
GET /locales EJS locales + sections (ключи верхнего уровня из en.json)
POST /locales/save JSON {lang, key, value} (точечный путь, напр. menu.shop); запись в src/i18n/locales/{lang}.json; 400 при неверном lang

Языки: en, es, de (AVAILABLE_LANGUAGES). Структура файлов — вложенные объекты; ключ в запросе — dot-path.


7. Спецификация экранов

7.1. Login (/login)

  • Одно поле token (пароль-токен), кнопка Submit.
  • Ошибки: 401 Invalid token, 429 Too many attempts... (15 мин).
  • После успеха — redirect /.
  • Подсказка: если SUPER_ADMIN_SECRET задан — вход с ним даёт роль super_admin.

7.2. Dashboard (/)

KPI-карточки:

  • Пользователи всего, Товары, Покупки всего, Выручка (, completed), Средний чек (), Конверсия (%), Сплит по статусам (completed/pending/cancelled), Подкатегории.

Графики (данные готовы в chartData):

  1. Выручка 7 дней (line/area) — days × revenueData.
  2. Выручка 30 дней (line/area) — days30 × revenueData30.
  3. Новые пользователи 7 дней (bar) — days × usersData.
  4. Топ-5 товаров (bar) — topProducts.
  5. Топ-5 покупателей (bar) — topSpenders.
  6. Выручка по категориям (pie/donut) — revenueByCategory.
  7. Топ-5 стран (bar) — topCountries.

Блоки: Активность (последние покупки/аудит), Сводка кошельков по валютам, SaaS-комиссия (enabled/rate/due/последняя оплата).

7.3. Catalog (/catalog) — самый сложный экран

Две колонки:

  • Слева — дерево: accordion Страна → Город → Район → Категория → Подкатегория. У каждого узла:
    • счётчик товаров (включая подкатегории),
    • бейдж «Disabled» у неактивных,
    • inline-формы: переименовать, вкл/выкл (⏻), добавить ребёнка, удалить (✕, с confirm).
  • Справа — таблица товаров (до 200): ID, фото (40×40), название, категория, подкатегория, цена $, остаток (∞ для mono), действия: ✎ Edit (модалка), ✕ Delete (confirm).
  • Фильтры через клик по узлам дерева (?loc=, ?cat=, ?sub=).
  • Модалка товара (Add/Edit): каскадные селекты Страна→Город→Район→(Категория фильтруется по району)→Подкатегория; inline-создание категории/подкатегории через fetch к /json-эндпоинтам; чекбокс «Digital Product (infinite stock)» блокирует поле остатка; загрузка 2 фото (public, hidden) или URL; поля скрытого контента (фото, координаты, описание) и private_data.
  • Флеш-сообщения: ?msg= + ?msg_type= (success/error/info).

7.4. Products (/products) — legacy

Простая таблица (ID, фото, название, категория, цена, остаток, действия) + формы Add/Edit на отдельных страницах. Фронту: экран можно заменить ссылкой на Catalog.

7.5. Categories (/categories)

Плоские таблицы: категории (с локацией и product_count) + подкатегории (сгруппированы по категориям). Действия: add/edit/toggle/delete. Ошибки через ?error= (с счётчиком блокирующих товаров), успех через ?success=.

7.6. Locations (/locations)

Таблица: Страна/Город/Район, category_count, product_count, is_active, действия add/edit/toggle/delete. Ошибки/успехи как у Categories. Заблокированное удаление — с подсказкой «Remove categories/products first».

7.7. Users (/users, /users/:id)

  • Список (100): ID, Telegram ID, username, страна, город, статус (badge Active/Blocked/Deleted), баланс $, кнопки View и Ban/Unban.
  • Детальная (/users/:id): профиль + таблица покупок (20) + форма корректировки баланса (amount + выбор total_balance/bonus_balance) + audit-запись.

7.8. Wallets (/wallets)

  • Слева: поиск + список пользователей (статус, число кошельков), выбор ?user=.
  • Справа: карточка балансов выбранного (Main/Bonus/Available/Status), таблица кошельков (тип, адрес — клик=копировать, баланс 8 знаков, дата), кнопка «Refresh balances» (fetch /wallets/refresh-balances/:userId).
  • Owner Summary (для всех): Total USD, Users, Active Wallets, Commission Due; таблица «Balances by Currency»; блок комиссии: rate, total, full commission, last paid, due, форма «Record Payment» (paid_amount, note), адреса для оплаты (клик=копировать); таблица Payment History (delta между платежами).
  • Seed Phrases (super_admin, если комиссия оплачена): кнопка «Unlock», таблица всех seed-фраз (user, type, address, derivation, mnemonic — клик=копировать), кнопка «Export All Seeds as CSV» (/wallets/export-seeds). Если не оплачено — предупреждение и заблокированная кнопка.

7.9. Purchases (/purchases)

Таблица (200): ID, user (ссылка), product, qty, цена, валюта, дата, статус (badge completed/pending/failed). Нет фильтров и действий — только просмотр.

7.10. Audit (/audit)

Таблица (200): action, admin_id, details (JSON-строка), created_at. Только просмотр.

7.11. Settings (/settings)

Форма с секциями:

  • Bot: BOT_TOKEN (placeholder-маска), SUPPORT_LINK, ADMIN_IDS, SUPER_ADMIN_IDS.
  • WireGuard: WG_ENABLED (checkbox), WG_ENDPOINT, WG_ADDRESS, WG_PUBLIC_KEY, WG_DNS.
  • Admin: ADMIN_PORT, ADMIN_URL, CATALOG_PATH, GITEA_API_URL.
  • Только для чтения (маскированные •••••••): ENCRYPTION_KEY, ADMIN_SECRET, GITEA_TOKEN, WG_PRIVATE_KEY, WG_PRESHARED_KEY, COMMISSION_*.
  • Предупреждение: «Restart the application to apply changes».

7.12. Payment Wallets (/payment-wallets)

Таблица адресов комиссии (BTC/LTC/USDT/USDC/ETH) — только чтение; ссылка на Settings.

7.13. Locales (/locales)

  • Таблица: строки = ключи из en.json (секции-заголовки), колонки = языки en/es/de.
  • Редактирование значения: inline (fetch POST /locales/save {lang, key, value}).
  • Требование: 3 языка всегда синхронны по набору ключей; добавление ключа в одном — добавить в остальных (200+ ключей).

7.14. Seed & Reset (/seed)

Две опасные кнопки: «Seed Demo Data» и «Clear All Data». Обе требуют повторный ввод токена (reauth_token). В UI — двойное подтверждение (модалка + поле токена), предупреждение о необратимости.


8. Роли и права

Возможность admin super_admin
Все страницы, кроме seed-выгрузки
Просмотр/экспорт seed-фраз (/wallets/seed*, export-seeds) (401/403)
Запись комиссионных платежей (форма на /wallets)
Seed & Reset (/seed/*) (с reauth) (с reauth)

Middleware: requireAuth (все роуты после /login), requireSuperAuth (seed-эндпоинты), requireReAuth (seed/reset).


9. Формат данных

  • Деньги: USD, REAL. Выводить $X.XX.
  • Крипто-балансы: до 8 знаков, обрезать хвостовые нули.
  • Даты: DATETIME SQLite (YYYY-MM-DD HH:MM:SS, UTC). На клиенте конвертировать в локальное время.
  • Фото: абсолютный путь (например /uploads/...) или внешний URL. В таблицах — превью 4050px.
  • Status пользователя: 0 активен, 2 заблокирован, иное — удалён.
  • Status покупки: pending | completed | cancelled.
  • is_mono: 1 = цифровой (бесконечный остаток, показывать ∞).
  • Флеш-сообщения: через query-параметры (?msg=, ?error=, ?success=, ?seeded=1, ?cleared=1, ?saved=1, ?payment=recorded).

10. Существующие ограничения и подводные камни

  1. Нет REST API. Всё — SSR EJS + формы + редкие fetch. Для полноценного SPA бэкенду нужны JSON-эндпоинты (предложены в §12) — отдел фронтенда не должен проектировать свой API без согласования с бэкендом.
  2. CSRF отключён (заглушка). Новая админка не должна полагаться на Origin/SameSite; если оставляем формы — обязательны серверные токены или хотя бы подтверждения на деструктив.
  3. Работа через Tor: нельзя полагаться на внешние CDN (Bootstrap/FontAwesome/ApexCharts должны быть локальными), нельзя требовать HTTPS/WSS, куки Secure не работают.
  4. Нет пагинации нигде (LIMIT 100/200 фиксирован). При росте данных списки «режутся» — фронт может предложить пагинацию, но это потребует новых query-параметров на бэкенде.
  5. Дублирование каталога: /catalog (богатый) и /products (бедный) — менять оба или убрать один (решение за продуктом).
  6. Фото из uploads отдаются с Content-Disposition: attachment — их нельзя показывать <img> inline из /uploads/ (скачаются как файл). В таблицах товаров фото берутся из photo_url, который может указывать на внешний URL или /uploads/... — при /uploads превью не отобразится в браузере. Для галереи товаров нужно либо отдельный inline-эндпоинт, либо хранить публичные фото вне uploads (например, статика public/img/products/).
  7. Нет нотификаций/вебхуков — dashboard статичен до перезагрузки.
  8. Settings перезаписывают .env — изменения требуют рестарта; форма должна явно это сообщать.
  9. Seed/Reset опасны — полная очистка БД; в новом UI обязателен confirm + reauth (уже реализовано на бэкенде).
  10. i18n-файлы — живые данные админки; редактирование без синхронизации ключей ломает бота (fallback на en).
  11. Legacy JS-слой (views/*.js) частично дублирует EJS — не использовать как источник истины, ссылаться на роуты и EJS.

11. Требования к новому фронтенду

11.1. Целевой стек (рекомендация)

  • SPA на Vue 3 или React (или продолжение SSR EJS, если команда не готова к SPA).
  • Все ассеты локальные (сборка в public/dist), без CDN — совместимость с Tor.
  • Адаптивность: админкой пользуются с десктопа и планшета.
  • Тёмная/светлая тема (SmartAdmin уже имеет темы — можно переиспользовать SCSS-переменные).
  • Charts: ApexCharts (уже есть в проекте) или ECharts.

11.2. Обязательные экраны (приоритет)

  1. Dashboard — KPI + 7 графиков (данные в §6.2).
  2. Catalog — дерево + таблица товаров + модалка товара с каскадными селектами и загрузкой 2 фото.
  3. Users + User detail — список, бан/разбан, корректировка баланса.
  4. Wallets — пользователь + кошельки + Owner Summary + комиссия + seed (super_admin).
  5. Purchases — таблица со статусами.
  6. Audit — журнал.
  7. Categories / Locations — CRUD + toggle.
  8. Settings — форма .env с масками секретов.
  9. Locales — редактор переводов на 3 языка.
  10. Seed & Reset — подтверждённая деструктивная панель.
  11. Login / Logout.

11.3. UI/UX-требования

  • Все деструктивные действия — confirm-модалка (не window.confirm).
  • Все флеш-сообщения — toast/alert с автоскрытием.
  • Каскадные select'ы — с загрузкой и disabled-состояниями.
  • Копирование адресов/seed — по клику с визуальной обратной связью «Copied!».
  • Пустые состояния («No products found», «No users», «No wallets yet»).
  • Загрузочные спиннеры на fetch-запросах.
  • Ошибки API (400/401/403/404/500) — понятные сообщения пользователю.
  • Валидация форм на клиенте (price>0, name required, photo required, amount>0) + серверная.
  • Хлебные крошки, поиск по меню (уже есть в SmartAdmin).
  • Индикация роли (admin/super_admin) в шапке; скрывать seed-раздел для обычных админов.

11.4. Интеграционные требования

  • Авторизация: работа с кукой admin_token; при 401/403 от /wallets/seed* — редирект на /login или сообщение о недостатке прав.
  • Все POST-формы в SPA отправлять с Content-Type: application/x-www-form-urlencoded или multipart/form-data в зависимости от бэкенд-роута (см. §6).
  • Не вводить собственный роутинг API-путей — строго использовать карту §6 (или согласованные новые из §12).
  • Реализовать обработку флеш-query-параметров на клиенте.

11.5. Что НЕ делать

  • Не разворачивать отдельный фронтенд-сервер (админка живёт в контейнере бота; статика отдаётся из src/admin/public).
  • Не менять схему БД без миграций и без бэкенда.
  • Не показывать seed-фразы не-super_admin.
  • Не загружать фото в произвольные пути — только через существующий multipart-роут /catalog/products (+ file input) или /products.

12. API-контракты JSON (дополнить бэкенду)

Для SPA-режима предлагаются новые JSON-эндпоинты (требуют реализации на бэкенде — вне зоны ответственности фронта, но контракты фиксируем заранее). Все — под requireAuth, префикс /api.

Метод Путь Ответ
GET /api/stats Агрегат дашборда (поля из §6.2)
GET /api/users?search=&page=&limit= Пагинированный список пользователей
GET /api/users/:id Профиль + балансы
POST /api/users/:id/toggle-status {ok:true}
POST /api/users/:id/adjust-balance body {amount, currency}{ok:true, newBalance}
GET /api/catalog/tree Дерево (страна→город→район→категория→подкатегория) с is_active и счётчиками
GET /api/products?loc=&cat=&sub=&page=&limit= Пагинированный список товаров
GET /api/products/:id Товар целиком (вкл. скрытый контент и private_data)
POST /api/products Создание (JSON или multipart)
PUT /api/products/:id Обновление
DELETE /api/products/:id Удаление
GET /api/locations Список с counts
POST/PUT/DELETE /api/locations... CRUD
GET /api/categories / /api/subcategories Списки с counts
POST/PUT/DELETE /api/categories... CRUD
GET /api/purchases?status=&page=&limit= Пагинированные покупки с фильтром по статусу
PATCH /api/purchases/:id/status Смена статуса (требует согласования с ботом — статусы влияют на доставку контента!)
GET /api/wallets/overview Owner summary + комиссия + платежи
GET /api/wallets?userId= Кошельки пользователя
POST /api/wallets/refresh body {userId} → свежие балансы
POST /api/wallets/record-payment body {paidAmount, note}
GET /api/wallets/seeds 🔓 Список seed-фраз
GET /api/wallets/seeds/:id 🔓 Одна seed-фраза
GET /api/wallets/seeds/:id/qr 🔓 PNG QR
GET /api/audit?page=&limit= Пагинированный аудит
GET/POST /api/locales Чтение/сохранение ключей (как /locales/save)
GET/PUT /api/settings Чтение маскированных / запись разрешённых ключей
POST /api/seed/demo, /api/seed/clear ⚠️ с reauth_token
GET /api/session Текущая роль (admin/super_admin) для условного рендера

⚠️ Важно: PATCH статуса покупки должен быть синхронизирован с purchaseService (доставка скрытого контента происходит при completed).


13. Чек-лист приёмки

  • Логин/логаут работают; роль super_admin корректно отображается.
  • Dashboard: все KPI и 7 графиков рендерятся без ошибок.
  • Catalog: дерево полностью управляемо (add/rename/toggle/delete на всех 4 уровнях), товары CRUD с загрузкой 2 фото, каскадные селекты, mono-флаг.
  • Users: список, детальная, бан/разбан, корректировка баланса с audit.
  • Wallets: выбор пользователя, обновление балансов (fetch), Owner Summary, запись комиссии, история платежей, seed-блок с правами и QR/CSV.
  • Purchases: таблица со статусами и ссылками на пользователей.
  • Audit: журнал читается, details парсится.
  • Categories/Locations: CRUD с блокировками удаления и понятными ошибками.
  • Settings: сохраняются только разрешённые ключи; секреты маскированы.
  • Locales: inline-редактирование всех 3 языков, dot-path ключей.
  • Seed & Reset: двойное подтверждение, reauth-токен, предупреждения.
  • Все деструктивные действия с confirm; все флеши; все пустые состояния.
  • Работает через Tor (без CDN-зависимостей), тёмная тема.
  • Нет утечек seed/private_data/масок в DOM для не-super_admin.
  • Нет XSS: все данные экранированы на клиенте (в т.ч. из JSON в <script>).

Документ подготовлен на основе фактического кода: src/admin/server.js, src/admin/routes/*.js, src/admin/auth.js, src/admin/views/*.ejs, src/migrations/*.js, src/config/*.js, src/services/*.js. При изменении бэкенда — перегенерировать разделы 67.