В компании нужен единый сервис бронирования переговорок: администраторы создают переговорки и настраивают расписание их доступности (например, по дням недели и времени), система сама формирует слоты для бронирования на основе расписания. Сотрудники просматривают свободные слоты и создают или отменяют брони.
Необходимо реализовать сервис, который позволит:
Администратору:
- Создавать переговорки (только admin)
- Создавать расписание доступности переговорки один раз (дни недели, время начала и окончания). После создания расписание изменить нельзя. Длительность одного слота фиксирована — 30 минут. Слоты система формирует сама на основе расписания (например, на скользящее окно дат или при запросе списка доступных слотов — необходимо самостоятельно выбрать подход и обосновать в README). (только admin)
- Просматривать список всех броней с поддержкой пагинации (только admin)
Пользователю:
- Просматривать список переговорок (admin и user)
- Просматривать доступные для бронирования слоты по переговорке и дате (admin и user)
- Создавать бронь на слот от своего имени (только user)
- Отменять свою бронь (только user)
- Просматривать список своих броней (только user)
Основные бизнес-ограничения:
- Один слот может быть занят только одной активной бронью.
- Слоты одной переговорки не должны пересекаться по времени.
- Если для переговорки нет расписания, слоты для неё не создаются — такая переговорка считается всегда недоступной для бронирования.
- Все даты и время хранятся и передаются в UTC.
- Администратор не может создавать брони — бронирование доступно только роли user.
- Отмена брони является идемпотентной операцией: повторный вызов на уже отменённой брони не является ошибкой и возвращает 200 с актуальным состоянием брони.
- В 99.9% случаев пользователи запрашивают для бронирования доступные слоты в пределах ближайших 7 дней от текущей даты.
- Нельзя создать бронь на слот, временной интервал которого находится в прошлом — такой запрос возвращает ошибку 400.
- Список броней текущего пользователя (
/bookings/my) возвращает только брони на будущие слоты; брони на уже прошедшие слоты в ответ не включаются.
Пользователь — участник системы с уникальным идентификатором, email, ролью (admin / user) и датой регистрации.
Переговорка (Room) — ресурс с уникальным идентификатором, названием, опционально — описанием и вместимостью. Управление переговорками доступно только роли admin.
Расписание (Schedule) — правило доступности переговорки: к какой переговорке относится, в какие дни недели и в каком временном диапазоне (начало и конец) она доступна для бронирования. Длительность одного слота фиксирована — 30 минут.
Слот (Slot) — временной интервал в рамках одной переговорки (начало и конец, date-time в UTC), сформированный системой по расписанию. В один момент времени слот может быть либо свободен, либо занят одной активной бронью. Слоты должны иметь стабильные UUID-идентификаторы, сохранённые в базе данных — иначе бронирование по slotId невозможно.
Бронь (Booking) — связь пользователя со слотом. Имеет статус active или cancelled. Только одна активная бронь на один слот. При отмене статус меняется на cancelled, слот становится свободным.
Авторизация: для получения токена доступа к API используется эндпоинт /dummyLogin, который выдаёт тестовый JWT по указанной роли (admin / user). Эндпоинт возвращает фиксированный UUID пользователя для каждой роли: один и тот же UUID для всех запросов с ролью admin и один и тот же UUID для всех запросов с ролью user. Это позволяет стабильно тестировать сценарии, требующие проверки владельца брони. Полноценные эндпоинты регистрации и авторизации по email/паролю являются дополнительным заданием (см. ниже).
- Используйте API из файла api.yaml. Реализация должна соответствовать описанной в нём спецификации.
- Объём данных: до 50 переговорок, до 1k слотов в день, до 10k пользователей, до 100k броней. RPS — 100, SLI успешности ответа — 99.9%. Самый высоконагруженный эндпоинт — получение списка доступных для бронирования слотов по переговорке и дате; требования по времени ответа в первую очередь должны учитывать именно этот эндпоинт (ориентир SLI времени ответа — 200 мс для данного эндпоинта).
- Для авторизации должен использоваться JWT. Тестовый токен доступа выдаётся через
/dummyLoginпо указанной роли (admin / user). JWT должен содержать как минимумuser_id(UUID) иrole. При создании брониuser_idберётся из токена, а не из тела запроса. - Реализуйте покрытие бизнес-сценариев юнит-тестами. Общее тестовое покрытие проекта должно превышать 40%.
- Реализуйте интеграционный или E2E-тест на сценарий: создание переговорки → создание расписания → создание брони пользователем.
- Реализуйте интеграционный или E2E-тест на сценарий отмены брони пользователем.
- Реализуйте GET API метод /_info, который должен возвращать на любое запрос 200.
Эти задания не являются обязательными, но выполнение всех или части из них даст вам преимущество перед другими кандидатами.
- Регистрация и авторизация по email/паролю. Реализовать эндпоинты
/registerи/loginс выдачей JWT. При регистрации создаётся пользователь в БД; при входе проверяется email и хеш пароля. Эндпоинт/dummyLoginявляется обязательным в любом случае и должен быть реализован независимо от этого задания. - Опциональное создание ссылки на конференцию при бронировании. В эндпоинт создания брони добавить опциональный параметр (например,
createConferenceLink: true). При его передаче необходимо выполнить запрос к внешнему сервису («Conference Service»), получить ссылку на конференцию и сохранить её в связи с созданной бронью. Реализовывать реальный внешний сервис не требуется — достаточно создать мок, имитирующий его поведение. Необходимо продумать поведение системы при сбоях: самостоятельно смоделируйте возможные случаи (недоступность внешнего сервиса, ошибка после успешного ответа и т.п.) и опишите принятые решения в README. - Makefile. Написать
Makefileс командами: запуск проекта со всеми зависимостями (make up) и наполнение БД тестовыми данными (make seed). - Swagger-документация. Настроить кодогенерацию Swagger-документации на основе аннотаций в коде (например,
swaggo/swagдля Go или аналог для выбранного языка). - CI в GitHub Actions. Настроить пайплайн: сборка приложения, запуск тестов, подсчёт тестового покрытия. Статусы джобов должны отображаться в заголовке
README.md. - Нагрузочное тестирование. Провести нагрузочное тестирование полученного решения и приложить краткие результаты к решению.
- Конфигурация линтера. Описать конфигурацию линтера (
.golangci.yamlв корне проекта для Go или аналог для выбранного вами языка).
Язык сервиса: предпочтительным является Go, но также допустимы следующие языки: PHP, Java, Python, C#.
База данных: рекомендуется использовать PostgreSQL, но также допустимо использовать MySQL.
Для деплоя зависимостей и самого сервиса используйте Docker Compose. Порт доступа к сервису должен быть 8080 и быть доступен снаружи как localhost:8080.
Если у вас возникнут вопросы по заданию, ответы на которые вы не найдёте в описанных «Условиях», вы можете принимать решения самостоятельно. В таком случае приложите к проекту README-файл, в котором будет список вопросов и пояснения, как вы решили проблему и почему именно выбранным способом.
Необходимо предоставить ссылку на свой репозиторий в форму для сбора заданий.