Маркетплейс / EdTech
Repetitor.my — маркетплейс репетиторов
Repetitor.my — заказная разработка полноценного B2C-маркетплейса репетиторов для Москвы, Санкт-Петербурга, Екатеринбурга, Нижнего Новгорода и Казани. Сервис объединяет каталог преподавателей, кабинеты ученика и репетитора, расписание и онлайн-запись.
Обсудить похожий проект
Задача
Разработать мультигородской маркетплейс, где ученик находит репетитора и записывается на занятие, а преподаватель управляет профилем и расписанием.
Решение
Как реализовали проект
Единое приложение на Next.js обслуживает все города без копирования кода. Поддомен определяет город и переводит запрос на общий динамический маршрут, PostgreSQL хранит профили, предметы, цены, расписание, заявки и контент, а отдельный Python-сервис подготавливает автоматический импорт объявлений. Проект разбит на 10 принимаемых этапов с прозрачным журналом прогресса.
Состав работ
Что вошло в решение
- 01Мультигородской каталог на единой кодовой базе
- 02Личные кабинеты ученика, репетитора и администратора
- 03Онлайн-запись со слотами на 30, 60 и 90 минут
- 04Ранжирование преподавателей по реальным метрикам
- 05Управление городами, категориями и контентом
- 06Интеграционный контур импорта объявлений Avito
Инфраструктура
Архитектура решения
- 01
Next.js 16 с App Router обслуживает публичный каталог, личные кабинеты и административный интерфейс из одной кодовой базы.
- 02
Прокси-слой определяет город по Host и делает rewrite на общий маршрут /[city]/; локально та же логика проверяется через параметр ?city= без wildcard-DNS.
- 03
PostgreSQL и Drizzle ORM описывают 18 таблиц с индексами под реальные запросы и составными ключами для связей многие-ко-многим.
- 04
Единая таблица accounts отвечает за роли admin, tutor и student, а профильные данные вынесены в связанные сущности.
- 05
Python-сервис скрапинга запускается по systemd-таймеру и передаёт данные через защищённый API-контракт импорта.
- 06
Демо работает в отдельном LXC-контейнере; приложение и Cloudflare Tunnel разделены на независимые systemd-юниты, чтобы рестарт приложения не менял публичную ссылку.
Возможности
Функционал
Мультигородской каталог
Одна кодовая база обслуживает пять городов. Город определяется по поддомену, а список городов редактируется в админке без изменения маршрутизации вручную.
Профили и личные кабинеты
Репетитор управляет предметами, классами, ценами, медиа и доступностью, ученик сохраняет данные и создаёт заявки, администратор контролирует справочники и контент.
Онлайн-запись на занятия
Сетка 8:00–23:00 работает с шагом 30 минут. Бронь на 30, 60 или 90 минут занимает последовательные слоты и не может выйти за конец рабочего дня.
Умное хранение расписания
По умолчанию время доступно, а в базу записываются только закрытые интервалы. Пустая база сразу означает полностью открытое расписание без стартовой миграции.
Ранжирование по реальным метрикам
Карточки сортируются по онлайн-статусу, заявкам за день, лайкам и дате последнего поста. Каждый показатель служит детерминированным тайбрейком предыдущего.
Импорт объявлений
Отдельный Python-сервис обходит матрицу город × категория, собирает доступные данные и отправляет их в приложение через bearer-защищённый endpoint.
Постепенное наполнение контента
Вопросы, ответы и фотографии получают заранее рассчитанные даты публикации и появляются постепенно при чтении, без отдельной постоянно работающей cron-задачи.
Администрирование и контроль
Админка управляет городами, категориями, рекламой и настройками сайта, а GET /api/health позволяет подключить внешний мониторинг доступности.
Стек проекта
Технологии
Защита данных
Безопасность
- Пароли хешируются bcryptjs, формы и API проходят Zod-валидацию, а сессионные cookies получают secure-флаг.
- Тип и размер файла проверяются единообразно во всех точках загрузки, включая массовое добавление и рекламные креативы.
- Дата записи проверяется не только по формату, но и на календарную корректность; несуществующие даты отклоняются.
- Вопросы и отзывы проходят кириллица-only проверку, которая блокирует транслит, но сохраняет цифры и пунктуацию.
- Импорт данных использует отдельный bearer-токен вместо пользовательской cookie-сессии.
Живые данные
Интеграции
UX / UI
Дизайн-процесс
Интерфейс строится вокруг короткого пути ученика: выбрать город и предмет, сравнить понятные карточки, открыть профиль и забронировать свободное время. Для репетитора сложные данные разделены на последовательные настройки профиля, цен и расписания. Адаптивная сетка, явные состояния слотов и единые формы уменьшают число ошибок на мобильных устройствах.
Инженерная работа
Нетривиальные решения
Пять городов без пяти приложений
Поддоменная маршрутизация отделена от страниц: новый город добавляется в базе и сразу использует общий набор компонентов и бизнес-правил.
Расписание без лишних данных
Инверсная модель хранит только закрытые слоты, поэтому база остаётся компактной, а новое расписание сразу доступно без генерации тысяч записей.
Avito блокирует лёгкие запросы
WAF возвращал 403 обычному HTTP-клиенту, поэтому интеграционный сервис использует полноценный headless-браузер. OCR номера телефона намеренно не реализован как обход антибот-защиты.
Органический рост без фоновой джобы
Контент публикуется по заранее рассчитанным датам, а запрос показывает только записи с createdAt не позднее текущего времени.
Повторный аудит перед приёмкой
Отдельная проверка нашла и закрыла шесть реальных ошибок безопасности и граничных случаев в загрузках, датах и валидации.
Честная граница демо и продакшна
Демо развёрнуто и мониторится, но реальный wildcard-домен, SSL, нагрузка и бэкапы не объявлены проверенными без доступа к инфраструктуре клиента.
Результат
Создана функциональная платформа и демо-стенд: закрыто около 96% сметы из 10 этапов, реализованы ключевые пользовательские сценарии, админка, тесты и инфраструктура. Боевой хостинг, реальный wildcard-домен и нагрузочное тестирование честно оставлены отдельным этапом после получения доступа от заказчика.
Следующий кейс