GitHub - AleksSPb/ai-for-developers-project-387 · GitHub
Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Календарь звонков

hexlet-check

Сервис записи на звонок. Гость выбирает дату и свободный слот, подтверждает запись именем и почтой, а потом находит её в списке записей. Один календарь, слоты по 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 — не удаляйте и не переименовывайте ни его, ни репозиторий.

О Хекслете

Хеклет — школа программирования: авторские программы обучения с практикой, поддержкой наставников и реальными проектами, которые остаются в резюме. Этот репозиторий — один из таких проектов.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages