wa-dev-tools/DOCS.md

61 KiB
Raw Permalink Blame History

WA Dev Tools — Полная документация проекта

Версия документации: март 2026 Репозиторий: git.wadevelop.ru/treamz/wa-dev-tools Продакшн: https://wadevelop.ru


Содержание

  1. Обзор проекта
  2. Архитектура
  3. Инструменты
  4. Авторизация и роли
  5. AdminJS панель
  6. База данных
  7. API Reference
  8. Фронтенд архитектура
  9. Безопасность
  10. Деплой и инфраструктура
  11. Мониторинг
  12. Известные ограничения
  13. Будущие доработки

1. Обзор проекта

WA Dev Tools — веб-платформа с набором утилит для разработчиков. Доступна по адресу https://wadevelop.ru. Целевая аудитория: веб-разработчики, которым нужны быстрые инструменты для работы с изображениями, видео, PDF, кодом и HTTP-запросами без установки десктопных приложений.

Технологии

Слой Технология
Среда выполнения Node.js 20
Веб-фреймворк Express 5
База данных MariaDB 10.11
Хранение сессий express-mysql-session (MariaDB)
Очередь задач better-sqlite3 (SQLite WAL, /mnt/webdata/storage/jobs.db)
Обработка изображений Sharp 0.34
Обработка видео FFmpeg 5.1.8 (libx264, libvpx, libmp3lame), ffprobe
Обработка PDF pdf-lib, pdf-parse, Ghostscript (системный)
Парсинг HTML @mozilla/readability, jsdom
Авторизация express-session, bcrypt
Панель администратора AdminJS 7 + @adminjs/sequelize
Стили Tailwind CSS 3 (CLI build, vendor/tailwind.min.css)
WebSocket ws (реалтайм прогресс конвертации)
Логирование pino (структурированный JSON)
Процесс-менеджер 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           # Логгер pino: структурированный JSON + logToFile
│   ├── queue.js            # Очередь задач на SQLite (better-sqlite3, WAL mode)
│   ├── session.js          # Persistent sessions (express-mysql-session → MariaDB)
│   ├── storage.js          # Единые пути хранения: /mnt/webdata/storage/{uploads,results}
│   ├── ws.js               # WebSocket сервер (ws) — реалтайм прогресс конвертации
│   └── 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:

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 штук)
  • formatoriginal, webp, jpeg, png, avif, tiff, gif
  • quality — 1100 (по умолчанию из 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:

  • size300x200, 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-преобразованиями:

  • Удаление <?xml>, комментариев, <metadata>, <title>, <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 Статус и прогресс задачи (0100%)
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 /formatterformatter.html

Алгоритм форматирования реализован в formatter.html через regexp:

  • Строки и комментарии сохраняются в массив (не затрагиваются)
  • Расставляются переносы строк вокруг { и }
  • Применяется отступ 2 пробела с счётчиком вложенности

Нет зависимости от сторонних библиотек (prettier, babel и т.д.).

Фронтенд: public/formatter.html — две textarea (input/output), переключатель CSS/JS, кнопки копировать/очистить, счётчик символов.


HTML Sanitizer (/sanitizer)

Что делает: очистка HTML от потенциально опасных тегов и атрибутов. Работает полностью на фронтенде через браузерный DOM.

Бэкенд: только GET /sanitizersanitizer.html

Фронтенд: public/sanitizer.html


Format Converter (/converter)

Что делает: конвертация данных между форматами (JSON, YAML, CSV и т.д.). Работает полностью на фронтенде.

Бэкенд: только GET /converterconverter.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:

{
  "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:

{
  "url": "https://example.com/api",
  "method": "POST",
  "headers": {"Authorization": "Bearer ..."},
  "body": "{\"key\": \"value\"}",
  "timeout": 30000
}

Ответ /api/proxy:

{
  "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) Анализ цепочки редиректов

Параметры:

{
  "url": "https://example.com",
  "userAgent": "desktop|mobile|googlebot",
  "method": "GET|HEAD"
}

Ответ:

{
  "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 /passwordpassword.html

Фронтенд: public/password.html — слайдер длины, чекбоксы наборов символов, индикатор сложности, кнопка копирования.


Markdown Viewer (/md)

Что делает: рендеринг Markdown в HTML с предпросмотром. Работает полностью на фронтенде.

Бэкенд: только GET /mdmd.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

Структура сессии

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/

Конфигурация сессии

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

Rate limit работает per-user: keyGenerator использует req.session.user.id для авторизованных пользователей, req.ip для анонимных. Это предотвращает ситуацию, когда один бот исчерпывает лимит для всех.

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"
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"
POST /pdf/protect {fileId, password}{downloadUrl, size}
POST /pdf/extract-text {fileId}{text, pages, metadata}
POST /pdf/toImages `{fileId, format: "png"
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

Страница инструмента (шаблон)

<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)

helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", "https://mc.yandex.ru"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https://mc.yandex.ru"],
      connectSrc: ["'self'", "wss:", "ws:"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
      baseUri: ["'self'"],
      formAction: ["'self'"],
    },
  },
  crossOriginEmbedderPolicy: false,
})

CSP включён. unsafe-inline необходим для inline <script> и <style> блоков UI. Остальные политики 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 символов
  • Оригинальный пароль никогда не логируется

Сессия

  • Хранение: MariaDB через express-mysql-session (таблица sessions)
  • Сессии переживают рестарт PM2 — пользователи остаются залогиненными
  • httpOnly: true — недоступно JavaScript
  • secure: true в production — только по HTTPS
  • sameSite: 'lax' — защита от CSRF
  • Session regeneration при логине — защита от session fixation attack
  • Logout: req.session.destroy()
  • Автоочистка: checkExpirationInterval: 900000 (15 мин), expiration: 86400000 (24ч)

Санитизация ввода

  • display_name — HTML-теги удаляются regexp'ом, длина обрезается до 100
  • Email — нормализуется в lowercase, проверяется regexp

AdminJS

Двойная проверка: наличие сессии + role === 'admin'. Статические ресурсы AdminJS (.js, .css, .woff и т.д.) пропускаются без auth-проверки по расширению.


10. Деплой и инфраструктура

PM2

Конфигурация в ecosystem.config.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

Команды управления:

pm2 start ecosystem.config.js   # первый запуск
pm2 restart images               # перезапуск
pm2 reload images                # graceful reload (0-downtime)
pm2 logs images                  # просмотр логов
pm2 monit                        # мониторинг в реальном времени

Nginx

wadevelop.ru — основной сайт:

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 (если сервер отвечает)
  • Databasepool.execute('SELECT 1'), operational или outage
  • Image Processing — всегда operational
  • Video Processing — всегда operational
  • Authentication — всегда operational

Ответ:

{
  "overall": "operational",
  "uptime": 86400,
  "services": [
    {"name": "API", "status": "operational"},
    {"name": "Database", "status": "operational"}
  ],
  "timestamp": "2026-03-22T10:00:00.000Z"
}

Health check /health

{
  "status": "ok",
  "uptime": 86400,
  "memory": {
    "rss": "302MB",
    "heap": "86/91MB",
    "system": "37%"
  },
  "cpu": {
    "load1m": "0.00",
    "load5m": "0.00",
    "load15m": "0.05",
    "cores": 4
  },
  "queue": {
    "stats": { "done": 5, "processing": 1 },
    "active": 1,
    "pending": 0
  },
  "ws": {
    "connections": 2
  },
  "node": "v20.19.5",
  "pid": 12345
}

Расширенный health: системная память, CPU load, глубина очереди задач, активные WebSocket подключения.

Логирование

pino (lib/logger.js):

  • Формат: структурированный JSON ({"level":30,"time":...,"msg":"..."})
  • Уровни: error, warn, info, debug (контролируется через LOG_LEVEL)
  • API совместим: log.info(msg, meta), log.error(msg, meta), log.logToFile(text)
  • PM2 перенаправляет stdout/stderr в файлы логов

logToFile — append-запись в файл для операций сжатия/парсинга:

  • Формат: [ISO timestamp] [RU] 1.2.3.4 compress: file.jpg 800x600 format=webp q=80 (234KB)
  • Включает страну из GeoIP, IP, имя файла, параметры, размер

12. Известные ограничения

AdminJS: медленная загрузка

ESM-импорт AdminJS при старте занимает ~6 секунд. В течение этого времени /admin возвращает 404. После рестарта PM2 нужно подождать перед открытием панели.

Память (swap)

Swap загружен на ~100% из-за LLM-кластера (llama.cpp + open-webui ~3.8 GB). PM2 перезапустит процесс при превышении 256 MB RSS.

PM2 cluster mode

Несовместим с текущей архитектурой AdminJS (ESM dynamic import + in-memory state). Работает только в fork mode (instances: 1).

Производительность конвертации

Sharp — нативный, быстрый. FFmpeg и Ghostscript — CPU-интенсивные. На Raspberry Pi 5 длинное видео может конвертироваться несколько минут.

PDF-файлы

Загруженные PDF хранятся в in-memory Map с таймаутом 30 мин. При рестарте теряются (в отличие от видео-задач, которые уже в SQLite-очереди).


13. Будущие доработки

OAuth авторизация

Добавить вход через Google и Яндекс. Пакет openid-client уже установлен в зависимостях, passport тоже присутствует — реализация не завершена.

Улучшения инструментов

Инструмент Улучшение
Конвертер изображений Quality slider в UI (сейчас фиксируется в .env)
Генератор паролей Режим passphrase (несколько слов через дефис)
HTTP-клиент Поддержка переменных окружения (как в Postman)

Usage analytics

Логирование использования инструментов в отдельную таблицу БД для аналитики популярности.

PDF-файлы в очередь

Перенести pdfFiles Map в SQLite-очередь (аналогично видео-задачам) для персистентности.

Монетизация

Тарифы, API-ключи, billing usage, multi-tenant лимиты.