Сервис записи на звонок. Гость выбирает дату и свободный слот, подтверждает запись именем и почтой, а потом находит её в списке записей. Один календарь, слоты по 30 минут в окне приёма.
Разработан совместно с ИИ.
Учебный проект Хекслета: https://ru.hexlet.io/programs/ai-for-developers Как это должно работать: https://files.hexlet.app/a/2ipc5m
Приложение и сервер лежат на Render:
https://ai-for-developers-project-386-1-d6fi.onrender.com/
Для добавления типов событий - https://ai-for-developers-project-386-1-d6fi.onrender.com/#/event-types
На Render это два сервиса: страницу раздаёт статическая площадка, API отвечает отдельный сервис, и клиент собирается со ссылкой на него. Проверка курса устроена иначе — она собирает один образ и ждёт от него и страницу, и API, — поэтому тот же код собирается и так: клиент отдаёт сам сервер, а база поднимается внутри контейнера (см. «Сборка в образ»). Локальный запуск с живым сервером описан в разделе «Два режима».
Клиент: React 19, TypeScript, Vite, Mantine 9, React Router 8. Тесты — Vitest и Testing Library.
Сервер: Java 25, Spring Boot 4.1.1, Maven, PostgreSQL, Liquibase.
GLOSSARY.md— словарь предметной области: Слот, День, Бронь, Гость, Встреча, Местное время гостя.docs/adr/— решения, которые выглядят ошибкой, если не знать причины: почему слоты не хранятся, почему между компонентами и клиентом нет порта, почему время в интерфейсе одно.vite-project/— клиент приложения. Команды разработки — из этой папки.server/— сервер на Spring Boot: локальный запуск, схема базы, сид окон приёма и все семь операций контракта — отGET /windowsдоGET /bookings. Подробности — вserver/README.md.Dockerfileв корне — образ приложения целиком: внутри собирается клиент и поднимается база (см. «Сборка в образ»).server/Dockerfile— образ одного сервера, он нуженdocker composeи деплою.vite-project/src/domain/— расписание, брони и время. Чистый TypeScript: ни React, ни сети, всё проверяется без браузера.vite-project/main.tspиvite-project/contract/openapi.yaml— контракт API. Источник правды —main.tsp, из него собирается спецификация.vite-project/src/api/— слой обращения к серверу: адрес, сборка запроса и сгенерированный клиент. Клиент генерируется из спецификации и не коммитится. Компоненты и хуки ходят в него прямо, без порта между ними — подробности в ADR-0006.
Раздел Владельца календаря не охраняется: ни профиля, ни прав, ни входа у него нет, и открывает его любой, кто знает адрес. Это осознанное ограничение учебного проекта, а не забывчивость — подробности в ADR-0004.
Клиент:
git clone https://github.com/AleksSPb/ai-for-developers-project-387.git
cd ai-for-developers-project-387
cd vite-project
npm installРучных шагов после клона не нужно: клиент генерируется сам при запуске разработки, тестов и сборки.
Сервер (нужен Maven 3.9.x и Docker):
cd server
docker compose up --buildПоднимется база и сервер на http://localhost:8080, миграции выполнятся при
старте. Подробности — в server/README.md.
Чтобы клиент ходил в этот сервер, задайте адрес (см. «Два режима» ниже).
docker build -t calendar-app .
docker run --rm -p 8080:8080 -e PORT=8080 calendar-appОбраз самодостаточен: клиент собирается внутри и его отдаёт сам сервер, а
PostgreSQL поднимается в контейнере — поэтому http://localhost:8080 отвечает и
страницей, и API. Типов событий рядом нет, и первый заводят руками — как и при
локальном запуске.
Встроенная база нужна только такому запуску. Чтобы сервер пошёл во внешнюю
(Neon на Render), задайте POSTGRES_HOST и остальные POSTGRES_* — тогда
встроенная не стартует вовсе. Порт читается из PORT; SERVER_PORT перебивает
его, если на машине удобнее своё имя.
По этому файлу собирает образ и автоматическая проверка курса
(.github/workflows/hexlet-check.yml): она запускает контейнер с PORT=8080 и
ждёт 200 на главной странице. server/Dockerfile — отдельный: он собирает
только сервер и живёт ради docker compose и деплоя.
Почему база и клиент живут в образе — ADR-0009.
Приложение лежит в каталоге vite-project:
npm run dev # http://localhost:5173Адреса вида /#/book и /#/bookings работают на любой статической раздаче без
серверной настройки.
Как записаться: выбрать дату в календаре и свободный слот, нажать «Продолжить», ввести имя и почту, нажать «Подтвермить запись». Запись появится в разделе «Брони» и переживёт перезагрузку страницы.
Дальше по горизонту календаря: у заглушек доступно четыре дня, потому что столько заводят они. Сервер заводит четырнадцать, и в режиме «с сервером» срок больше. Сид одноразовый: когда эти четырнадцать дней истекут, календарь станет пустым до тех пор, пока окна не заводят заново.
Режим выбирает адрес сервера, а не отдельный флаг: заглушка поверх ответов настоящего сервера была бы ложью.
С заглушками — режим по умолчанию. Переменная VITE_API_BASE_URL не
задана, приложение отвечает себе само рукописными заглушками
(src/test/handlers.ts, те же самые, что и в тестах). Работать можно без
сервера вообще.
npm run devС сервером — адрес задан, заглушки не поднимаются, запросы идут в
server/. Поднимите сервер (docker compose up --build) и задайте адрес:
echo 'VITE_API_BASE_URL=http://localhost:8080' > .env.local
npm run devАдрес можно задать и прямо в команде: VITE_API_BASE_URL=http://localhost:8080 npm run dev. Файл .env.local удобнее — его не надо повторять, и он не
коммитится.
Свой источник — адрес /: сервер живёт по тому же адресу, что и страница.
Так собирается образ приложения, где клиент и API отдаёт один процесс, и
абсолютного адреса у клиента нет. / — тоже заданный адрес, поэтому заглушки
при нём не поднимаются, а запрос уходит в свой источник.
Сервер должен пускать эту страницу. Браузер считает localhost:5173 и
localhost:8080 разными источниками и без разрешения выбрасывает ответы: GET
молча, а POST и PATCH ещё и на предварительном запросе. Разрешённые адреса
живут в server/src/main/resources/application.yml (свойство
cors.allowed-origins, по умолчанию http://localhost:5173) — если Vite
занял другой порт, поправьте его и там, перезапустив сервер. Подробности — в
server/README.md, раздел «Откуда приходит клиент».
Первый запуск против сервера почти пустой. Сид заводит окна приёма, а типов
событий в базе нет — их завёл бы себе Владелец. Заведите Тип в разделе
Владельца (/#/event-types), иначе страница выбора покажет пустоту, а это
выглядит как поломка сервера.
Клиентские команды выполняются из каталога vite-project:
npm run dev # dev-сервер
npm run test # тесты
npm run lint # eslint
npm run build # сборка и проверка типов
npm run contract # пересобрать спецификацию API из main.tsp
npm run client # пересобрать клиент из спецификацииКлиент генерируется из contract/openapi.yaml и в репозиторий не попадает: он
воспроизводится командами выше. Правьте main.tsp, а не сгенерированный файл.
Правка main.tsp считается изменением контракта: спецификацию нужно
пересобрать и закоммитить вместе с источником.
Тест src/test/contract.test.ts сверяет рукописные заглушки со спецификацией и
падает, если у операции не разобран хотя бы один код ответа.
Сообщения коммитов оформляются по Conventional Commits, правила и список типов описаны в docs/conventional-commits.md. Формат проверяет commitlint на этапе коммита.
Автоматические тесты Хекслета
Тесты запускаются на каждый коммит. За запуск отвечает файл .github/workflows/hexlet-check.yml — не удаляйте и не переименовывайте ни его, ни репозиторий.
Хеклет — школа программирования: авторские программы обучения с практикой, поддержкой наставников и реальными проектами, которые остаются в резюме. Этот репозиторий — один из таких проектов.
