API_CONTRACT.md 10 KB

API Контракт (OpenAPI 3.0)

Базовый URL

  • Локально: http://localhost:8080/api/v1
  • Стенд: https://staging.api.photoplaces.ru/api/v1
  • Прод: https://api.photoplaces.ru/api/v1

Аутентификация

  • Access Token: JWT, срок 15 мин, в заголовке Authorization: Bearer <token>
  • Refresh Token: JWT, срок 30 дней, передаётся в заголовке X-Refresh-Token
  • Роли: superadmin, moderator, landlord, executor, customer

Формат ошибок

{
  "error": "описание ошибки"
}

HTTP статусы: 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 422 (Validation Error), 500 (Internal Server Error)


Эндпоинты авторизации

POST /auth/register

// Запрос
{
  "email": "user@example.com",
  "password": "securePass123",
  "role": "customer",  // customer | landlord | executor
  "name": "Иван Иванов"
}

// Ответ 201
{
  "user": { "id": "uuid", "email": "...", "role": "customer", "created_at": "..." },
  "access_token": "eyJ...",
  "refresh_token": "eyJ..."
}

POST /auth/login

// Запрос
{
  "email": "user@example.com",
  "password": "securePass123"
}

// Ответ 200
{
  "user": { ... },
  "access_token": "eyJ...",
  "refresh_token": "eyJ..."
}

POST /auth/refresh

  • Заголовок: X-Refresh-Token: <refresh_token>
  • Ответ 200: новый access_token + обновлённый refresh_token

POST /auth/logout

  • Заголовок: Authorization: Bearer <access_token>
  • Ответ 204

GET /auth/me

  • Возвращает профиль текущего пользователя
  • Ответ 200: объект User

Эндпоинты мест (Places)

GET /places

Параметры запроса:

  • type: place | studio (по умолчанию оба)
  • tags: ID тегов (можно несколько, напр. ?tags=portrait&tags=nature)
  • min_rating: число 0-5
  • features: ID характеристик (можно несколько)
  • price_min, price_max: для студий
  • bounds: sw_lat,sw_lng,ne_lat,ne_lng (область карты)
  • limit: 1-100 (по умолчанию 20)
  • sort: rating | created_at | distance (требует user_lat,user_lng)

Ответ 200:

