Событийная платформа для кураторов онлайн-обучения. Ученик пишет в сообщество VK, а куратор работает с вопросом в Telegram Mini App: отвечает вручную либо выбирает сообщения диалога, получает AI-черновик, редактирует и принимает или отклоняет его. AI не отправляет текст ученику без решения куратора.
Репозиторий содержит четыре Spring Boot-сервиса, React Mini App, локальную и staging-инфраструктуру, single-host production-профиль, мониторинг, backup и load-test scripts.
Production Mini App использует same-origin proxy: Cloudflare Worker передаёт
/api/miniapp/** на HTTPS-origin backend, Caddy маршрутизирует этот путь в
tg-connector-service, а production preflight требует URL и allowed origin
интерфейса. Порядок настройки описан в
production runbook.
- приём, проверка и дедупликация VK Callback API;
- привязка VK-сообществ к кураторам и синхронизация участников;
- постоянная история диалогов и рабочая очередь по ученикам;
- ручной ответ без AI-вызова и списания AI-кредитов;
- ручной выбор 1–20 сообщений одного диалога для AI-контекста;
- журналируемая генерация через OpenRouter и curator approval;
- reservation, charge, release и refund внутреннего баланса;
- идемпотентная доставка ответов и персональных рассылок в VK;
- каталог учеников, membership/messaging status и кураторские имена;
- рабочая область Google Таблиц с service account, гибкими заголовками, community-scoped фильтрами и диагностируемым preview;
- оплата Telegram Stars и административное пополнение;
- transactional outbox, retries, DLT registry и workflow watchdog;
- Actuator, Prometheus, Grafana, Alertmanager и PostgreSQL backup/restore.
Backend и отдельный mobile-first раздел Mini App уже поддерживают:
- подключение общего школьного журнала через единый service account без привязки самого файла к VK-сообществу;
- независимые curator-owned подключения одной таблицы, выбор нескольких листов и ручной выбор строки заголовков;
- поля по названиям без контракта на буквы колонок, позиции и оформление;
- community-scoped шаблоны фильтров с
AND/OR,EQUALS,NOT_EQUALS,IN,EMPTYиNOT_EMPTYи проверкой совместимости заголовков; - bounded чтение до 20 000 строк логическими страницами по 1000 и пакетами до
пяти диапазонов, после чего
maxPreviewRowsограничивает уже найденные карточки, а не область поиска; - сопоставление числового VK ID,
id123и ссылокvk.com/id123с каталогом выбранного сообщества, а также отдельные диагностики invalid, duplicate, unresolved profile и отсутствия ученика в сообществе; - защищённый Telegram
initDataflow от connection и выбора листа до fields, фильтра и preview; два реальных тестовых журнала проверены только на чтение; - миграцию
V26и owner-scoped backend-store для конфигурации VK/отображаемых полей и последнего успешного preview.
Хранилище V26 пока не подключено к HTTP refresh flow: после reload интерфейс
ещё использует browser state, а ошибка Google не возвращает последний snapshot.
Также остаются запись ячеек с optimistic conflict, создание рассылки из
выбранных карточек и финальный production E2E. Актуальный порядок только
оставшихся задач находится в
architecture backlog,
а подробный контракт — в
Google Sheets backend contract.
Фоновый polling, синхронизация по расписанию, AI-интерпретация структуры и отдельные парсеры предметов в текущий scope не входят.
sequenceDiagram
autonumber
actor Student as Ученик
participant VK as VK Connector
participant Kafka
participant Flow as Orchestrator
participant TG as TG Connector
participant App as Mini App
participant AI as AI Service
participant LLM as OpenRouter
actor Curator as Куратор
Student->>VK: Сообщение в VK
VK-->>Kafka: vk-webhook-intake
Kafka-->>VK: Асинхронное enrichment
VK-->>Kafka: vk-incoming-messages
Kafka-->>Flow: Создать workflow
Flow-->>Kafka: curator-intake-requests
Kafka-->>TG: Обновить проекцию очереди
Curator->>App: Открыть диалог
alt Ручной ответ
App->>TG: manual-answer
TG-->>Kafka: curator-intake-decisions
else AI-черновик
App->>TG: send-to-ai + выбранные messageIds
TG-->>Kafka: curator-intake-decisions
Flow-->>Kafka: reserve credits
Flow-->>Kafka: ai-generation-commands
Kafka-->>AI: Стабильная команда
AI->>LLM: Выбранный контекст
LLM-->>AI: Черновик + usage.cost
AI-->>Kafka: ai-generation-results
Flow-->>Kafka: curator-approval-requests
Kafka-->>TG: Сохранить draft
Curator->>App: Изменить и approve/reject
TG-->>Kafka: curator-decisions
Flow-->>Kafka: charge или release
end
Flow-->>Kafka: vk-outgoing-messages
Kafka-->>VK: Доставить финальный ответ
VK-->>Kafka: vk-message-delivery-results
Kafka-->>Flow: Complete или refund
Webhook, state transitions и обязательные исходящие команды сохраняются в БД.
Долгие шаги проходят через Kafka. Повторная доставка компенсируется
стабильными requestId, уникальными ключами, row locks, журналами side effects
и проверками состояния.
| Модуль | Ответственность | Порт |
|---|---|---|
vk-connector-service |
VK webhook, credentials, профили, участники, delivery journal и retries | 8081 |
orchestrator-service |
Saga/state machine, pricing validation, compensation, outbox, watchdog и DLT registry | 8082 |
ai-service |
OpenRouter, generation journal и AI conversation projection | 8083 |
tg-connector-service |
Telegram-боты, Mini App API/projections, billing, students и broadcasts | 8084 |
shared-libs |
Общие pricing, Kafka correlation/retry и AES-256-GCM | библиотека |
mini-app |
React/Vinext Telegram Mini App и Cloudflare Worker artifact | отдельно |
Каждый прикладной сервис владеет своей PostgreSQL database и Flyway миграциями. Межсервисного чтения таблиц нет; HTTP используется только на внешних границах, а внутренние команды и события передаются через Kafka.
- AI-текст не отправляется без curator decision.
- Ручной ответ не вызывает OpenRouter и не списывает AI-кредиты.
- Повтор события не должен повторять резерв, charge, refund, AI-вызов или отправку сообщения.
- Стоимость вычисляется из provider
usage.costобщей формулойmax(minimum_charge, ceil(provider_cost_usd * credits_per_usd)). - Orchestrator и TG billing независимо проверяют pricing и итоговую сумму.
- Необратимая ошибка оплаченной доставки запускает идемпотентный refund.
- Секреты VK в БД шифруются AES-256-GCM; plaintext credentials не должны попадать в Git или логи.
- Mini App доверяет только Telegram
initDataв заголовкеX-Telegram-Init-Data, проверенному backend по HMAC, возрасту и curator ownership.
| Область | Текущее состояние |
|---|---|
| Backend | Java 21, Spring Boot 3.5.15, Gradle wrapper 8.7 |
| Messaging | Apache Kafka, Spring Kafka |
| Persistence | PostgreSQL, Spring Data JPA, Hibernate, Flyway |
| Cache/rate limit | Redis, Lettuce, Bucket4j, Caffeine |
| Mini App | TypeScript 5.9.3, React 19.2.6, Vinext 0.0.50, Vite 8.0.13, Tailwind CSS 4.2.1 |
| Mini App runtime | Node.js 22.13+, pnpm 11.9.0, Cloudflare Vite plugin/Worker |
| Observability | Actuator, Micrometer, Prometheus, Grafana, Alertmanager |
| Tests | JUnit 5, Mockito, AssertJ, Spring Boot Test, Testcontainers, Node test runner, ESLint |
Application default OpenRouter model — openai/gpt-5.5; production model
задаётся явно через обязательную переменную OPENROUTER_MODEL. Production
preflight и Compose отклоняют пустое значение; .env.prod.example использует
тот же openai/gpt-5.5.
.
|-- shared-libs/ Общие backend-компоненты
|-- vk-connector-service/ VK intake, directory sync и delivery
|-- orchestrator-service/ Workflow state machine и compensation
|-- ai-service/ OpenRouter и generation journal
|-- tg-connector-service/ Telegram, Mini App API, billing, broadcasts
|-- mini-app/ React/Vinext Mini App
|-- docs/ Runbook, checklist и профильные контракты
|-- infra/ PostgreSQL init, Caddy, monitoring, stubs
|-- load-tests/ k6 VK webhook scenario
|-- scripts/ Preflight, staging, load, backup/restore
|-- .github/workflows/ CI и ручной staging load workflow
|-- docker-compose.yml Только локальная инфраструктура
|-- compose.staging.yml Полный изолированный E2E-стенд
`-- compose.prod.yml Single-host backend/infra profile
Нужны JDK 21, Docker Engine с Compose v2, Node.js 22.13+ и pnpm 11.9.0.
Создайте локальный env-файл и заполните credentials:
cp .env.example .envПоднимите PostgreSQL, Redis, Kafka и Kafka UI:
docker compose up -ddocker-compose.yml не содержит приложений. Запустите сервисы в отдельных
терминалах из корня репозитория:
./gradlew :vk-connector-service:bootRun
./gradlew :orchestrator-service:bootRun
./gradlew :ai-service:bootRun
./gradlew :tg-connector-service:bootRunЗатем запустите Mini App:
pnpm --dir mini-app install --frozen-lockfile
pnpm --dir mini-app devЛокальный Vite server проксирует /api/miniapp/** в 127.0.0.1:8084.
Live-режим требует Telegram initData; для изолированного UI-preview нужен
явный NEXT_PUBLIC_DEMO_MODE=true.
Backend unit tests:
./gradlew test --no-daemonIntegration tests с PostgreSQL/Kafka Testcontainers:
./gradlew integrationTest --no-daemonMini App:
pnpm --dir mini-app run lint
pnpm --dir mini-app run build
pnpm --dir mini-app run testКонфигурация и security checks:
docker compose --env-file .env.prod.example -f compose.prod.yml config --quiet
sh scripts/verify-prod-openrouter-config.sh
docker compose -f compose.staging.yml config --quiet
sh scripts/secret-scan.shCI выполняет Java unit/integration tests, Mini App lint/build/tests, Compose и monitoring validation, ShellCheck, backup/restore drill и secret scan.
docker-compose.yml— только локальные PostgreSQL 15, Redis 7, Kafka и Kafka UI; приложения запускаются отдельно.compose.staging.yml— четыре приложения, PostgreSQL 17, Redis, Kafka и WireMock. Провайдеры изолированы, TG polling выключен, auto-curator включён только с confirmation values.compose.prod.yml— четыре backend-приложения, PostgreSQL 17, Redis AOF, single-node Kafka, Caddy, Prometheus, Alertmanager и Grafana. Caddy публикует/vk/webhookи защищённый TelegraminitDataмаршрут/api/miniapp/**; ops UI слушают localhost.mini-app/.openai/hosting.json— отдельный Sites/Cloudflare deployment frontend. Worker проксирует same-origin API-запросы на origin из runtime variableMINI_APP_API_ORIGIN.
Production containers приложений запускаются не от root, с read-only
filesystem, no-new-privileges, health checks и ротацией логов. Production
профиль не настраивает PostgreSQL failover, multi-node Kafka или несколько
экземпляров приложений.
- Active broadcast после reload восстанавливается через API только на стадии
выбора получателей;
AWAITING_TEXTиREADYнельзя продолжить из UI. - AI использует один OpenRouter API key; provider failover и ротации нет.
- Conversation summaries, retention и удаление персональных данных не реализованы.
- Broadcast scheduling, сегментация и AI-генерация текста не реализованы.
- Для Google Таблиц создано owner-scoped хранилище snapshot/settings, но оно ещё не подключено к HTTP refresh/fallback; запись ячеек и переход к рассылке также не реализованы.
- AGENTS.md — актуальная карта проекта, инварианты, правила и действующие бизнес-задачи.
- Production runbook — операции single-host backend-профиля.
- Production launch checklist — ручные проверки запуска.
- Mini App queue contract — queue, conversations и decision semantics.
- Google Sheets backend contract — подключения, листы и сопоставление строк с учениками сообщества.
- Load testing — staging load ladder и критерии.
- Architecture backlog — ограничения вне текущей реализации.