7.5 KiB
publishing/markdown Specification
Purpose
Перечень требований к сервису md2vk — публикация Markdown на стену VK через официальный VK API, с защитой доступа, аудитом и инфраструктурой деплоя.
Requirements
Requirement: Конвертация Markdown в формат VK
Сервис MUST конвертировать Markdown-текст в VK format_data (bold, italic, link, inline_code) и
разбивать длинные посты на чанки (лимит ~4096 символов, учёт @ = 2 символа).
Scenario: конвертация без публикации
- WHEN клиент вызывает
POST /api/v1/convertс валиднымmessage_mdи API-ключом - THEN сервис возвращает
{text, format_data:{version:1,items:[...]}}без обращения к VK
Requirement: Публикация поста на стене VK
Сервис MUST публиковать посты через официальный VK API wall.post (access_token, owner_id,
from_group, format_data), сохраняя запись в архив с статусом published/error.
Scenario: успешная публикация
- WHEN клиент вызывает
POST /api/v1/publishс валидным API-ключом, vk_account_id и message_md - THEN сервис вызывает VK API wall.post и возвращает
{success:true, vk_post_id, vk_owner_id, url}, а публикация получает статусpublished
Scenario: ошибка VK API
- WHEN VK API возвращает ошибку (невалидный/отозванный токен, лимиты)
- THEN сервис возвращает
{success:false, error}и сохраняет публикацию со статусомerrorи текстом ошибки вerror_message
Requirement: Управление VK-аккаунтами
Сервис MUST поддерживать несколько VK-аккаунтов на пользователя: добавление (с проверкой токена через users.get), список, soft-delete; токены хранить только зашифрованными.
Scenario: добавление аккаунта с валидным токеном
- WHEN клиент вызывает
POST /api/v1/accountsс api_key и корректным access_token (scope wall) - THEN сервис проверяет токен через VK users.get, шифрует его Fernet и сохраняет аккаунт
Scenario: дубликат аккаунта
- WHEN клиент добавляет VK-аккаунт, который уже есть у пользователя (тот же vk_user_id)
- THEN сервис возвращает 409 и не создаёт дубликат
Requirement: Аутентификация по API-ключу
Все эндпоинты /api/v1/* (кроме /health) MUST требовать API-ключ md2vk_... (Bearer-заголовок
или поле api_key в теле); в БД хранится только SHA-256 хэш; сравнение constant-time.
API-ключ SHOULD NOT позволять прочитать VK-токен.
Scenario: запрос без ключа
- WHEN клиент вызывает
/api/v1/accountsбез Authorization-заголовка - THEN сервис возвращает 401 с сообщением об использовании Bearer
Scenario: неверный ключ
- WHEN клиент передаёт ключ, хэш которого не найден в БД
- THEN сервис возвращает 401
Requirement: Шифрование VK-токенов
VK OAuth-токены MUST храниться в БД только в зашифрованном виде (Fernet: AES-128-CBC + HMAC-SHA256);
ключ шифрования — из Docker secret /run/secrets/token_encryption_key; расшифровка только в памяти.
Scenario: утечка файла БД
- WHEN файл
data/md2vk.dbпопадает в чужие руки без ключа шифрования - THEN VK-токены не могут быть расшифрованы
Requirement: Внешний контроль доступа (basic auth + fail2ban)
Прод-домен https://md2vk.nixg.ru MUST быть закрыт HTTP Basic Auth (пользователь estorozhenko,
bcrypt-хэш в Caddyfile). Сервис MUST блокировать IP после 5 неудачных попыток подряд (fail2ban,
jail md2vk), с ручной разблокировкой (fail2ban-client unban).
Scenario: неверный пароль 5 раз подряд
- WHEN клиент 5 раз подряд отправляет запросы с неверным паролем basic auth
- THEN fail2ban банит IP (bantime=-1), последующие запросы получают отказ соединения
Scenario: ручная разблокировка
- WHEN администратор выполняет
fail2ban-client unban <ip>на vps02 - THEN IP снова может обращаться к сервису
Requirement: Аудит-лог запросов API
Сервис MUST логировать каждый запрос /api/v1/* в JSONL (ts, ip, method, path, api_key_prefix,
user_id, status, success, latency_ms, error) с ротацией по дням.
Scenario: запрос к API
- WHEN любой запрос к
/api/v1/*завершается - THEN в каталоге логов (AUDIT_LOG_DIR, по умолчанию ./logs) появляется строка JSON с полями ts, status, latency_ms и др.
Requirement: Деплой через docker и Caddy
Приложение MUST запускаться docker-контейнером на bigbox, слушать 127.0.0.1:8420, монтировать ./data → /data (SQLite), ./logs → /logs, секрет token_encryption_key; Caddy на vps02 MUST проксировать md2vk.nixg.ru → 10.8.0.2:8420 с basic auth и TLS (Let's Encrypt).
Scenario: проверка здоровья через Caddy
- WHEN выполняется
curl https://md2vk.nixg.ru/api/v1/healthс верными учётными данными - THEN возвращается
{"status":"ok"}
Requirement: Git-зеркала
Репозиторий MUST иметь истину на gitverse.ru (kpa39l/md2vk) и pull mirror на gitea.nixg.ru (estorozhenko/md2vk); секреты и данные не коммитятся (.gitignore).
Scenario: синхронизация зеркала
- WHEN в gitverse появляется новый коммит в main
- THEN gitea.nixg.ru подтягивает его по расписанию (mirror interval)
Requirement: Отложенные посты
Сервис MUST сохранять посты с publish_date в статусе scheduled. Планировщик (worker,
выполняющий wall.post для scheduled-постов) — открытая задача (SHOULD в будущем).
Scenario: отложенная публикация
- WHEN клиент вызывает
/api/v1/publishс publish_date в будущем - THEN публикация сохраняется со статусом
scheduledи не отправляется в VK сразу