Files
md2vk/openspec/specs/publishing/markdown/spec.md
T

7.5 KiB
Raw Blame History

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 сразу