{
  "data": [
    {
      "id": "uuid",
      "type": "place",
      "owner_id": "uuid",
      "title": "Красивый парк",
      "description": null,
      "address": "Москва, Парковая 1",
      "lat": 55.75,
      "lng": 37.61,
      "cover_image": "https://cdn.../uuid.jpg",
      "access_info": null,
      "status": "published",
      "rating": 4.5,
      "reviews_count": 12,
      "hourly_rate": null,
      "currency": "RUB",
      "min_hours": 0,
      "tags": [{ "id": "portrait", "name": "Портрет", "sort_order": 1 }],
      "features": [{ "id": "parking", "name": "Парковка", "sort_order": 1 }],
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}

GET /places/:id

Ответ 200: Полная карточка PlaceCard / StudioCard

{
  "id": "uuid",
  "type": "studio",
  "title": "Студия 'Свет'",
  "description": "Профессиональная фотостудия...",
  "address": "Москва, ул. Ленина 10, офис 5",
  "lat": 55.75,
  "lng": 37.61,
  "cover_image": "https://cdn.../cover.jpg",
  "access_info": "Метро 'Площадь Революции', 5 мин пешком. Парковка во дворе.",
  "tags": [{ "id": "portrait", "name": "Портрет" }],
  "features": [{ "id": "flash", "name": "Импульсные источники" }],
  "hourly_rate": 1500,
  "currency": "RUB",
  "min_hours": 2,
  "rating": 4.8,
  "reviews_count": 24,
  "status": "published",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-20T14:22:00Z"
}

POST /places

Роли: customer (type=place), landlord (type=studio)

// Запрос
{
  "type": "studio",
  "title": "Новая студия",
  "description": "Описание...",
  "address": "Москва, ул. Новая 5",
  "lat": 55.76,
  "lng": 37.62,
  "access_info": "Как добраться...",
  "tags": ["portrait", "fashion"],
  "features": ["flash", "cyclorama"],
  "hourly_rate": 2000,
  "currency": "RUB",
  "min_hours": 3
}

// Ответ 201 — полный объект Place в статусе pending_moderation

PATCH /places/:id

Роли: Владелец, moderator, superadmin

  • Редактирование владельцем переводит в статус pending_moderation

DELETE /places/:id

Роли: Владелец (свои места), moderator, superadmin

  • Проверка: owner_id == текущий пользователь || moderator || superadmin
  • Мягкое удаление (deleted_at)

POST /places/:id/moderate

Роли: moderator, superadmin

// Запрос
{ "action": "approve", "comment": "Всё хорошо" }
// action: approve | reject | request_changes

Эндпоинты услуг исполнителей (Services)

GET /services

Параметры: tags, min_rating, price_min, price_max, limit Ответ 200: { "data": [ Service ] }

GET /services/:id

Ответ 200: Полный объект Service (с тегами)

POST /services

Роль: executor

{
  "title": "Портретная съемка в студии",
  "description": "Профессиональная портретная съемка...",
  "price": 5000,
  "currency": "RUB",
  "duration_minutes": 120,
  "tags": ["portrait", "studio"]
}
  • Ответ 201: полный объект Service со статусом published

PATCH /services/:id

Роль: Владелец (свои услуги), moderator, superadmin

  • Проверка: executor_id == текущий пользователь || moderator || superadmin

DELETE /services/:id

Роль: Владелец (свои услуги), moderator, superadmin

  • Проверка: executor_id == текущий пользователь || moderator || superadmin

Эндпоинты пользователей (Users)

GET /users/:id

Публичный профиль

PATCH /users/me

Обновление своего профиля

{
  "name": "Новое имя",
  "phone": "+79991234567",
  "bio": "Фотограф с 10-летним стажем",
  "avatar_url": "https://cdn.../avatar.jpg",
  "country": "RU"
}

Административные эндпоинты

GET /admin/users

Роли: moderator, superadmin Параметры: role, status, limit

PATCH /admin/users/:id

Роли: superadmin (все), moderator (кроме superadmin/moderator)

{ "role": "customer", "status": "active" }
// status: active | banned | pending_verification

Модерация

Бронирования (Bookings)

POST /bookings

Роль: Любой авторизованный пользователь

{
  "place_id": "uuid",
  "start_time": "2024-02-01T10:00:00Z",
  "end_time": "2024-02-01T14:00:00Z",
  "comment": "Нужна циклорама"
}
  • Проверка доступности слота
  • Расчёт цены на основе hourly_rate

GET /bookings/me

Список своих бронирований

PATCH /bookings/:id/cancel

Роль: Владелец брони

  • Проверка: user_id == текущий пользователь

Отзывы (Reviews)

GET /reviews

// Параметры запроса
// ?target_type=place&target_id=uuid

// Ответ 200
{
  "data": [
    {
      "id": "uuid",
      "user_id": "uuid",
      "target_type": "place",
      "target_id": "uuid",
      "rating": 5,
      "text": "Отличное место",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z",
      "user": { "name": "Иван", "avatar_url": "https://cdn.../avatar.jpg" }
    }
  ]
}

POST /reviews

Роль: Любой авторизованный пользователь

{
  "target_type": "place",
  "target_id": "uuid",
  "rating": 5,
  "text": "Отличное место"
}
  • rating: 1-5
  • text: опционально
  • Ответ 201: полный объект Review
  • Ограничение: один отзыв на пользователя на цель (409 Conflict при повторе)

WebSocket — точки посетителей

GET /ws/visitors

  • WebSocket-соединение для получения точек других посетителей на карте
  • Клиент отправляет { "user_id": "...", "lat": 55.75, "lng": 37.61 }
  • Сервер рассылает всем подключённым клиентам список всех активных точек:

    { "visitors": [{ "user_id": "...", "lat": 55.75, "lng": 37.61 }] }
    

Теги и характеристики (справочные данные)

GET /tags

Ответ: Список тегов стилей съёмки

[{ "id": "portrait", "name": "Портрет", "category": "style" }, ...]

GET /features

Ответ: Список характеристик мест

[{ "id": "flash", "name": "Импульсный свет", "category": "equipment" }, ...]

Загрузка файлов

POST /upload/presigned-url

// Запрос
{ "content_type": "image/jpeg", "max_size_mb": 10 }
// Ответ
{ "upload_url": "https://s3...", "file_url": "https://cdn...", "fields": { "key": "...", "policy": "...", "signature": "..." } }
  • Прямая загрузка в S3/MinIO с фронтенда
  • Бэкенд возвращает политику presigned POST

Вебхуки

Планируется интеграция с CloudPayments (POST /webhooks/cloudpayments), но пока не реализована.


Лимиты запросов

  • Авторизация: 10 запросов/мин на IP
  • Чтение мест: 60 запросов/мин на пользователя
  • Запись мест: 10 запросов/мин на пользователя
  • Администрирование: 100 запросов/мин