
Образовательный центр с 200+ учениками работал через мессенджеры, таблицы и почту — процессы были разрознены между преподавателями, учениками и родителями. Учебные материалы жили в чатах, результаты тестов вели вручную, расписание постоянно менялось. Школе была нужна единая точка правды — но ни одна готовая система не покрывала специфику работы репетиторского центра.
Платформа, собранная целиком с нуля. Бэкенд: Java 21 и Spring Boot 3.5, аутентификация на JWT, PostgreSQL 16 под управлением Flyway, S3-совместимый RustFS для файлов, jsoup чистит любой пользовательский HTML. Фронтенд: React 18 на Vite и Tailwind 4, редактор TipTap, FullCalendar и drag-and-drop для расписания. Всё работает на одной VPS за nginx — Docker Compose, TLS от certbot, рейт-лимиты, заголовки безопасности, Prometheus для мониторинга и ночные бэкапы базы.
Строк кода
Java-бэкенд, React-фронтенд
Таблиц в базе
20 миграций Flyway
REST-контроллеров
Авторизация, курсы, тесты, чат
В проде
Коммерческий заказ, каждый день

JWT с ротацией refresh-токенов, подтверждение почты и ссылки для установки пароля, три роли — ученик, преподаватель, админ — проверяются во всех 20 REST-контроллерах. Без стороннего провайдера входа.
S3-совместимый RustFS держит учебные материалы, картинки тестов, вложения из чатов, медиа новостей и PDF образовательных программ в четырёх отдельных бакетах. В октябре 2026 он сменил MinIO без единой правки в коде бэкенда. Файлы не покидают сервер заказчика, и за хранение никто не платит.
После каждой сборки Playwright обходит все публичные маршруты и складывает статические снимки, которые nginx отдаёт первыми — всем подряд, так что контент виден до запуска JavaScript. Поисковым ботам вдобавок отдаются страницы, отрисованные прямо из базы: разделение по User-Agent на уровне nginx.
Боевая версия в проде
Платформа работает на cp-binom.ru с апреля 2026 года, ей каждый день пользуются ученики и преподаватели центра. Сам портал за логином, публичная часть открыта всем.

Расскажу про решения, которые за этим стоят: почему своё хранилище, почему два пути рендеринга одного и того же контента и что бы я сделал иначе.