Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
name: CI

on:
push:
branches: [ "**" ]
# Запускаем проверки для pull request'ов, целевая ветка — main
pull_request:
branches: [ "**" ]
branches: [ "main" ]
# И дополнительно — для пушей в main (после merge), чтобы проверить уже то, что влито
push:
branches: [ "main" ]

# Если быстро прилетает новый коммит в ту же ветку/PR — отменяем предыдущий прогон
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref || github.head_ref }}
cancel-in-progress: true

jobs:
test:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
.env
.DS_Store
coverage/
.nyc_output/
188 changes: 173 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,177 @@
[![CI](https://github.com/iBubenok/booking-api/actions/workflows/ci.yml/badge.svg)](https://github.com/iBubenok/booking-api/actions/workflows/ci.yml)

# Booking API

Демо API для бронирования мест:
- запрет повторной брони одним пользователем на одно событие,
- защита от овербукинга.
Демо API бронирования мест:
- **один пользователь — одна бронь** на одно событие,
- **без овербукинга** (конкурентно-безопасно, блокировка `FOR UPDATE` + уникальный индекс).

## Живой сервис

**Base URL:** **https://booking-api-mwff.onrender.com**

Быстрые ссылки:
- Домашняя страница — `/`
- Документация (Swagger UI) — `/docs`
- Health-check (app) — `/health`
- Health-check (DB) — `/health/db`
- Основной эндпоинт — `POST /api/bookings/reserve`

---

## Проверка доступности

Откройте в браузере:
- `/health` → ожидается: `{"status":"ok","db":"configured"}`
- `/health/db` → ожидается: `{"status":"ok"}` (при живой БД) или `503 {"status":"db-down"}`
- `/docs` → откроется Swagger UI (можно тестировать API из браузера)

---

## Примеры запросов (curl)

### 1) Первая бронь (ожидаем **201 Created**)

```bash
curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"user123"}'
````

Ожидается:

* статус `HTTP/1.1 201`
* JSON с полями `booking` и `seats_left`.

### 2) Повторная бронь тем же пользователем на то же событие (ожидаем **409 Conflict**)

```bash
curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"user123"}'
```

Ожидается:

* статус `HTTP/1.1 409`
* JSON `{"error":"User already booked this event"}`.

### 3) Продажа всех мест и отказ «sold out» (у события `#1` всего **3** места)

```bash
# три уникальные брони (должны пройти: 201 201 201)
curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"u2"}'

curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"u3"}'

curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"u4"}'

# четвёртая попытка (ожидаем 409 sold out)
curl -i -X POST https://booking-api-mwff.onrender.com/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id":1,"user_id":"u5"}'
```

Ожидается:

* первые три запроса вернут `201`,
* четвёртый — `409` и сообщение `Event is sold out`.

### 4) Тест конкуренции (быстрый)

Отправьте несколько запросов одновременно (через Swagger UI или скриптом) на одно и то же `event_id` с разными `user_id`.
Итог: число успешных бронирований **не превысит** `total_seats`, а лишние вернут `409`.

---

## Поведение API и коды ответов

* `POST /api/bookings/reserve`

* **Вход**: JSON `{"event_id": number, "user_id": string}`

* `event_id`: целое `> 0`
* `user_id`: непустая строка, **≤ 128** символов, допускаются только `A–Z a–z 0–9 _ - . : @`
* **Выход**:

* `201 Created` — бронь создана, `{ booking, seats_left }`
* `400 Bad Request` — неверный формат входа
* `404 Not Found` — событие не найдено
* `409 Conflict` — повторная бронь того же пользователя **или** мест больше нет
* `503 Service Unavailable` — БД не настроена (`DATABASE_URL` не задан)
* `500 Internal Error` — непредвиденная ошибка
* **Ограничение частоты**: на `POST /api/bookings/reserve` действует rate-limit **30 запросов/мин** на IP (помогает от случайных/скриптовых «бурстов»).

---

## Безопасность и сервисные возможности

* `helmet` (HTTP-заголовки безопасности, настроен `crossOriginResourcePolicy` для Swagger UI)
* `cors` (разрешены кросс-доменные запросы)
* `express.json({ limit: '32kb' })` (лимит тела запроса)
* `morgan('combined')` (логирование запросов)
* `app.set('trust proxy', 1)` (корректно определяем IP клиента за прокси/балансировщиком — важно для rate-limit)
* **Graceful shutdown** по `SIGINT`/`SIGTERM` (корректное закрытие соединений с БД)
* **Fallback 404** JSON-ответом

---

## Быстрый старт (локально)
1) `npm ci`
2) Создайте `.env` из `.env.example` и укажите `DATABASE_URL`
3) `npm start`
4) Откройте `/docs` для Swagger UI и `/health` для healthcheck

## Эндпоинты
- `POST /api/bookings/reserve`
- `GET /health`
- `GET /docs`

## Миграции
`db/migrations.sql` (таблицы, индексы, сиды)

1. Установите зависимости:

```bash
npm ci
```
2. Создайте `.env` на основе `.env.example` и задайте переменные:

```dotenv
PORT=3000
DATABASE_URL=postgres://user:password@host:5432/dbname
# Включайте SSL для облачных БД (Neon/Render и т.п.)
DATABASE_SSL=true
```
3. Запуск:

```bash
npm start
```
4. Проверьте:

* `GET http://localhost:3000/health`
* `GET http://localhost:3000/docs`

### Миграции

Выполните файл `db/migrations.sql` в вашей БД (создаёт таблицы, индексы и сид-данные).

---

## Структура проекта

```
.
├─ db/
│ └─ migrations.sql # таблицы, индексы, сиды
├─ __tests__/ # (опц.) автотесты
├─ openapi.yaml # спецификация OpenAPI для Swagger UI
├─ server.js # запуск приложения, graceful shutdown
├─ package.json
├─ .env.example
└─ README.md
```

---

## CI/CD

* **CI**: GitHub Actions (`.github/workflows/ci.yml`) выполняет `npm ci` и `npm test` на каждый `push`/`PR`.
* **CD**: Render Blueprint (`render.yaml`) автодеплоит ветку `main`; `/health` используется как health-check.

---