|
|
@@ -10,20 +10,15 @@
|
|
|
- **Refresh Token**: JWT, срок 30 дней, передаётся в заголовке `X-Refresh-Token`
|
|
|
- **Роли**: `superadmin`, `moderator`, `landlord`, `executor`, `customer`
|
|
|
|
|
|
-## Формат ошибок (RFC 7807)
|
|
|
+## Формат ошибок
|
|
|
```json
|
|
|
{
|
|
|
- "type": "https://api.photoplaces.ru/errors/validation-error",
|
|
|
- "title": "Ошибка валидации",
|
|
|
- "status": 422,
|
|
|
- "detail": "Некорректные входные данные",
|
|
|
- "instance": "/api/v1/places",
|
|
|
- "errors": [
|
|
|
- { "field": "title", "message": "Название обязательно" }
|
|
|
- ]
|
|
|
+ "error": "описание ошибки"
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+HTTP статусы: 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 422 (Validation Error), 500 (Internal Server Error)
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## Эндпоинты авторизации
|
|
|
@@ -81,12 +76,11 @@
|
|
|
### GET /places
|
|
|
**Параметры запроса:**
|
|
|
- `type`: `place` | `studio` (по умолчанию оба)
|
|
|
-- `tags`: ID тегов через запятую
|
|
|
+- `tags`: ID тегов (можно несколько, напр. `?tags=portrait&tags=nature`)
|
|
|
- `min_rating`: число 0-5
|
|
|
-- `features`: ID характеристик через запятую
|
|
|
+- `features`: ID характеристик (можно несколько)
|
|
|
- `price_min`, `price_max`: для студий
|
|
|
- `bounds`: `sw_lat,sw_lng,ne_lat,ne_lng` (область карты)
|
|
|
-- `cursor`: курсор пагинации
|
|
|
- `limit`: 1-100 (по умолчанию 20)
|
|
|
- `sort`: `rating` | `created_at` | `distance` (требует `user_lat,user_lng`)
|
|
|
|
|
|
@@ -97,19 +91,25 @@
|
|
|
{
|
|
|
"id": "uuid",
|
|
|
"type": "place",
|
|
|
+ "owner_id": "uuid",
|
|
|
"title": "Красивый парк",
|
|
|
- "cover_image": "https://cdn.../uuid.jpg",
|
|
|
- "coordinates": { "lat": 55.75, "lng": 37.61 },
|
|
|
+ "description": null,
|
|
|
"address": "Москва, Парковая 1",
|
|
|
- "rating": 4.5,
|
|
|
- "tags": ["nature", "architecture"],
|
|
|
- "features": ["parking", "public_transport"],
|
|
|
+ "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"
|
|
|
}
|
|
|
- ],
|
|
|
- "next_cursor": "eyJpZCI6InV1aWQifQ==",
|
|
|
- "has_more": true
|
|
|
+ ]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -122,28 +122,18 @@
|
|
|
"title": "Студия 'Свет'",
|
|
|
"description": "Профессиональная фотостудия...",
|
|
|
"address": "Москва, ул. Ленина 10, офис 5",
|
|
|
- "coordinates": { "lat": 55.75, "lng": 37.61 },
|
|
|
+ "lat": 55.75,
|
|
|
+ "lng": 37.61,
|
|
|
"cover_image": "https://cdn.../cover.jpg",
|
|
|
- "images": ["https://cdn.../1.jpg", "https://cdn.../2.jpg"],
|
|
|
"access_info": "Метро 'Площадь Революции', 5 мин пешком. Парковка во дворе.",
|
|
|
- "tags": ["portrait", "fashion", "product"],
|
|
|
- "features": [
|
|
|
- { "id": "flash", "name": "Импульсные источники" },
|
|
|
- { "id": "continuous", "name": "Постоянный свет" },
|
|
|
- { "id": "cyclorama", "name": "Циклорама" }
|
|
|
- ],
|
|
|
- "pricing": {
|
|
|
- "hourly_rate": 1500,
|
|
|
- "currency": "RUB",
|
|
|
- "min_hours": 2
|
|
|
- },
|
|
|
+ "tags": [{ "id": "portrait", "name": "Портрет" }],
|
|
|
+ "features": [{ "id": "flash", "name": "Импульсные источники" }],
|
|
|
+ "hourly_rate": 1500,
|
|
|
+ "currency": "RUB",
|
|
|
+ "min_hours": 2,
|
|
|
"booking_url": "https://booking.photoplaces.ru/studio/uuid",
|
|
|
- "executor_services": [
|
|
|
- { "id": "uuid", "title": "Портретная съемка", "price": 5000, "executor_name": "Иван Фотограф" }
|
|
|
- ],
|
|
|
"rating": 4.8,
|
|
|
"reviews_count": 24,
|
|
|
- "owner": { "id": "uuid", "name": "Студия 'Свет'", "avatar": "..." },
|
|
|
"status": "published",
|
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
|
"updated_at": "2024-01-20T14:22:00Z"
|
|
|
@@ -159,15 +149,17 @@
|
|
|
"title": "Новая студия",
|
|
|
"description": "Описание...",
|
|
|
"address": "Москва, ул. Новая 5",
|
|
|
- "coordinates": { "lat": 55.76, "lng": 37.62 },
|
|
|
+ "lat": 55.76,
|
|
|
+ "lng": 37.62,
|
|
|
"access_info": "Как добраться...",
|
|
|
"tags": ["portrait", "fashion"],
|
|
|
"features": ["flash", "cyclorama"],
|
|
|
- "pricing": { "hourly_rate": 2000, "currency": "RUB", "min_hours": 3 }
|
|
|
+ "hourly_rate": 2000,
|
|
|
+ "currency": "RUB",
|
|
|
+ "min_hours": 3
|
|
|
}
|
|
|
|
|
|
-// Ответ 201
|
|
|
-{ "id": "uuid", "status": "pending_moderation", "message": "Отправлено на проверку" }
|
|
|
+// Ответ 201 — полный объект Place в статусе pending_moderation
|
|
|
```
|
|
|
|
|
|
### PATCH /places/:id
|
|
|
@@ -192,10 +184,11 @@
|
|
|
## Эндпоинты услуг исполнителей (Services)
|
|
|
|
|
|
### GET /services
|
|
|
-**Параметры:** `tags`, `min_rating`, `price_min`, `price_max`, `cursor`, `limit`
|
|
|
+**Параметры:** `tags`, `min_rating`, `price_min`, `price_max`, `limit`
|
|
|
+**Ответ 200:** `{ "data": [ Service ] }`
|
|
|
|
|
|
### GET /services/:id
|
|
|
-**Ответ 200:** Полная карточка ServiceCard
|
|
|
+**Ответ 200:** Полный объект Service (с тегами)
|
|
|
|
|
|
### POST /services
|
|
|
**Роль:** `executor`
|
|
|
@@ -206,10 +199,10 @@
|
|
|
"price": 5000,
|
|
|
"currency": "RUB",
|
|
|
"duration_minutes": 120,
|
|
|
- "tags": ["portrait", "studio"],
|
|
|
- "portfolio_images": ["https://cdn.../1.jpg"]
|
|
|
+ "tags": ["portrait", "studio"]
|
|
|
}
|
|
|
```
|
|
|
+- Ответ 201: полный объект Service со статусом `published`
|
|
|
|
|
|
### PATCH /services/:id
|
|
|
**Роль:** Владелец (свои услуги), `moderator`, `superadmin`
|
|
|
@@ -238,19 +231,13 @@
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### GET /users/me/places
|
|
|
-**Свои места (все статусы)**
|
|
|
-
|
|
|
-### GET /users/me/services
|
|
|
-**Свои услуги (для исполнителей)**
|
|
|
-
|
|
|
---
|
|
|
|
|
|
## Административные эндпоинты
|
|
|
|
|
|
### GET /admin/users
|
|
|
**Роли:** `moderator`, `superadmin`
|
|
|
-**Параметры:** `role`, `status`, `cursor`, `limit`
|
|
|
+**Параметры:** `role`, `status`, `limit`
|
|
|
|
|
|
### PATCH /admin/users/:id
|
|
|
**Роли:** `superadmin` (все), `moderator` (кроме superadmin/moderator)
|
|
|
@@ -259,18 +246,10 @@
|
|
|
// status: active | banned | pending_verification
|
|
|
```
|
|
|
|
|
|
-### GET /admin/moderation/queue
|
|
|
-**Роли:** `moderator`, `superadmin`
|
|
|
-**Параметры:** `type`: place | studio | service, `status`: pending | rejected
|
|
|
-
|
|
|
-### POST /admin/moderation/:entityType/:id
|
|
|
-**Роли:** `moderator`, `superadmin`
|
|
|
-```json
|
|
|
-{ "action": "approve", "comment": "..." }
|
|
|
-```
|
|
|
-
|
|
|
---
|
|
|
|
|
|
+## Модерация
|
|
|
+
|
|
|
## Бронирования (Bookings)
|
|
|
|
|
|
### POST /bookings
|
|
|
@@ -293,8 +272,47 @@
|
|
|
**Роль:** Владелец брони
|
|
|
- Проверка: `user_id` == текущий пользователь
|
|
|
|
|
|
-### PATCH /bookings/:id/confirm
|
|
|
-**Роль:** `moderator`, `superadmin`
|
|
|
+---
|
|
|
+
|
|
|
+## Отзывы (Reviews)
|
|
|
+
|
|
|
+### GET /reviews
|
|
|
+```json
|
|
|
+// Параметры запроса
|
|
|
+// ?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
|
|
|
+**Роль:** Любой авторизованный пользователь
|
|
|
+```json
|
|
|
+{
|
|
|
+ "target_type": "place",
|
|
|
+ "target_id": "uuid",
|
|
|
+ "rating": 5,
|
|
|
+ "text": "Отличное место"
|
|
|
+}
|
|
|
+```
|
|
|
+- `rating`: 1-5
|
|
|
+- `text`: опционально
|
|
|
+- Ответ 201: полный объект Review
|
|
|
+- Ограничение: один отзыв на пользователя на цель (409 Conflict при повторе)
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -342,10 +360,7 @@
|
|
|
|
|
|
## Вебхуки
|
|
|
|
|
|
-### CloudPayments Webhook
|
|
|
-`POST /webhooks/cloudpayments`
|
|
|
-- Проверка подписи
|
|
|
-- Обновление статуса подписки/платежа
|
|
|
+Планируется интеграция с CloudPayments (`POST /webhooks/cloudpayments`), но пока не реализована.
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -356,9 +371,3 @@
|
|
|
- Администрирование: 100 запросов/мин
|
|
|
|
|
|
---
|
|
|
-
|
|
|
-## Пагинация
|
|
|
-Курсорная:
|
|
|
-- `cursor` = base64(JSON) ключа сортировки последнего элемента + ID
|
|
|
-- `limit` макс. 100
|
|
|
-- Ответ содержит `next_cursor` и `has_more`
|