From 306101649fb0977def98472525228a23f808ad5b Mon Sep 17 00:00:00 2001 From: treamz Date: Sun, 22 Mar 2026 12:45:48 +0300 Subject: [PATCH] Add comprehensive project documentation (DOCS.md, 1200+ lines) Complete technical documentation covering: - Architecture, middleware pipeline, file structure - All 16 tools with endpoints, deps, limits - Auth flow, roles, session config - AdminJS setup (ESM, models, password hooks) - Database schema (5 tables) - API reference (all endpoints) - Frontend architecture (shared.css/js, themes, components) - Security layers (SSRF, rate limits, headers, bcrypt) - Deploy (PM2, nginx, SSL, .env) - Monitoring, limitations, future plans --- DOCS.md | 1223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1223 insertions(+) create mode 100644 DOCS.md diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..73831c4 --- /dev/null +++ b/DOCS.md @@ -0,0 +1,1223 @@ +# WA Dev Tools — Полная документация проекта + +> Версия документации: март 2026 +> Репозиторий: `git.wadevelop.ru/treamz/wa-dev-tools` +> Продакшн: `https://wadevelop.ru` + +--- + +## Содержание + +1. [Обзор проекта](#1-обзор-проекта) +2. [Архитектура](#2-архитектура) +3. [Инструменты](#3-инструменты) +4. [Авторизация и роли](#4-авторизация-и-роли) +5. [AdminJS панель](#5-adminjs-панель) +6. [База данных](#6-база-данных) +7. [API Reference](#7-api-reference) +8. [Фронтенд архитектура](#8-фронтенд-архитектура) +9. [Безопасность](#9-безопасность) +10. [Деплой и инфраструктура](#10-деплой-и-инфраструктура) +11. [Мониторинг](#11-мониторинг) +12. [Известные ограничения](#12-известные-ограничения) +13. [Будущие доработки](#13-будущие-доработки) + +--- + +## 1. Обзор проекта + +**WA Dev Tools** — веб-платформа с набором утилит для разработчиков. Доступна по адресу `https://wadevelop.ru`. Целевая аудитория: веб-разработчики, которым нужны быстрые инструменты для работы с изображениями, видео, PDF, кодом и HTTP-запросами без установки десктопных приложений. + +### Технологии + +| Слой | Технология | +|------|-----------| +| Среда выполнения | Node.js 20 | +| Веб-фреймворк | Express 5 | +| База данных | MariaDB 10.11 | +| Обработка изображений | Sharp 0.34 | +| Обработка видео | FFmpeg (системный), ffprobe | +| Обработка PDF | pdf-lib, pdf-parse, Ghostscript (системный) | +| Парсинг HTML | @mozilla/readability, jsdom | +| Авторизация | express-session, bcrypt | +| Панель администратора | AdminJS 7 + @adminjs/sequelize | +| Стили | Tailwind CSS (CDN), кастомный shared.css | +| Процесс-менеджер | PM2 | + +### Сервер + +- Raspberry Pi 5, 8 GB RAM, Debian Bookworm +- Nginx как реверс-прокси с SSL от Let's Encrypt +- Файлы проекта: `/mnt/webdata/www/images.wadevelop.ru/` + +--- + +## 2. Архитектура + +### Файловая структура + +``` +/mnt/webdata/www/images.wadevelop.ru/ +│ +├── server.js # Точка входа: Express app, middleware pipeline, запуск сервера +├── package.json # Зависимости (name: "tinypng" — историческое название) +├── ecosystem.config.js # PM2 конфигурация +├── .env # Переменные окружения (не в git) +├── compress.log # Лог операций сжатия (пишется через lib/logger.logToFile) +│ +├── lib/ +│ ├── auth.js # Middleware авторизации: список публичных путей, редиректы +│ ├── admin.js # Настройка AdminJS: Sequelize модели, ресурсы, монтирование +│ ├── db.js # Пул соединений MariaDB (mysql2/promise), хелперы для users +│ ├── logger.js # Логгер: stdout + запись в compress.log через logToFile +│ └── ssrf.js # Защита от SSRF: проверка IP-диапазонов, DNS-резолвинг +│ +├── routes/ +│ ├── api.js # Публичный API: /api/settings, /api/tools, /api/content/:section +│ ├── auth.js # Авторизация: login, register, logout, /auth/me +│ ├── compress.js # Конвертер изображений: сжатие, ресайз, архивирование +│ ├── video.js # Видео конвертер: загрузка, конвертация через FFmpeg, прогресс +│ ├── pdf.js # PDF инструменты: merge, split, rotate, watermark и др. +│ ├── parser.js # Парсер статей: Readability + jsdom, кэш, несколько эндпоинтов +│ ├── httpclient.js # HTTP-клиент: прокси-запросы, история в сессии +│ ├── redirects.js # Redirect-анализатор: пошаговый обход цепочки редиректов +│ ├── placeholder.js # Placeholder-изображения: генерация через Sharp + SVG +│ ├── svgeditor.js # SVG редактор: страница + оптимизация + AI-иконки (lookup) +│ ├── status.js # Страница статуса: проверка DB, сервисов, uptime +│ ├── pages.js # Статические страницы: landing, dashboard, md, editor и др. +│ └── logs.js # [вспомогательный] логирование (не экспортирует маршруты) +│ +├── public/ +│ ├── shared.css # Все общие стили: переменные, sidebar, top-header, компоненты +│ ├── shared.js # Tailwind config, WA_TOOLS, WA_CATEGORIES, sidebar, тема +│ ├── landing.html # Лендинг (публичный) +│ ├── dashboard.html # Главная для авторизованных +│ ├── login.html # Форма входа +│ ├── register.html # Форма регистрации +│ ├── status.html # Страница статуса сервисов +│ ├── compress.html # Инструмент: конвертер изображений +│ ├── video.html # Инструмент: видео конвертер +│ ├── pdf.html # Инструмент: PDF инструменты +│ ├── parser.html # Инструмент: парсер статей +│ ├── httpclient.html # Инструмент: HTTP-клиент +│ ├── redirects.html # Инструмент: redirect-анализатор +│ ├── placeholder.html # Инструмент: placeholder изображения +│ ├── svgeditor.html # Инструмент: SVG редактор (full-screen) +│ ├── editor.html # Инструмент: фоторедактор (full-screen) +│ ├── formatter.html # Инструмент: code formatter (только фронтенд) +│ ├── sanitizer.html # Инструмент: HTML sanitizer (только фронтенд) +│ ├── converter.html # Инструмент: format converter (только фронтенд) +│ ├── password.html # Инструмент: генератор паролей (только фронтенд) +│ ├── md.html # Инструмент: markdown viewer (только фронтенд) +│ └── vendor/ # Локальные копии: tailwind.js, fonts.css +│ +├── uploads/ # Временные загруженные файлы (автоочистка 10 мин) +└── downloads/ # Результаты обработки (автоочистка 30 мин) +``` + +### Middleware pipeline + +Порядок регистрации middleware в `server.js` критичен для корректной работы авторизации: + +``` +Запрос + │ + ├─ helmet() # Security headers (CSP отключен) + ├─ session() # express-session: cookie maxAge 24ч + ├─ express.static('public') # Статика — всегда публична + ├─ /auth/* # Auth routes — до auth middleware + ├─ GET /api/settings # Публичные API для лендинга + ├─ GET /api/tools # + ├─ GET /api/content/advantages # + ├─ GET /api/content/dashboard # + ├─ /status/* # Статус — публичный + │ + ├─ authMiddleware() # ← ГРАНИЦА АВТОРИЗАЦИИ + │ + ├─ /api/* # Защищённый API + ├─ /health # Health check + ├─ /compress # Инструменты (защищены) + ├─ /download/:filename # Скачивание файлов (защищено) + ├─ /parser, /placeholder # ... + ├─ /httpclient, /redirects # ... + ├─ /svgeditor, /video, /pdf # ... + ├─ /admin/* # AdminJS (доп. проверка role=admin) + └─ pages router # Страницы: /, /dashboard, /md и т.д. +``` + +### Как работает авторизация + +`lib/auth.js` реализует middleware-функцию, которая пропускает запросы по белому списку без проверки: + +**Публичные пути** (без авторизации): +- `PUBLIC_PATHS`: `/`, `/health`, `/favicon.ico`, `/status`, `/placeholder` +- `PUBLIC_API`: `/api/settings`, `/api/tools`, `/api/content/advantages`, `/api/content/dashboard` +- `PUBLIC_FILES`: `/shared.css`, `/shared.js`, `/landing.html` +- `PUBLIC_PREFIXES`: `/auth/`, `/vendor/`, `/placeholder-img/`, `/status/` + +Всё остальное требует `req.session.user`. При отсутствии сессии: +- GET-запросы — редирект на `/auth/login` с сохранением `returnTo` +- API-запросы — `401 JSON` + +### Как работает AdminJS + +AdminJS v7 — ESM-only пакет. Поскольку основное приложение использует CommonJS (`"type": "commonjs"`), AdminJS загружается через динамический импорт внутри async IIFE в `server.js`: + +```js +const { setupAdmin } = require('./lib/admin'); +await setupAdmin(app); +``` + +Функция `setupAdmin` в `lib/admin.js`: +1. Создаёт отдельный экземпляр Sequelize (параллельно с пулом из `lib/db.js`) +2. Определяет Sequelize-модели для всех таблиц +3. Загружает `adminjs`, `@adminjs/express`, `@adminjs/sequelize` через `await import()` +4. Регистрирует адаптер, создаёт экземпляр AdminJS с ресурсами +5. Монтирует router через `app.use('/admin', authCheck, adminRouter)` + +Авторизация в AdminJS **не использует** встроенный механизм логина — вместо этого проверяется основная сессия Express (`req.session.user.role === 'admin'`). Статические ресурсы AdminJS (`.js`, `.css`, `.woff`) пропускаются без проверки. + +--- + +## 3. Инструменты + +Платформа содержит **16 инструментов** в 4 категориях. 5 инструментов работают полностью на фронтенде без серверных вызовов. + +--- + +### Категория: Изображения + +#### Конвертер изображений (`/compress`) + +**Что делает:** массовая конвертация и сжатие изображений с изменением формата и размера. Результат упаковывается в ZIP-архив. + +**Бэкенд:** `routes/compress.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /compress` | GET | Страница инструмента | +| `POST /compress` | POST | Загрузка файлов, обработка, возврат ссылки на ZIP | +| `GET /download/:filename` | GET | Скачивание ZIP (монтируется в server.js) | + +**Параметры POST-запроса (multipart/form-data):** +- `images[]` — файлы (до 50 штук) +- `format` — `original`, `webp`, `jpeg`, `png`, `avif`, `tiff`, `gif` +- `quality` — 1–100 (по умолчанию из `COMPRESS_QUALITY` в .env, обычно 80) +- `resize` — максимальный размер по длинной стороне в пикселях (0 = без ресайза) + +**Зависимости:** `sharp`, `archiver`, `multer`, `geoip-lite` + +**Принцип работы:** +1. Multer сохраняет файлы во временный `uploads/` +2. Sharp обрабатывает каждый файл (конвертация + опциональный ресайз) +3. Буферы добавляются в ZIP через archiver +4. ZIP сохраняется в `downloads/`, клиенту возвращается URL +5. Загруженные файлы удаляются немедленно, ZIP — через 30 минут +6. Кириллические имена файлов транслитерируются + +**Лимиты:** +- Размер файла: `MAX_FILE_SIZE_MB` (по умолчанию 20 MB) +- Количество файлов: `MAX_FILES` (по умолчанию 50) +- Rate limit: `RATE_LIMIT_MAX` запросов за `RATE_LIMIT_WINDOW_MS` (по умолчанию 30/мин) + +**Поддерживаемые форматы входа:** JPEG, PNG, WebP, AVIF, TIFF, GIF, BMP, SVG + +**Фронтенд:** `public/compress.html` — drag & drop загрузка, превью файлов, прогресс-бар, таблица статистики сжатия. + +--- + +#### Placeholder (`/placeholder`) + +**Что делает:** генерирует placeholder-изображения по URL-шаблону. Используется в разработке вместо реальных картинок. Работает **без авторизации**. + +**Бэкенд:** `routes/placeholder.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /placeholder` | GET | Страница с документацией | +| `GET /placeholder/:size/:bg/:fg` | GET | Генерация изображения | +| `GET /placeholder/:size/:bg/:fg/:text` | GET | С кастомным текстом | +| `GET /placeholder-img/:size/:bg/:fg` | GET | Алиас (тот же роутер) | + +**Параметры URL:** +- `size` — `300x200`, `1920x1080` и т.д. (макс. 4000x4000) +- `bg` — цвет фона в hex без `#` (например `cccccc`) +- `fg` — цвет текста +- `text` — текст поверх изображения (опционально) +- `?format=png|jpg|webp` — формат вывода (по умолчанию png) +- `?fontsize=N` — размер шрифта (по умолчанию автоматически) + +**Пример:** `GET /placeholder/800x400/0054e6/ffffff/Preview` + +**Зависимости:** `sharp` (SVG → растровое) + +**Фронтенд:** `public/placeholder.html` — конструктор URL, предпросмотр, код для вставки. + +--- + +#### SVG-редактор (`/svgeditor`) + +**Что делает:** редактирование SVG-кода с предпросмотром, оптимизацией и генерацией простых иконок по ключевому слову. + +**Бэкенд:** `routes/svgeditor.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /svgeditor` | GET | Страница редактора (full-screen) | +| `POST /api/svg-optimize` | POST | Оптимизация SVG (до 5 MB) | +| `POST /api/svg-ai` | POST | Генерация иконки по ключевому слову | + +**Оптимизация** (`/api/svg-optimize`) выполняется на сервере regexp-преобразованиями: +- Удаление ``, комментариев, ``, ``, `<desc>` +- Удаление пустых `<g>` и `data-*` атрибутов +- Округление чисел до 2 знаков, сжатие пробелов + +**AI-генерация** (`/api/svg-ai`) — это lookup-таблица из ~20 предопределённых SVG-путей (home, user, search, heart, star, и т.д.) в стилях outline/filled/duotone. Не использует внешний AI. + +**Параметры `/api/svg-ai`:** `{ keyword, style: "outline"|"filled"|"duotone", size, color }` + +**Фронтенд:** `public/svgeditor.html` — полноэкранный редактор, split-панель код/превью. + +--- + +#### Фоторедактор (`/editor`) + +**Что делает:** браузерный редактор изображений на Canvas API. Работает **полностью на фронтенде** — серверных вызовов нет. + +**Бэкенд:** `routes/pages.js` — только отдаёт `editor.html` + +**Фронтенд:** `public/editor.html` + `public/editor.js` — полноэкранный редактор. + +Возможности (реализованы на Canvas): обрезка, яркость/контраст/насыщенность, фильтры, вращение, отражение, сохранение в JPEG/PNG. + +--- + +#### Видео конвертер (`/video`) + +**Что делает:** конвертация видеофайлов между форматами, сжатие, извлечение аудио, создание GIF. Обработка асинхронная с polling-прогресса. + +**Бэкенд:** `routes/video.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /video` | GET | Страница инструмента | +| `POST /video/upload` | POST | Загрузка файла, получение `jobId` и метаданных | +| `POST /video/convert` | POST (JSON) | Запуск конвертации, возвращает немедленно | +| `GET /video/progress/:jobId` | GET | Статус и прогресс задачи (0–100%) | +| `GET /video/download/:filename` | GET | Скачивание результата | + +**Режимы конвертации (`mode`):** +- `convert` — смена формата (mp4, webm, avi, mkv) +- `compress` — сжатие с выбором качества (high/medium/low) +- `audio` — извлечение аудио (mp3, aac) +- `gif` — конвертация в анимированный GIF (макс. ширина 480px, 12 fps) + +**Зависимости:** FFmpeg, ffprobe (системные), `multer` + +**Лимиты:** +- Обычный пользователь: 200 MB +- Администратор: 1 GB +- Таймаут FFmpeg: 10 минут +- Rate limit: 10 запросов/мин + +**Хранение задач:** in-memory Map `jobs`, автоочистка через 30 минут. При рестарте сервера все задачи теряются. + +**Прогресс:** FFmpeg пишет в stderr строки вида `time=00:01:23`, роут парсит их и обновляет `job.progress`. + +**Фронтенд:** `public/video.html` — загрузка, выбор режима и параметров, polling прогресса, скачивание. + +--- + +### Категория: Код + +#### Code Formatter (`/formatter`) + +**Что делает:** форматирование CSS и JavaScript с правильными отступами. Работает **полностью на фронтенде**. + +**Бэкенд:** только `GET /formatter` → `formatter.html` + +**Алгоритм форматирования** реализован в `formatter.html` через regexp: +- Строки и комментарии сохраняются в массив (не затрагиваются) +- Расставляются переносы строк вокруг `{` и `}` +- Применяется отступ 2 пробела с счётчиком вложенности + +Нет зависимости от сторонних библиотек (prettier, babel и т.д.). + +**Фронтенд:** `public/formatter.html` — две textarea (input/output), переключатель CSS/JS, кнопки копировать/очистить, счётчик символов. + +--- + +#### HTML Sanitizer (`/sanitizer`) + +**Что делает:** очистка HTML от потенциально опасных тегов и атрибутов. Работает **полностью на фронтенде** через браузерный DOM. + +**Бэкенд:** только `GET /sanitizer` → `sanitizer.html` + +**Фронтенд:** `public/sanitizer.html` + +--- + +#### Format Converter (`/converter`) + +**Что делает:** конвертация данных между форматами (JSON, YAML, CSV и т.д.). Работает **полностью на фронтенде**. + +**Бэкенд:** только `GET /converter` → `converter.html` + +**Фронтенд:** `public/converter.html` + +--- + +### Категория: Веб + +#### Парсер статей (`/parser`) + +**Что делает:** извлекает основной текст статьи из URL с помощью алгоритма Readability (тот же, что использует Firefox Reader View). Возвращает заголовок, автора, текст, HTML, изображения, ссылки. + +**Бэкенд:** `routes/parser.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /parser` | GET | Страница инструмента | +| `GET /parse?url=` | GET | Полный разбор статьи | +| `GET /metadata?url=` | GET | Только OG/мета-теги | +| `GET /text?url=` | GET | Только текст (text/plain) | +| `GET /preview?url=` | GET | Карточка предпросмотра | + +**Зависимости:** `@mozilla/readability`, `jsdom`, `iconv-lite`, `geoip-lite` + +**Особенности:** +- SSRF-защита через `lib/ssrf.js` на всех эндпоинтах +- In-memory кэш: 50 записей, TTL 10 минут +- Определение кодировки из `Content-Type` и `<meta charset>` +- Лимит страницы: 2 MB +- Таймаут запроса: 10 секунд +- Rate limit: 20 запросов/мин + +**Ответ `/parse`:** +```json +{ + "url": "...", + "title": "...", + "author": "...", + "site": "...", + "date_published": "...", + "excerpt": "...", + "content_html": "...", + "content_text": "...", + "lead_image": "...", + "images": [...], + "links": [{"text": "...", "href": "..."}], + "word_count": 1234, + "lang": "ru" +} +``` + +**Фронтенд:** `public/parser.html` — поле URL, вкладки с результатами (текст, HTML, метаданные, изображения). + +--- + +#### HTTP-клиент (`/httpclient`) + +**Что делает:** выполняет произвольные HTTP-запросы с сервера (полезно для тестирования API, недоступных из браузера из-за CORS). История сохраняется в сессии. + +**Бэкенд:** `routes/httpclient.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /httpclient` | GET | Страница инструмента | +| `POST /api/proxy` | POST (JSON) | Выполнение HTTP-запроса | +| `GET /api/history` | GET | История запросов сессии (до 30) | +| `POST /api/history/add` | POST | Добавление записи в историю | +| `DELETE /api/history` | DELETE | Очистка истории | + +**Параметры `/api/proxy`:** +```json +{ + "url": "https://example.com/api", + "method": "POST", + "headers": {"Authorization": "Bearer ..."}, + "body": "{\"key\": \"value\"}", + "timeout": 30000 +} +``` + +**Ответ `/api/proxy`:** +```json +{ + "status": 200, + "statusText": "OK", + "headers": {...}, + "body": "...", + "time": 234, + "size": 1024, + "truncated": false, + "url": "..." +} +``` + +**Ограничения:** +- Тело ответа обрезается до 10 MB +- Таймаут: до 60 секунд (параметр `timeout`, но не более 60000 мс) +- SSRF-защита: приватные IP и localhost заблокированы +- Хедеры `host`, `connection`, `transfer-encoding` фильтруются из запроса +- Rate limit прокси: `PROXY_RATE_LIMIT_MAX` (по умолчанию 60/мин) + +**Фронтенд:** `public/httpclient.html` — Postman-подобный интерфейс: метод, URL, заголовки, тело, подсветка ответа, история. + +--- + +#### Redirect-анализатор (`/redirects`) + +**Что делает:** пошагово обходит цепочку HTTP-редиректов, показывает каждый шаг (статус, Location, время), выявляет проблемы (длинная цепочка, смешанный HTTP/HTTPS, 302 вместо 301). + +**Бэкенд:** `routes/redirects.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /redirects` | GET | Страница инструмента | +| `POST /api/redirect-analyze` | POST (JSON) | Анализ цепочки редиректов | + +**Параметры:** +```json +{ + "url": "https://example.com", + "userAgent": "desktop|mobile|googlebot", + "method": "GET|HEAD" +} +``` + +**Ответ:** +```json +{ + "chain": [ + {"url": "...", "status": 301, "statusText": "Moved Permanently", "location": "...", "time": 123, "headers": {...}}, + {"url": "...", "status": 200, "statusText": "OK", "location": null, "time": 456, "headers": {...}} + ], + "final_url": "...", + "total_time": 579, + "loop_detected": false, + "issues": ["long_chain", "302_not_301"] +} +``` + +**Ограничения:** +- Максимум 15 шагов в цепочке +- Таймаут на каждый шаг: 10 секунд +- SSRF-защита +- Rate limit: 20 запросов/мин + +**Фронтенд:** `public/redirects.html` — визуальная цепочка шагов, выделение ошибок, подсказки. + +--- + +### Категория: Утилиты + +#### Генератор паролей (`/password`) + +**Что делает:** генерация случайных паролей с настройкой длины и набора символов. Работает **полностью на фронтенде** через `crypto.getRandomValues()`. + +**Бэкенд:** только `GET /password` → `password.html` + +**Фронтенд:** `public/password.html` — слайдер длины, чекбоксы наборов символов, индикатор сложности, кнопка копирования. + +--- + +#### Markdown Viewer (`/md`) + +**Что делает:** рендеринг Markdown в HTML с предпросмотром. Работает **полностью на фронтенде**. + +**Бэкенд:** только `GET /md` → `md.html` + +**Фронтенд:** `public/md.html` — split-панель редактор/превью, библиотека marked.js. + +--- + +#### PDF инструменты (`/pdf`) + +**Что делает:** полный набор операций с PDF-файлами: объединение, разделение, поворот, удаление страниц, перестановка, водяной знак, нумерация, сжатие через Ghostscript, защита паролем, конвертация PDF → изображения и изображений → PDF. + +**Бэкенд:** `routes/pdf.js` + +| Эндпоинт | Метод | Описание | +|----------|-------|----------| +| `GET /pdf` | GET | Страница инструмента | +| `POST /pdf/upload` | POST | Загрузка PDF/изображений, возврат `fileId` | +| `GET /pdf/preview/:fileId` | GET | Превью первой страницы (PNG, Ghostscript) | +| `GET /pdf/preview/:fileId/:page` | GET | Превью конкретной страницы | +| `GET /pdf/thumbnails/:fileId` | GET | Список URL превью всех страниц | +| `GET /pdf/info/:fileId` | GET | Метаданные PDF (страницы, автор, заголовок) | +| `POST /pdf/merge` | POST (JSON) | Объединение нескольких PDF | +| `POST /pdf/split` | POST (JSON) | Извлечение страниц по диапазону | +| `POST /pdf/rotate` | POST (JSON) | Поворот страниц | +| `POST /pdf/delete` | POST (JSON) | Удаление страниц | +| `POST /pdf/reorder` | POST (JSON) | Перестановка страниц | +| `POST /pdf/watermark` | POST (JSON) | Добавление текстового водяного знака | +| `POST /pdf/pagenumbers` | POST (JSON) | Нумерация страниц | +| `POST /pdf/compress` | POST (JSON) | Сжатие через Ghostscript | +| `POST /pdf/protect` | POST (JSON) | Защита паролем (pdf-lib encrypt) | +| `POST /pdf/extract-text` | POST (JSON) | Извлечение текста | +| `POST /pdf/toImages` | POST (JSON) | PDF → изображения ZIP (Ghostscript) | +| `POST /pdf/fromImages` | POST | Изображения → PDF (pdf-lib) | +| `GET /pdf/download/:filename` | GET | Скачивание результата | + +**Зависимости:** `pdf-lib` (ESM, через dynamic import), `pdf-parse`, Ghostscript (системный, команда `gs`) + +**Хранение файлов:** in-memory Map `pdfFiles` с `fileId`, автоочистка через 30 минут. + +**Превью** кэшируется на диске в `downloads/preview_<fileId>_p<N>.png`, удаляется через 30 минут. + +**Лимиты:** +- Размер файла: 50 MB +- До 10 файлов за раз при загрузке +- До 50 изображений при конвертации из картинок + +**Фронтенд:** `public/pdf.html` — drag & drop загрузка, визуальная сетка миниатюр страниц, панель операций. + +--- + +## 4. Авторизация и роли + +### Flow регистрации + +1. `GET /auth/register` → форма `register.html` +2. `POST /auth/register` — валидация email (regexp), пароль ≥ 6 символов, проверка на дубликат +3. Хеширование пароля `bcrypt.hash(password, 10)` +4. Запись в `users`: email (lowercase), password_hash, display_name +5. Создание сессии `req.session.user = { id, email, name, role: 'user' }` +6. Редирект на `/dashboard` + +`display_name` санируется: HTML-теги удаляются, длина ограничена 100 символами. + +### Flow логина + +1. `GET /auth/login` → форма `login.html` +2. `POST /auth/login` — поиск пользователя по email, проверка `is_blocked`, `bcrypt.compare` +3. `db.updateLastLogin(id)` — обновление поля `last_login` +4. **Session regeneration** (`req.session.regenerate`) для защиты от session fixation +5. Новая сессия с данными пользователя +6. Редирект на `returnTo` или `/dashboard` + +### Структура сессии + +```js +req.session.user = { + id: 42, + email: "user@example.com", + name: "Иван", + role: "user" // или "admin" +} +``` + +Дополнительные поля сессии: +- `req.session.returnTo` — URL для редиректа после логина +- `req.session.httpHistory` — история HTTP-клиента (до 30 записей) + +### Роли + +| Возможность | `user` | `admin` | +|-------------|--------|---------| +| Доступ к инструментам | да | да | +| Лимит загрузки видео | 200 MB | 1 GB | +| Ссылка «Админ» в сайдбаре | нет | да | +| Доступ к `/admin` | нет | да | + +### Публичные маршруты (без авторизации) + +- Лендинг `/` +- Страницы: `/auth/login`, `/auth/register` +- API лендинга: `/api/settings`, `/api/tools`, `/api/content/advantages`, `/api/content/dashboard` +- Статус: `/status`, `/status/api` +- Placeholder: `/placeholder`, `/placeholder-img/*` +- Статика: `shared.css`, `shared.js`, `landing.html`, всё из `/vendor/` + +### Конфигурация сессии + +```js +session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + cookie: { + maxAge: 24 * 60 * 60 * 1000, // 1 день + httpOnly: true, // недоступно JS + secure: NODE_ENV === 'production', // только HTTPS + sameSite: 'lax', // защита от CSRF + } +}) +``` + +Сессии хранятся in-memory (MemoryStore Express). При рестарте PM2 все сессии теряются — пользователям нужно войти заново. + +### Rate limiting для auth + +15 минут, 10 попыток (применяется к `POST /auth/login` и `POST /auth/register`). + +--- + +## 5. AdminJS панель + +**URL:** `https://wadevelop.ru/admin` + +### Доступ + +Доступ контролируется двойной проверкой: +1. Основная сессия Express (`req.session.user` должен существовать) +2. Роль администратора (`req.session.user.role === 'admin'`) + +Если пользователь не авторизован или не является admin — редирект на `/auth/login` с `returnTo=/admin`. Встроенный логин AdminJS не используется. + +### Управляемые сущности + +| Сущность | Навигация | Описание | +|----------|-----------|----------| +| Users | Пользователи | Управление аккаунтами, блокировка, смена роли | +| Categories | Контент | Категории инструментов с иконками и цветами | +| Tools | Контент | Список инструментов, включение/отключение | +| ContentBlocks | Контент | Текстовые блоки лендинга (преимущества, контент дашборда) | +| Settings | Настройки | Ключ-значение настройки сайта | + +### Смена пароля пользователя + +В форме редактирования User есть два виртуальных поля: `password` и `password_confirm`. Они не хранятся в БД — вместо этого action-hook `before` обрабатывает их: + +1. Если `password` пустой — пропуск (пароль не меняется) +2. Если `password !== password_confirm` — ошибка валидации +3. Если длина < 6 символов — ошибка +4. Иначе: `bcrypt.hash(password, 10)` → запись в `password_hash` + +Поле `password_hash` скрыто в интерфейсе (`isVisible: false`). + +### Особенности AdminJS + +- Загрузка занимает ~6 секунд при старте сервера (ESM import + инициализация Sequelize) +- В первые секунды после рестарта PM2 `/admin` может возвращать 502 +- Sequelize использует отдельное соединение, независимое от пула `lib/db.js` + +--- + +## 6. База данных + +**СУБД:** MariaDB 10.11 +**База:** `wa_tools` +**Пользователь:** `wa_tools` +**Кодировка:** utf8mb4 + +### Таблица `users` + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | INT AUTO_INCREMENT PK | Идентификатор | +| `email` | VARCHAR(255) UNIQUE NOT NULL | Email (хранится в lowercase) | +| `password_hash` | VARCHAR(255) NOT NULL | bcrypt-хеш пароля | +| `display_name` | VARCHAR(100) | Отображаемое имя | +| `role` | ENUM('user', 'admin') | Роль, по умолчанию 'user' | +| `is_blocked` | BOOLEAN | Заблокирован ли аккаунт | +| `created_at` | DATETIME | Дата регистрации | +| `last_login` | DATETIME | Дата последнего входа | + +### Таблица `settings` + +Ключ-значение для настроек сайта. Отдаётся через `/api/settings` на лендинг. + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | INT AUTO_INCREMENT PK | Идентификатор | +| `setting_key` | VARCHAR(100) UNIQUE NOT NULL | Ключ настройки | +| `setting_value` | TEXT | Значение | +| `description` | VARCHAR(255) | Описание (для AdminJS) | +| `updated_at` | DATETIME | Дата последнего изменения | + +### Таблица `categories` + +Категории инструментов для лендинга и дашборда. + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | INT AUTO_INCREMENT PK | Идентификатор | +| `slug` | VARCHAR(50) UNIQUE NOT NULL | URL-идентификатор (images, code, web, utils) | +| `title` | VARCHAR(100) NOT NULL | Название | +| `description` | VARCHAR(255) | Краткое описание | +| `icon_svg` | TEXT | SVG-путь иконки | +| `color` | VARCHAR(20) | Цвет акцента (по умолчанию '#0054e6') | +| `sort_order` | INT | Порядок сортировки | + +### Таблица `tools` + +Список инструментов платформы. + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | INT AUTO_INCREMENT PK | Идентификатор | +| `slug` | VARCHAR(50) UNIQUE NOT NULL | URL-идентификатор | +| `title` | VARCHAR(100) NOT NULL | Название | +| `description` | VARCHAR(255) | Описание | +| `path` | VARCHAR(100) NOT NULL | URL-путь (например `/compress`) | +| `icon_svg` | TEXT | SVG-путь иконки | +| `category_id` | INT FK → categories.id | Категория | +| `sort_order` | INT | Порядок сортировки | +| `is_enabled` | BOOLEAN | Отображается ли инструмент | + +### Таблица `content_blocks` + +Редактируемый контент для лендинга. + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | INT AUTO_INCREMENT PK | Идентификатор | +| `block_key` | VARCHAR(100) UNIQUE NOT NULL | Ключ блока | +| `title` | VARCHAR(255) | Заголовок блока | +| `body` | TEXT | Содержимое (HTML, richtext в AdminJS) | +| `section` | VARCHAR(50) | Секция: `advantages`, `dashboard` | +| `sort_order` | INT | Порядок | +| `is_visible` | BOOLEAN | Отображается ли блок | +| `updated_at` | DATETIME | Дата последнего изменения | + +### Соединение с БД + +`lib/db.js` использует `mysql2/promise` с connection pool (лимит 5 соединений). Предоставляет 4 хелпера: `findUserByEmail`, `findUserById`, `createUser`, `updateLastLogin`, и экспортирует `pool` для прямых запросов из роутов. + +--- + +## 7. API Reference + +### Публичные API (без авторизации) + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/api/settings` | Все настройки как `{key: value}` | +| GET | `/api/tools` | Категории с вложенными инструментами | +| GET | `/api/content/advantages` | Блоки секции advantages | +| GET | `/api/content/dashboard` | Блоки секции dashboard | + +### Auth API + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/auth/login` | Страница логина | +| GET | `/auth/register` | Страница регистрации | +| POST | `/auth/login` | Вход: `{email, password}` → `{ok, redirect}` | +| POST | `/auth/register` | Регистрация: `{email, password, password2, name}` → `{ok, redirect}` | +| GET | `/auth/logout` | Выход, разрушение сессии | +| GET | `/auth/me` | Данные текущего пользователя → `{id, email, name, role}` | + +### Tool APIs (защищены) + +#### Изображения + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/compress` | Страница инструмента | +| POST | `/compress` | `multipart: images[], format, quality, resize` → `{downloadUrl, stats}` | +| GET | `/download/:filename` | Скачивание ZIP | +| GET | `/placeholder/:size/:bg/:fg` | Генерация placeholder (публично) | +| GET | `/placeholder/:size/:bg/:fg/:text` | С текстом (публично) | +| POST | `/api/svg-optimize` | `{svg}` → `{svg, saved}` | +| POST | `/api/svg-ai` | `{keyword, style, size, color}` → `{svg, keyword, available}` | + +#### Видео + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/video` | Страница | +| POST | `/video/upload` | `multipart: video` → `{jobId, info, originalName, size}` | +| POST | `/video/convert` | `{jobId, mode, format, quality, startTime, endTime}` → `{status}` | +| GET | `/video/progress/:jobId` | → `{status, progress, downloadUrl?, outputSize?, savings?, error?}` | +| GET | `/video/download/:filename` | Скачивание результата | + +#### PDF + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/pdf` | Страница | +| POST | `/pdf/upload` | `multipart: files[]` → `{files: [{id, name, size, pages}]}` | +| GET | `/pdf/preview/:fileId[/:page]` | PNG превью страницы | +| GET | `/pdf/thumbnails/:fileId` | `{pages, thumbnails: [...urls]}` | +| GET | `/pdf/info/:fileId` | `{pages, title, author, creator, size}` | +| POST | `/pdf/merge` | `{fileIds: [...]}` → `{downloadUrl, size}` | +| POST | `/pdf/split` | `{fileId, ranges: "1-3,5"}` → `{downloadUrl, size, pages}` | +| POST | `/pdf/rotate` | `{fileId, pages: "all"|"1,3", angle: 90}` → `{downloadUrl, size}` | +| POST | `/pdf/delete` | `{fileId, pages: "2,4"}` → `{downloadUrl, size, pages}` | +| POST | `/pdf/reorder` | `{fileId, order: [3,1,2]}` → `{downloadUrl, size}` | +| POST | `/pdf/watermark` | `{fileId, text, fontSize, opacity, color}` → `{downloadUrl, size}` | +| POST | `/pdf/pagenumbers` | `{fileId, position, startFrom}` → `{downloadUrl, size}` | +| POST | `/pdf/compress` | `{fileId, quality: "screen"|"ebook"|"printer"}` → `{downloadUrl, size, savings}` | +| POST | `/pdf/protect` | `{fileId, password}` → `{downloadUrl, size}` | +| POST | `/pdf/extract-text` | `{fileId}` → `{text, pages, metadata}` | +| POST | `/pdf/toImages` | `{fileId, format: "png"|"jpg", dpi: 150}` → `{downloadUrl, size}` | +| POST | `/pdf/fromImages` | `multipart: images[]` → `{downloadUrl, size, pages}` | +| GET | `/pdf/download/:filename` | Скачивание результата | + +#### Веб-инструменты + +| Метод | Путь | Описание | +|-------|------|----------| +| GET | `/parse?url=` | Полный разбор статьи | +| GET | `/metadata?url=` | Только метаданные | +| GET | `/text?url=` | Только текст | +| GET | `/preview?url=` | Карточка предпросмотра | +| POST | `/api/proxy` | HTTP-прокси запрос | +| GET | `/api/history` | История HTTP-клиента | +| POST | `/api/history/add` | Добавить в историю | +| DELETE | `/api/history` | Очистить историю | +| POST | `/api/redirect-analyze` | Анализ цепочки редиректов | + +### System API + +| Метод | Путь | Auth | Описание | +|-------|------|------|----------| +| GET | `/health` | нет | `{status, uptime, memory, node, pid}` | +| GET | `/status` | нет | Страница статуса | +| GET | `/status/api` | нет | `{overall, uptime, services, timestamp}` | + +--- + +## 8. Фронтенд архитектура + +### shared.css + +Единый файл стилей для всех страниц. Структура: + +**CSS-переменные** (`:root` и `.dark`): +- `--bg`, `--bg-grad1/2` — фон страницы с радиальными градиентами +- `--surface-800/700/600` — уровни поверхностей (фон карточек, бордеры) +- `--text-primary/secondary/muted` — уровни текста +- `--accent`, `--accent-dim`, `--accent-bright`, `--accent-bg` — акцентный синий (#0054e6) + +**Компоненты:** +- `.sidebar` — фиксированная боковая панель 56px (на мобильных: нижняя панель 52px) +- `.sidebar-link` — кнопка инструмента 36x36px с активным состоянием и индикатором +- `.sidebar-avatar` — аватар пользователя (первая буква имени) +- `.theme-toggle` — переключатель темы +- `.top-header` — верхняя шапка для публичных страниц +- `.tool-container` — контейнер содержимого с padding +- `.wa-input`, `.wa-btn`, `.wa-card` — базовые элементы форм +- `.wa-modal-overlay`, `.wa-modal` — модальное окно с анимацией + +### shared.js + +Загружается на всех страницах с sidebar. Содержит: + +**`WA_CATEGORIES`** — массив категорий: `id`, `title`, `description`, `icon` (SVG-path), `tools` (массив slug-ов). + +**`WA_TOOLS`** — массив всех инструментов: `id`, `path`, `title`, `category`, `icon`, `adminOnly`. Определяет полный список сайдбара. Добавить новый инструмент = добавить объект сюда. + +**`initSidebar(activeId)`** — вставляет `<nav class="sidebar">` в `document.body.prepend()`. Автоматически определяет активный инструмент по `window.WA_TOOL_ID` (устанавливается в каждом HTML) или по URL. Группирует ссылки по категориям с разделителями. + +**`_loadUser()`** — вызывает `/auth/me`, заполняет аватар инициалами имени. Для admin-пользователей делает видимыми элементы `.sidebar-admin-link` (ссылку «Админ»). + +**`_showLogoutModal(userName)`** — модальное окно подтверждения выхода. + +**`initTheme()`** — читает `localStorage.theme`, устанавливает класс `.dark` на `<html>`. Переключение через клик на `#themeToggle`. Вызывается немедленно (до DOMContentLoaded) для предотвращения мигания. + +**Tailwind config** — тёмный режим через `class`, кастомные цвета `surface.*`, `accent.*`, шрифты Manrope и JetBrains Mono. + +### Типы страниц + +| Тип | Примеры | Layout | +|-----|---------|--------| +| Tool pages | `/compress`, `/pdf`, `/video` | Sidebar слева, `<div class="main-content">` | +| Public pages | `/`, `/auth/login`, `/status` | `top-header` сверху, без sidebar | +| Full-screen | `/editor`, `/svgeditor` | Без sidebar и top-header, 100vw/100vh | + +### Страница инструмента (шаблон) + +```html +<script>window.WA_TOOL_ID = 'compress';</script> <!-- устанавливает активный элемент sidebar --> +<script src="/shared.js?v=3"></script> +<link href="/shared.css" rel="stylesheet"> + +<body> + <!-- sidebar вставляется shared.js автоматически --> + <div class="main-content"> + <div class="tool-container"> + <!-- содержимое инструмента --> + </div> + </div> +</body> +``` + +### Лендинг (`/`) + +`public/landing.html` — публичная страница, загружает данные динамически: +- `/api/settings` — настройки (заголовок, описание сайта) +- `/api/tools` — список инструментов для секции категорий +- `/api/content/advantages` — блоки преимуществ + +Структура: hero с CTA → секция категорий → секция преимуществ → footer. + +### Дашборд (`/dashboard`) + +`public/dashboard.html` — главная страница для авторизованных пользователей. Загружает `/auth/me` для персонализации приветствия, `/api/tools` для отображения категорий с иконками. + +### Яндекс Метрика + +Счётчик `108185982` подключён на всех страницах инструментов (в конце `<body>`). + +--- + +## 9. Безопасность + +### HTTP-заголовки (helmet) + +```js +helmet({ + contentSecurityPolicy: false, // отключено — Tailwind CDN требует inline-скриптов + crossOriginEmbedderPolicy: false, +}) +``` + +Остальные политики helmet включены: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy. + +### Защита от SSRF (`lib/ssrf.js`) + +Применяется в HTTP-клиенте, парсере и redirect-анализаторе. Два уровня проверки: + +1. **Синтаксическая** — только `http:` и `https:`, блокировка `localhost`, `*.local`, `*.internal` +2. **Диапазоны IP** (regexp): + - `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` — приватные RFC 1918 + - `127.0.0.0/8` — loopback + - `169.254.0.0/16` — link-local + - `::1`, `fc*/fd*`, `fe80*` — IPv6 приватные +3. **DNS-резолвинг** — hostname разрешается через `dns.resolve4`, все IP проверяются на попадание в приватные диапазоны + +### Загрузка файлов + +- MIME-тип валидируется через whitelist (multer `fileFilter`) +- Проверяется также расширение файла (regexp) как запасной вариант +- Максимальный размер задаётся через переменные окружения +- Временные файлы удаляются немедленно после обработки + +### Path traversal + +Имена файлов при скачивании обрабатываются через `path.basename(req.params.filename)` — попытки выйти из директории (`../`) отсекаются. + +### Пароли + +- bcrypt с `saltRounds: 10` +- Минимальная длина: 6 символов +- Оригинальный пароль никогда не логируется + +### Сессия + +- `httpOnly: true` — недоступно JavaScript +- `secure: true` в production — только по HTTPS +- `sameSite: 'lax'` — защита от CSRF +- Session regeneration при логине — защита от session fixation attack +- Logout: `req.session.destroy()` + +### Санитизация ввода + +- `display_name` — HTML-теги удаляются regexp'ом, длина обрезается до 100 +- Email — нормализуется в lowercase, проверяется regexp + +### AdminJS + +Двойная проверка: наличие сессии + role === 'admin'. Статические ресурсы AdminJS (`.js`, `.css`, `.woff` и т.д.) пропускаются без auth-проверки по расширению. + +--- + +## 10. Деплой и инфраструктура + +### PM2 + +Конфигурация в `ecosystem.config.js`: + +```js +{ + name: 'images', + script: 'server.js', + cwd: '/mnt/webdata/www/images.wadevelop.ru', + instances: 1, + exec_mode: 'fork', + max_memory_restart: '256M', + restart_delay: 3000, + max_restarts: 10, + min_uptime: 5000, +} +``` + +Логи: +- stdout: `~/.pm2/logs/images-out.log` +- stderr: `~/.pm2/logs/images-error.log` +- Формат времени: `YYYY-MM-DD HH:mm:ss` + +Команды управления: +```bash +pm2 start ecosystem.config.js # первый запуск +pm2 restart images # перезапуск +pm2 reload images # graceful reload (0-downtime) +pm2 logs images # просмотр логов +pm2 monit # мониторинг в реальном времени +``` + +### Nginx + +**wadevelop.ru** — основной сайт: +```nginx +server { + server_name wadevelop.ru www.wadevelop.ru; + listen 443 ssl; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 200M; # для загрузки видео + } +} +# HTTP → HTTPS редирект управляется certbot +``` + +**images.wadevelop.ru** — 301 редирект на wadevelop.ru (историческое доменное имя). + +Логи nginx: +- `/var/log/nginx/wadevelop.access.log` +- `/var/log/nginx/wadevelop.error.log` + +### SSL + +Let's Encrypt через certbot. Сертификат: +- `/etc/letsencrypt/live/wadevelop.ru/fullchain.pem` +- `/etc/letsencrypt/live/wadevelop.ru/privkey.pem` + +Автообновление через certbot systemd timer. + +### Переменные окружения (`.env`) + +| Переменная | Описание | По умолчанию | +|-----------|----------|--------------| +| `PORT` | Порт Express | 3000 | +| `NODE_ENV` | Окружение (`production`/`development`) | — | +| `SESSION_SECRET` | Секрет сессий (обязательно сменить) | 'change-me-in-env' | +| `DB_HOST` | Хост MariaDB | localhost | +| `DB_USER` | Пользователь БД | wa_tools | +| `DB_PASSWORD` | Пароль БД | — | +| `DB_NAME` | Имя базы данных | wa_tools | +| `COMPRESS_QUALITY` | Качество сжатия изображений по умолчанию | 60 | +| `MAX_FILE_SIZE_MB` | Максимальный размер файла для конвертера изображений (MB) | 20 | +| `MAX_FILES` | Максимальное количество файлов за раз | 50 | +| `RATE_LIMIT_WINDOW_MS` | Окно rate limit (мс) | 60000 | +| `RATE_LIMIT_MAX` | Максимум запросов за окно (compress) | 30 | +| `PROXY_RATE_LIMIT_MAX` | Максимум запросов за окно (http proxy) | 60 | +| `LOG_LEVEL` | Уровень логирования (`error`/`warn`/`info`/`debug`) | info | + +--- + +## 11. Мониторинг + +### Страница статуса `/status` + +Публичная страница, доступная без авторизации. API: `GET /status/api`. + +Проверяемые сервисы: +- **API** — всегда `operational` (если сервер отвечает) +- **Database** — `pool.execute('SELECT 1')`, `operational` или `outage` +- **Image Processing** — всегда `operational` +- **Video Processing** — всегда `operational` +- **Authentication** — всегда `operational` + +Ответ: +```json +{ + "overall": "operational", + "uptime": 86400, + "services": [ + {"name": "API", "status": "operational"}, + {"name": "Database", "status": "operational"} + ], + "timestamp": "2026-03-22T10:00:00.000Z" +} +``` + +### Health check `/health` + +```json +{ + "status": "ok", + "uptime": 86400, + "memory": { + "rss": "124MB", + "heap": "67/128MB" + }, + "node": "v20.18.0", + "pid": 12345 +} +``` + +Uptime считается от момента запуска процесса Node.js (не PM2). + +### Логирование + +**stdout/stderr** (`lib/logger.js`): +- Формат: `[ISO timestamp] [LEVEL] message {meta JSON}` +- Уровни: `error`, `warn`, `info`, `debug` +- Уровень контролируется через `LOG_LEVEL` в `.env` +- PM2 перенаправляет в файлы логов + +**compress.log** — отдельный файл для операций сжатия изображений и парсинга: +- Формат: `[ISO timestamp] [RU] 1.2.3.4 compress: file.jpg 800x600 format=webp q=80 (234KB)` +- Включает страну из GeoIP, IP, имя файла, параметры, размер + +--- + +## 12. Известные ограничения + +### FFmpeg: нет libx264 + +На сервере (Raspberry Pi 5, Debian Bookworm) FFmpeg скомпилирован без `libx264` (требует отдельной лицензии). Видео конвертируется через `mpeg4` software encoder (`-c:v mpeg4 -q:v 5`). Это означает: +- Выходной mp4 технически корректен, но использует MPEG-4 Part 2 вместо H.264 +- Совместимость чуть ниже, чем у H.264 +- WebM-конвертация реализована как fallback в AVI-контейнере (`-f avi`), а не настоящий VP8/VP9 + +### AdminJS: медленная загрузка + +ESM-импорт AdminJS при старте занимает ~6 секунд. В течение этого времени `/admin` возвращает 404 (маршрут ещё не зарегистрирован). После рестарта PM2 нужно подождать перед открытием панели. + +### In-memory сессии + +Сессии хранятся в памяти Node.js процесса. При `pm2 restart` все пользователи разлогиниваются. Аналогично теряются: история HTTP-клиента, задачи видеоконвертера (`jobs Map`), загруженные PDF-файлы (`pdfFiles Map`). + +### Память (swap) + +На сервере swap загружен на ~100%. llama.cpp и open-webui занимают ~1.5 GB RAM. При высокой нагрузке на видеоконвертер (FFmpeg) возможна конкуренция за память. PM2 перезапустит процесс при превышении 256 MB RSS. + +### CSP отключён + +`contentSecurityPolicy: false` в helmet. Причина: Tailwind CSS подключается через CDN и требует inline-скриптов. Это снижает защиту от XSS. Решение — собрать Tailwind локально и убрать зависимость от CDN. + +### Производительность конвертации + +Sharp работает нативно и быстро. FFmpeg и Ghostscript — CPU-интенсивные операции. На Raspberry Pi 5 конвертация длинного видео может занять несколько минут. + +--- + +## 13. Будущие доработки + +### OAuth авторизация + +Добавить вход через Google и Яндекс. Пакет `openid-client` уже установлен в зависимостях, `passport` тоже присутствует — реализация не завершена. + +### Persistent sessions + +Перенести хранение сессий из памяти в MariaDB (пакет `express-mysql-session` или аналог). Позволит пользователям оставаться залогиненными после перезапуска сервера. + +### Улучшения инструментов + +| Инструмент | Улучшение | +|-----------|-----------| +| Конвертер изображений | Quality slider в UI (сейчас фиксируется в .env) | +| Генератор паролей | Режим passphrase (несколько слов через дефис) | +| HTTP-клиент | Поддержка переменных окружения (как в Postman) | +| Видео конвертер | Установить libx264 и переключить encoder | + +### Usage analytics + +Логирование использования инструментов в отдельную таблицу БД для аналитики популярности. + +### CSP настройка + +Перейти на локальную сборку Tailwind CSS, убрать CDN, включить Content-Security-Policy. + +### Persistent job storage + +Видео задачи (`jobs Map`) и PDF файлы (`pdfFiles Map`) хранить в БД или на диске с восстановлением после рестарта.