653 lines
30 KiB
Markdown
653 lines
30 KiB
Markdown
# WA Dev Tools
|
||
|
||
Веб-платформа с набором инструментов для разработчиков. Включает 15 утилит для работы с изображениями, видео, PDF, кодом и веб-ресурсами — всё в одном защищённом интерфейсе с авторизацией.
|
||
|
||
**Продакшн:** [wadevelop.ru](https://wadevelop.ru) / [images.wadevelop.ru](https://images.wadevelop.ru)
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Быстрый старт](#быстрый-старт)
|
||
2. [Архитектура](#архитектура)
|
||
3. [Инструменты](#инструменты)
|
||
4. [Авторизация](#авторизация)
|
||
5. [Админ-панель](#админ-панель)
|
||
6. [API Reference](#api-reference)
|
||
7. [Безопасность](#безопасность)
|
||
8. [Конфигурация](#конфигурация)
|
||
9. [Деплой](#деплой)
|
||
10. [Мониторинг](#мониторинг)
|
||
11. [Технологии](#технологии)
|
||
12. [База данных](#база-данных)
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
### Требования
|
||
|
||
- Node.js 18+
|
||
- MariaDB 10.6+
|
||
- FFmpeg (для видеоконвертера)
|
||
- Ghostscript (для сжатия и конвертации PDF)
|
||
|
||
### Установка
|
||
|
||
```bash
|
||
# Клонировать репозиторий
|
||
git clone <repo-url>
|
||
cd wa-dev-tools
|
||
|
||
# Установить зависимости
|
||
npm install
|
||
|
||
# Создать файл конфигурации
|
||
cp .env.example .env
|
||
# Заполнить переменные (см. раздел Конфигурация)
|
||
|
||
# Создать базу данных
|
||
mysql -u root -p < schema.sql
|
||
|
||
# Запустить в режиме разработки
|
||
node server.js
|
||
|
||
# Или через PM2 в продакшне
|
||
pm2 start ecosystem.config.js
|
||
```
|
||
|
||
Сервер стартует на `http://localhost:3000` (порт задаётся через `PORT` в `.env`).
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
Приложение построено на **Express 5** с модульной маршрутизацией. Каждый инструмент изолирован в отдельном файле роутера. Общая библиотека (`lib/`) предоставляет auth middleware, подключение к БД, логгер и SSRF-защиту.
|
||
|
||
```
|
||
wa-dev-tools/
|
||
├── server.js # Точка входа: Express, сессии, middleware, AdminJS
|
||
├── ecosystem.config.js # PM2-конфигурация
|
||
├── lib/
|
||
│ ├── auth.js # Middleware авторизации (список публичных путей)
|
||
│ ├── admin.js # AdminJS: Sequelize-модели, маунт /admin
|
||
│ ├── db.js # Пул соединений mysql2, CRUD-функции для users
|
||
│ ├── logger.js # Структурированный логгер (stdout + файл)
|
||
│ └── ssrf.js # Валидация URL, блокировка приватных IP и диапазонов
|
||
├── routes/
|
||
│ ├── api.js # Публичный REST API (settings, tools, content)
|
||
│ ├── auth.js # Регистрация, вход, выход, /auth/me
|
||
│ ├── compress.js # Сжатие изображений (Sharp + Archiver)
|
||
│ ├── placeholder.js # Генератор placeholder-изображений
|
||
│ ├── svgeditor.js # SVG-оптимизация и AI-генерация иконок
|
||
│ ├── video.js # Конвертер видео (FFmpeg, асинхронные задачи)
|
||
│ ├── pdf.js # PDF-инструменты (pdf-lib, Ghostscript)
|
||
│ ├── parser.js # Парсер статей (Readability, JSDOM, кеш)
|
||
│ ├── httpclient.js # HTTP-клиент (прокси-запросы, история сессии)
|
||
│ ├── redirects.js # Анализатор цепочек редиректов
|
||
│ ├── pages.js # HTML-страницы инструментов (formatter, md и др.)
|
||
│ ├── status.js # Страница статуса сервисов
|
||
│ └── logs.js # Просмотр логов (защищённый)
|
||
└── public/
|
||
├── shared.js # Сайдбар, темы, список инструментов (клиент)
|
||
├── shared.css # Базовые стили
|
||
├── landing.html # Публичная лендинг-страница
|
||
├── login.html # Страница входа
|
||
├── register.html # Страница регистрации
|
||
└── *.html # Страницы инструментов
|
||
```
|
||
|
||
### Порядок middleware в server.js
|
||
|
||
```
|
||
helmet (security headers)
|
||
↓
|
||
express-session
|
||
↓
|
||
express.static (public/) — всегда публично
|
||
↓
|
||
/auth/* — публично (вход/регистрация)
|
||
↓
|
||
/api/settings, /api/tools, /api/content/* — публично (лендинг)
|
||
↓
|
||
/status — публично
|
||
↓
|
||
authMiddleware — всё ниже требует сессии
|
||
↓
|
||
/api/*, /compress/*, /video/*, /pdf/*, /parser, ...
|
||
```
|
||
|
||
---
|
||
|
||
## Инструменты
|
||
|
||
### Изображения
|
||
|
||
| Инструмент | Путь | Описание |
|
||
|---|---|---|
|
||
| Конвертер изображений | `/compress` | Пакетное сжатие и конвертация до 50 файлов. Форматы: JPEG, PNG, WebP, AVIF, TIFF, GIF. Настройка качества и максимальной стороны. Результат — ZIP-архив. |
|
||
| Placeholder | `/placeholder` | Генератор заглушек для макетов. API-эндпоинт `/placeholder/:size/:bg/:fg/:text` возвращает PNG/JPEG/WebP без авторизации. |
|
||
| SVG-редактор | `/svgeditor` | Оптимизация SVG (удаление комментариев, metadata, пустых групп, обрезка чисел). Генерация простых иконок по ключевому слову (20 встроенных шаблонов). |
|
||
| Фоторедактор | `/editor` | Клиентский редактор изображений. |
|
||
| Видеоконвертер | `/video` | Конвертация, сжатие, извлечение аудио и создание GIF через FFmpeg. Асинхронная обработка с поллингом прогресса. |
|
||
|
||
### Код
|
||
|
||
| Инструмент | Путь | Описание |
|
||
|---|---|---|
|
||
| Code Formatter | `/formatter` | Форматирование кода на стороне клиента. |
|
||
| HTML Sanitizer | `/sanitizer` | Очистка HTML от потенциально опасных тегов и атрибутов. |
|
||
| Конвертер форматов | `/converter` | Конвертация между форматами данных (JSON, YAML, TOML и др.). |
|
||
|
||
### Веб
|
||
|
||
| Инструмент | Путь | Описание |
|
||
|---|---|---|
|
||
| Парсер статей | `/parser` | Извлечение текста и метаданных из URL через Mozilla Readability. Поддержка кодировок (iconv-lite), кеш 50 записей / 10 мин, автоматическая смена кодировки. |
|
||
| HTTP-клиент | `/httpclient` | Прокси-запросы к внешним URL: GET/POST/PUT/DELETE, произвольные заголовки, тело запроса, таймаут до 60 сек. История запросов в сессии (до 30 записей). |
|
||
| Redirect-анализатор | `/redirects` | Трассировка цепочки редиректов до 15 шагов. Три User-Agent профиля: desktop, mobile, Googlebot. Детектирует длинные цепочки, смешанный протокол HTTP→HTTPS, временные 302-редиректы. |
|
||
|
||
### Утилиты
|
||
|
||
| Инструмент | Путь | Описание |
|
||
|---|---|---|
|
||
| Генератор паролей | `/password` | Клиентская генерация паролей с настройкой сложности. |
|
||
| Markdown Viewer | `/md` | Рендер Markdown в HTML. |
|
||
| PDF-инструменты | `/pdf` | Merge, Split, Rotate, Delete pages, Reorder, Watermark, Page numbers, Compress (Ghostscript), Protect (пароль), Extract text, PDF→Images, Images→PDF. |
|
||
|
||
---
|
||
|
||
## Авторизация
|
||
|
||
Используется локальная авторизация на основе email + пароль.
|
||
|
||
**Технический стек:** bcrypt (10 rounds), express-session, MariaDB.
|
||
|
||
### Процесс входа
|
||
|
||
1. Пользователь отправляет `POST /auth/login` с `email` и `password`.
|
||
2. Пароль сравнивается с `password_hash` в таблице `users` через `bcrypt.compare`.
|
||
3. При успехе сессия перегенерируется (`req.session.regenerate`) для защиты от session fixation.
|
||
4. В сессии сохраняется объект `{ id, email, name, role }`.
|
||
|
||
### Процесс регистрации
|
||
|
||
```
|
||
POST /auth/register
|
||
Body: { email, password, password2, name }
|
||
```
|
||
|
||
Валидация: формат email, минимум 6 символов пароля, совпадение паролей, уникальность email. Имя пользователя очищается от HTML-тегов.
|
||
|
||
### Сессия
|
||
|
||
```javascript
|
||
// Конфигурация сессии (server.js)
|
||
{
|
||
secret: SESSION_SECRET,
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
cookie: {
|
||
maxAge: 86400000, // 24 часа
|
||
httpOnly: true,
|
||
secure: true, // только в production
|
||
sameSite: 'lax',
|
||
}
|
||
}
|
||
```
|
||
|
||
### Роли
|
||
|
||
| Роль | Доступ |
|
||
|---|---|
|
||
| `user` | Все инструменты платформы |
|
||
| `admin` | Все инструменты + ссылка на `/admin` в сайдбаре + доступ к AdminJS |
|
||
|
||
Заблокированные аккаунты (`is_blocked = true`) получают `403` при попытке входа.
|
||
|
||
### Публичные пути (без авторизации)
|
||
|
||
- `/` — лендинг
|
||
- `/auth/*` — вход, регистрация, выход
|
||
- `/status` — страница статуса
|
||
- `/placeholder`, `/placeholder-img/*` — API заглушек
|
||
- `/api/settings`, `/api/tools`, `/api/content/advantages`, `/api/content/dashboard` — данные для лендинга
|
||
- `/health` — health check
|
||
|
||
---
|
||
|
||
## Админ-панель
|
||
|
||
Доступна по адресу `/admin`. Требует роль `admin` в основной сессии сайта — отдельная авторизация AdminJS не используется.
|
||
|
||
Построена на **AdminJS 7** + **@adminjs/sequelize**. Модели подключены через Sequelize (параллельно с основным пулом mysql2).
|
||
|
||
### Управляемые сущности
|
||
|
||
| Раздел | Модель | Таблица | Что можно делать |
|
||
|---|---|---|---|
|
||
| Пользователи | `User` | `users` | Просмотр, создание, редактирование, блокировка, смена роли и пароля |
|
||
| Контент | `Category` | `categories` | CRUD категорий инструментов |
|
||
| Контент | `Tool` | `tools` | CRUD инструментов: включение/отключение, порядок сортировки, иконки |
|
||
| Контент | `ContentBlock` | `content_blocks` | Редактирование текстовых блоков лендинга (richtext) |
|
||
| Настройки | `Setting` | `settings` | Редактирование значений настроек приложения |
|
||
|
||
Смена пароля через AdminJS: пароль хешируется bcrypt'ом в before-хуке действия `edit` / `new`. Поле `password_hash` скрыто из интерфейса.
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### Публичные эндпоинты
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `GET` | `/api/settings` | Все настройки как объект `{ key: value }` |
|
||
| `GET` | `/api/tools` | Категории с вложенными активными инструментами |
|
||
| `GET` | `/api/content/advantages` | Блоки контента секции `advantages` |
|
||
| `GET` | `/api/content/dashboard` | Блоки контента секции `dashboard` |
|
||
| `GET` | `/placeholder/:size/:bg/:fg` | Изображение-заглушка |
|
||
| `GET` | `/placeholder/:size/:bg/:fg/:text` | Изображение-заглушка с текстом |
|
||
| `GET` | `/status/api` | Статус сервисов |
|
||
| `GET` | `/health` | Uptime, память, версия Node |
|
||
|
||
**Пример запроса к placeholder API:**
|
||
```
|
||
GET /placeholder/800x600/0054e6/ffffff/Hello
|
||
```
|
||
Параметры: `size` = `WxH`, `bg` и `fg` = hex-цвет без `#`, `text` = текст на изображении. Формат: `?format=png|jpg|webp`.
|
||
|
||
### Защищённые эндпоинты (требуют сессию)
|
||
|
||
**Авторизация:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/auth/login` | Вход. Body: `{ email, password }` |
|
||
| `POST` | `/auth/register` | Регистрация. Body: `{ email, password, password2, name }` |
|
||
| `GET` | `/auth/logout` | Выход, уничтожение сессии |
|
||
| `GET` | `/auth/me` | Текущий пользователь `{ id, email, name, role }` |
|
||
|
||
**Изображения:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/compress` | Загрузка файлов (`multipart/form-data`, поле `images[]`). Параметры: `format`, `quality`, `resize`. Ответ: `{ downloadUrl, stats[] }` |
|
||
| `GET` | `/download/:filename` | Скачать ZIP с результатом |
|
||
|
||
**Видео:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/video/upload` | Загрузка видео (до 200MB). Ответ: `{ jobId, info }` |
|
||
| `POST` | `/video/convert` | Запуск обработки. Body: `{ jobId, mode, format, quality, startTime, endTime }` |
|
||
| `GET` | `/video/progress/:jobId` | Прогресс задачи. Ответ: `{ status, progress, downloadUrl?, savings? }` |
|
||
| `GET` | `/video/download/:filename` | Скачать результат |
|
||
|
||
Режимы конвертации (`mode`): `convert`, `compress`, `audio`, `gif`.
|
||
|
||
**PDF:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/pdf/upload` | Загрузка PDF/изображений (до 50MB, до 10 файлов). Ответ: `{ files: [{ id, name, size, pages }] }` |
|
||
| `GET` | `/pdf/info/:fileId` | Метаданные PDF (страницы, автор, создатель) |
|
||
| `POST` | `/pdf/merge` | Объединение. Body: `{ fileIds: [] }` |
|
||
| `POST` | `/pdf/split` | Разбивка страниц. Body: `{ fileId, ranges: "1-3,5" }` |
|
||
| `POST` | `/pdf/rotate` | Поворот. Body: `{ fileId, pages: "all" или "1,3", angle: 90 }` |
|
||
| `POST` | `/pdf/delete` | Удаление страниц. Body: `{ fileId, pages: "2,4" }` |
|
||
| `POST` | `/pdf/reorder` | Переупорядочивание. Body: `{ fileId, order: [3,1,2] }` |
|
||
| `POST` | `/pdf/watermark` | Водяной знак. Body: `{ fileId, text, fontSize, opacity, color }` |
|
||
| `POST` | `/pdf/pagenumbers` | Нумерация страниц. Body: `{ fileId, position, startFrom }` |
|
||
| `POST` | `/pdf/compress` | Сжатие через Ghostscript. Body: `{ fileId, quality: "screen"|"ebook"|"printer" }` |
|
||
| `POST` | `/pdf/protect` | Защита паролем. Body: `{ fileId, password }` |
|
||
| `POST` | `/pdf/extract-text` | Извлечение текста. Body: `{ fileId }` |
|
||
| `POST` | `/pdf/toImages` | PDF → ZIP с PNG/JPEG. Body: `{ fileId, format, dpi }` |
|
||
| `POST` | `/pdf/fromImages` | Изображения → PDF (`multipart/form-data`, поле `images[]`) |
|
||
| `GET` | `/pdf/download/:filename` | Скачать результат |
|
||
|
||
**Парсер статей:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `GET` | `/parse?url=` | Полный разбор статьи: заголовок, автор, текст, HTML, изображения, ссылки |
|
||
| `GET` | `/metadata?url=` | Только метаданные (OG, description, дата) |
|
||
| `GET` | `/text?url=` | Только текст (`text/plain`) |
|
||
| `GET` | `/preview?url=` | Карточка превью: заголовок, описание, изображение |
|
||
|
||
**HTTP-клиент:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/api/proxy` | Прокси-запрос. Body: `{ url, method, headers, body, timeout }` |
|
||
| `GET` | `/api/history` | История запросов текущей сессии |
|
||
| `POST` | `/api/history/add` | Добавить запись в историю |
|
||
| `DELETE` | `/api/history` | Очистить историю |
|
||
|
||
**Redirect-анализатор:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/api/redirect-analyze` | Трассировка. Body: `{ url, userAgent: "desktop"|"mobile"|"googlebot", method }` |
|
||
|
||
**SVG-редактор:**
|
||
|
||
| Метод | Путь | Описание |
|
||
|---|---|---|
|
||
| `POST` | `/api/svg-optimize` | Оптимизация SVG. Body: `{ svg: "..." }`. Ответ: `{ svg, saved: % }` |
|
||
| `POST` | `/api/svg-ai` | Генерация иконки. Body: `{ keyword, style: "outline"|"filled"|"duotone", size, color }` |
|
||
|
||
---
|
||
|
||
## Безопасность
|
||
|
||
### HTTP-заголовки
|
||
|
||
Helmet добавляет стандартные security headers. Политика CSP отключена (используется Tailwind CDN с inline-скриптами).
|
||
|
||
### Rate Limiting (express-rate-limit)
|
||
|
||
| Эндпоинт | Лимит |
|
||
|---|---|
|
||
| `/auth/login`, `/auth/register` | 10 запросов / 15 мин |
|
||
| `/compress` | Настраивается через `RATE_LIMIT_MAX` / `RATE_LIMIT_WINDOW_MS` (по умолчанию 30 / 60 сек) |
|
||
| `/parse`, `/metadata`, `/text`, `/preview` | 20 запросов / 60 сек |
|
||
| `/api/proxy` | Настраивается через `PROXY_RATE_LIMIT_MAX` (по умолчанию 60 / 60 сек) |
|
||
| `/api/redirect-analyze` | 20 запросов / 60 сек |
|
||
| `/video/upload` | 10 запросов / 60 сек |
|
||
| `/pdf/*` | 30 запросов / 60 сек |
|
||
|
||
### SSRF-защита
|
||
|
||
Все эндпоинты, выполняющие server-side запросы к внешним URL (парсер, HTTP-клиент, redirect-анализатор), проходят валидацию через `lib/ssrf.js`:
|
||
|
||
- Разрешены только протоколы `http:` и `https:`
|
||
- Блокируются прямые обращения к приватным IP-диапазонам: `10.x`, `172.16-31.x`, `192.168.x`, `127.x`, `169.254.x`, IPv6 loopback и link-local
|
||
- Блокируются hostname'ы: `localhost`, `*.local`, `*.internal`
|
||
- DNS-резолвинг: все A-записи домена проверяются на принадлежность к приватным диапазонам
|
||
|
||
### Прочее
|
||
|
||
- Сессионные cookie: `httpOnly`, `secure` (в production), `sameSite: lax`
|
||
- Session fixation: при входе сессия перегенерируется через `req.session.regenerate`
|
||
- Загружаемые файлы изображений валидируются по MIME-типу и расширению
|
||
- Временные файлы автоматически удаляются: загрузки через 10 мин, результаты через 30 мин
|
||
- Имена файлов транслитерируются и очищаются от спецсимволов перед записью в архив
|
||
|
||
---
|
||
|
||
## Конфигурация
|
||
|
||
Файл `.env` в корне проекта. Пример структуры:
|
||
|
||
```env
|
||
# Сервер
|
||
NODE_ENV=production
|
||
PORT=3000
|
||
|
||
# База данных (MariaDB)
|
||
DB_HOST=localhost
|
||
DB_USER=wa_tools
|
||
DB_PASSWORD=секрет
|
||
DB_NAME=wa_tools
|
||
|
||
# Сессия
|
||
SESSION_SECRET=длинная-случайная-строка
|
||
|
||
# Логирование
|
||
LOG_LEVEL=info # error | warn | info | debug
|
||
|
||
# Загрузка файлов (изображения)
|
||
MAX_FILE_SIZE_MB=20 # максимальный размер одного файла
|
||
MAX_FILES=50 # максимальное количество файлов в одном запросе
|
||
|
||
# Сжатие изображений
|
||
COMPRESS_QUALITY=80 # качество по умолчанию (1-100)
|
||
|
||
# Rate limiting (изображения)
|
||
RATE_LIMIT_WINDOW_MS=60000
|
||
RATE_LIMIT_MAX=30
|
||
|
||
# Rate limiting (HTTP-клиент)
|
||
PROXY_RATE_LIMIT_MAX=60
|
||
```
|
||
|
||
| Переменная | Описание | По умолчанию |
|
||
|---|---|---|
|
||
| `NODE_ENV` | Режим работы. В `production` cookie становятся `secure` | `development` |
|
||
| `PORT` | Порт Express-сервера | `3000` |
|
||
| `DB_HOST` | Хост MariaDB | `localhost` |
|
||
| `DB_USER` | Пользователь БД | `wa_tools` |
|
||
| `DB_PASSWORD` | Пароль БД | — |
|
||
| `DB_NAME` | Имя базы данных | `wa_tools` |
|
||
| `SESSION_SECRET` | Секрет для подписи сессионных cookie | `change-me-in-env` |
|
||
| `LOG_LEVEL` | Уровень логирования | `info` |
|
||
| `MAX_FILE_SIZE_MB` | Лимит размера изображения | `20` |
|
||
| `MAX_FILES` | Лимит файлов в пакетной загрузке | `50` |
|
||
| `COMPRESS_QUALITY` | Качество сжатия по умолчанию | `80` |
|
||
| `RATE_LIMIT_WINDOW_MS` | Окно rate limiting (мс) | `60000` |
|
||
| `RATE_LIMIT_MAX` | Лимит запросов в окне | `30` |
|
||
| `PROXY_RATE_LIMIT_MAX` | Лимит для HTTP-клиента | `60` |
|
||
|
||
> **Важно:** Никогда не коммитьте `.env` в репозиторий. Значение `SESSION_SECRET` должно быть случайной строкой длиной не менее 32 символов.
|
||
|
||
---
|
||
|
||
## Деплой
|
||
|
||
### PM2
|
||
|
||
Конфигурация в `ecosystem.config.js`:
|
||
|
||
```javascript
|
||
{
|
||
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,
|
||
env: { NODE_ENV: 'production' }
|
||
}
|
||
```
|
||
|
||
```bash
|
||
# Запуск
|
||
pm2 start ecosystem.config.js
|
||
|
||
# Перезапуск после обновления
|
||
pm2 restart images
|
||
|
||
# Логи
|
||
pm2 logs images
|
||
```
|
||
|
||
### Graceful shutdown
|
||
|
||
Сервер перехватывает `SIGTERM` и `SIGINT`: закрывает HTTP-сервер, ждёт завершения активных соединений до 10 секунд, затем завершается принудительно.
|
||
|
||
### Nginx
|
||
|
||
Пример конфигурации виртуального хоста:
|
||
|
||
```nginx
|
||
server {
|
||
listen 443 ssl;
|
||
server_name images.wadevelop.ru;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/images.wadevelop.ru/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/images.wadevelop.ru/privkey.pem;
|
||
|
||
client_max_body_size 210M; # чуть больше MAX видео (200MB)
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 120s;
|
||
}
|
||
}
|
||
```
|
||
|
||
В `server.js` установлено `app.set('trust proxy', 1)` — необходимо для корректной работы rate limiting и логирования IP за nginx.
|
||
|
||
### SSL
|
||
|
||
```bash
|
||
certbot --nginx -d images.wadevelop.ru
|
||
```
|
||
|
||
---
|
||
|
||
## Мониторинг
|
||
|
||
### Страница статуса
|
||
|
||
**URL:** `/status` — публичная HTML-страница в стиле status.claude.com.
|
||
|
||
**API:** `GET /status/api` — возвращает статус пяти сервисов:
|
||
|
||
```json
|
||
{
|
||
"overall": "operational",
|
||
"uptime": 86400,
|
||
"services": [
|
||
{ "name": "API", "status": "operational" },
|
||
{ "name": "Database", "status": "operational" },
|
||
{ "name": "Image Processing", "status": "operational" },
|
||
{ "name": "Video Processing", "status": "operational" },
|
||
{ "name": "Authentication", "status": "operational" }
|
||
],
|
||
"timestamp": "2026-03-22T10:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
Статус Database проверяется реальным запросом `SELECT 1` к MariaDB.
|
||
|
||
### Health Endpoint
|
||
|
||
**URL:** `GET /health` — расширенная информация о процессе (не требует авторизации):
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"uptime": 3600,
|
||
"memory": {
|
||
"rss": "128MB",
|
||
"heap": "64/128MB"
|
||
},
|
||
"node": "v22.0.0",
|
||
"pid": 12345
|
||
}
|
||
```
|
||
|
||
### Логи
|
||
|
||
Структурированный вывод в stdout (формат: `[ISO timestamp] [LEVEL] message {meta}`). PM2 пишет stdout в `/home/treamz/.pm2/logs/images-out.log`, stderr — в `images-error.log`.
|
||
|
||
Операции сжатия изображений дополнительно пишутся в файл `compress.log` в корне проекта (через `log.logToFile`).
|
||
|
||
---
|
||
|
||
## Технологии
|
||
|
||
| Компонент | Технология | Назначение |
|
||
|---|---|---|
|
||
| Runtime | Node.js 18+ | Серверная среда выполнения |
|
||
| Framework | Express 5 | HTTP-сервер, маршрутизация |
|
||
| База данных | MariaDB 10.6+ | Хранение пользователей, настроек, контента |
|
||
| ORM | Sequelize 6 | Модели для AdminJS |
|
||
| DB Driver | mysql2 | Прямые SQL-запросы (пул соединений) |
|
||
| Авторизация | bcrypt + express-session | Хеширование паролей, серверные сессии |
|
||
| Админ-панель | AdminJS 7 + @adminjs/sequelize | Веб-интерфейс управления данными |
|
||
| Изображения | Sharp | Конвертация, сжатие, генерация placeholder |
|
||
| Видео | FFmpeg + ffprobe | Конвертация, сжатие, извлечение аудио, GIF |
|
||
| PDF | pdf-lib | Merge, split, rotate, watermark, protect |
|
||
| PDF | pdf-parse | Извлечение текста и метаданных |
|
||
| PDF | Ghostscript | Сжатие, конвертация PDF в изображения |
|
||
| Парсер | @mozilla/readability + JSDOM | Извлечение контента статей |
|
||
| Кодировки | iconv-lite | Декодирование страниц в non-UTF-8 |
|
||
| Загрузка файлов | Multer | Обработка multipart/form-data |
|
||
| Архивация | Archiver | ZIP-архивы с результатами |
|
||
| Геолокация | geoip-lite | Определение страны по IP в логах |
|
||
| Безопасность | Helmet | HTTP security headers |
|
||
| Rate Limiting | express-rate-limit | Ограничение частоты запросов |
|
||
| Шрифты SVG | opentype.js | Работа с шрифтами (вспомогательная) |
|
||
| UI | Tailwind CSS (CDN) | Стилизация интерфейса |
|
||
| Шрифты UI | JetBrains Mono, Manrope | Типографика |
|
||
| Процесс | PM2 | Управление процессом в production |
|
||
| Веб-сервер | Nginx | Reverse proxy, SSL termination |
|
||
| SSL | Let's Encrypt (certbot) | TLS-сертификаты |
|
||
|
||
---
|
||
|
||
## База данных
|
||
|
||
### Схема таблиц
|
||
|
||
**`users`** — учётные записи пользователей
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `id` | INT PK AI | Идентификатор |
|
||
| `email` | VARCHAR(255) UNIQUE | Email-адрес |
|
||
| `password_hash` | VARCHAR(255) | bcrypt-хеш пароля |
|
||
| `display_name` | VARCHAR(100) | Отображаемое имя |
|
||
| `role` | ENUM('user','admin') | Роль, по умолчанию `user` |
|
||
| `is_blocked` | BOOLEAN | Блокировка аккаунта |
|
||
| `created_at` | DATETIME | Дата регистрации |
|
||
| `last_login` | DATETIME | Дата последнего входа |
|
||
|
||
**`settings`** — настройки приложения (key-value)
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `id` | INT PK AI | Идентификатор |
|
||
| `setting_key` | VARCHAR(100) UNIQUE | Ключ настройки |
|
||
| `setting_value` | TEXT | Значение |
|
||
| `description` | VARCHAR(255) | Описание для AdminJS |
|
||
| `updated_at` | DATETIME | Дата обновления |
|
||
|
||
**`categories`** — категории инструментов
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `id` | INT PK AI | Идентификатор |
|
||
| `slug` | VARCHAR(50) UNIQUE | URL-идентификатор |
|
||
| `title` | VARCHAR(100) | Название |
|
||
| `description` | VARCHAR(255) | Описание |
|
||
| `icon_svg` | TEXT | SVG-иконка категории |
|
||
| `color` | VARCHAR(20) | Акцентный цвет (hex) |
|
||
| `sort_order` | INT | Порядок отображения |
|
||
|
||
**`tools`** — инструменты платформы
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `id` | INT PK AI | Идентификатор |
|
||
| `slug` | VARCHAR(50) UNIQUE | URL-идентификатор |
|
||
| `title` | VARCHAR(100) | Название |
|
||
| `description` | VARCHAR(255) | Описание |
|
||
| `path` | VARCHAR(100) | URL-путь инструмента |
|
||
| `icon_svg` | TEXT | SVG-иконка |
|
||
| `category_id` | INT FK | Ссылка на `categories.id` |
|
||
| `sort_order` | INT | Порядок в категории |
|
||
| `is_enabled` | BOOLEAN | Включён/отключён |
|
||
|
||
**`content_blocks`** — текстовые блоки лендинга
|
||
|
||
| Поле | Тип | Описание |
|
||
|---|---|---|
|
||
| `id` | INT PK AI | Идентификатор |
|
||
| `block_key` | VARCHAR(100) UNIQUE | Уникальный ключ блока |
|
||
| `title` | VARCHAR(255) | Заголовок |
|
||
| `body` | TEXT | HTML-содержимое (richtext) |
|
||
| `section` | VARCHAR(50) | Секция лендинга (`advantages`, `dashboard` и др.) |
|
||
| `sort_order` | INT | Порядок в секции |
|
||
| `is_visible` | BOOLEAN | Отображать/скрыть |
|
||
| `updated_at` | DATETIME | Дата последнего изменения |
|