504 lines
16 KiB
Markdown
504 lines
16 KiB
Markdown
# Документация API
|
||
|
||
Справочник эндпоинтов для бэкофиса маркетплейса.
|
||
Базовый URL: `https://your-api-domain.com/api`
|
||
|
||
> 🇬🇧 English documentation: [API.md](./API.md)
|
||
|
||
---
|
||
|
||
## Проекты
|
||
|
||
### Получить все проекты
|
||
```
|
||
GET /api/projects
|
||
|
||
Ответ 200:
|
||
[
|
||
{
|
||
"id": "dexar",
|
||
"name": "dexar",
|
||
"displayName": "Dexar Marketplace",
|
||
"active": true,
|
||
"logoUrl": "https://..."
|
||
}
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## Категории
|
||
|
||
### Получить категории проекта
|
||
```
|
||
GET /api/projects/:projectId/categories
|
||
|
||
Ответ 200:
|
||
[
|
||
{
|
||
"id": "cat1",
|
||
"name": "Электроника",
|
||
"visible": true,
|
||
"priority": 1,
|
||
"img": "https://...",
|
||
"projectId": "dexar",
|
||
"subcategories": [ ...Subcategory[] ],
|
||
"translations": {
|
||
"ru": { "name": "Электроника" }
|
||
}
|
||
}
|
||
]
|
||
```
|
||
|
||
### Получить категорию по ID
|
||
```
|
||
GET /api/categories/:categoryId
|
||
|
||
Ответ 200: (объект категории с вложенными подкатегориями)
|
||
```
|
||
|
||
### Создать категорию
|
||
```
|
||
POST /api/projects/:projectId/categories
|
||
|
||
Тело запроса:
|
||
{
|
||
"name": "Новая категория", // обязательно
|
||
"visible": true,
|
||
"priority": 10,
|
||
"img": "https://...",
|
||
"translations": { // опционально
|
||
"ru": { "name": "Новая категория" }
|
||
}
|
||
}
|
||
|
||
Ответ 201: (созданный объект категории)
|
||
```
|
||
|
||
### Обновить категорию
|
||
```
|
||
PATCH /api/categories/:categoryId
|
||
|
||
Тело запроса: (любое подмножество полей)
|
||
{
|
||
"name": "Обновлённое название",
|
||
"visible": false,
|
||
"priority": 5,
|
||
"translations": {
|
||
"ru": { "name": "Обновлённое название" }
|
||
}
|
||
}
|
||
|
||
Ответ 200: (обновлённый объект категории)
|
||
```
|
||
|
||
### Удалить категорию
|
||
```
|
||
DELETE /api/categories/:categoryId
|
||
|
||
Ответ 204 No Content
|
||
|
||
Примечание: каскадно удаляет все подкатегории и товары внутри
|
||
```
|
||
|
||
---
|
||
|
||
## Подкатегории
|
||
|
||
Подкатегории **рекурсивны** — вложенность неограничена. Единственное ограничение:
|
||
**подкатегория с товарами не может иметь дочерних подкатегорий** (и наоборот).
|
||
|
||
### Объект подкатегории
|
||
```json
|
||
{
|
||
"id": "sub1",
|
||
"name": "Смартфоны",
|
||
"visible": true,
|
||
"priority": 1,
|
||
"img": "https://...",
|
||
"categoryId": "cat1", // всегда ID корневой категории
|
||
"parentId": "cat1", // ID прямого родителя (категория или подкатегория)
|
||
"itemCount": 15,
|
||
"hasItems": true,
|
||
"subcategories": [], // дочерние подкатегории (пусто, если hasItems = true)
|
||
"translations": {
|
||
"ru": { "name": "Смартфоны" }
|
||
}
|
||
}
|
||
```
|
||
|
||
> `categoryId` — всегда ID **корневой категории** этого поддерева.
|
||
> `parentId` — ID **прямого родителя**: может быть ID категории или подкатегории.
|
||
|
||
---
|
||
|
||
### Получить подкатегории категории
|
||
```
|
||
GET /api/categories/:categoryId/subcategories
|
||
|
||
Ответ 200: Subcategory[] (вложенные подкатегории рекурсивно заполнены)
|
||
```
|
||
|
||
### Получить подкатегорию по ID
|
||
```
|
||
GET /api/subcategories/:subcategoryId
|
||
|
||
Ответ 200: объект подкатегории (с вложенными подкатегориями при наличии)
|
||
```
|
||
|
||
### Создать подкатегорию в категории (уровень 1)
|
||
```
|
||
POST /api/categories/:categoryId/subcategories
|
||
|
||
Тело запроса:
|
||
{
|
||
"id": "custom-id", // опционально, генерируется автоматически (используется в URL)
|
||
"name": "Смартфоны", // обязательно
|
||
"visible": true,
|
||
"priority": 10,
|
||
"translations": {
|
||
"ru": { "name": "Смартфоны" }
|
||
}
|
||
}
|
||
|
||
Ответ 201: (созданный объект подкатегории)
|
||
Ошибка 400: если категория не существует
|
||
```
|
||
|
||
### Создать подкатегорию в подкатегории (уровень 2+, вложенная)
|
||
```
|
||
POST /api/subcategories/:parentSubcategoryId/subcategories
|
||
|
||
Тело запроса:
|
||
{
|
||
"id": "custom-id", // опционально
|
||
"name": "Apple", // обязательно
|
||
"visible": true,
|
||
"priority": 10
|
||
}
|
||
|
||
Ответ 201: (созданный объект подкатегории)
|
||
Ошибка 400: если родительская подкатегория содержит товары (hasItems = true)
|
||
Ошибка 404: если родительская подкатегория не найдена
|
||
```
|
||
|
||
### Обновить подкатегорию
|
||
```
|
||
PATCH /api/subcategories/:subcategoryId
|
||
|
||
Тело запроса: (любое подмножество полей)
|
||
{
|
||
"id": "new-slug", // ID редактируется — используется в URL маркетплейса
|
||
"name": "Новое название",
|
||
"visible": false,
|
||
"priority": 3,
|
||
"translations": {
|
||
"ru": { "name": "Новое название" }
|
||
}
|
||
}
|
||
|
||
Ответ 200: (обновлённый объект подкатегории)
|
||
```
|
||
|
||
### Удалить подкатегорию
|
||
```
|
||
DELETE /api/subcategories/:subcategoryId
|
||
|
||
Ответ 204 No Content
|
||
|
||
Примечание: каскадно удаляет все вложенные подкатегории и товары
|
||
```
|
||
|
||
---
|
||
|
||
## Товары
|
||
|
||
Товары всегда принадлежат **самой глубокой подкатегории** в иерархии (листовой узел).
|
||
Подкатегория с хотя бы одним товаром имеет `hasItems: true` и не может принимать дочерние подкатегории.
|
||
|
||
### Получить товары (с пагинацией)
|
||
```
|
||
GET /api/subcategories/:subcategoryId/items
|
||
|
||
Query-параметры:
|
||
page number (по умолчанию: 1)
|
||
limit number (по умолчанию: 20)
|
||
search string опционально — фильтр по названию (без учёта регистра)
|
||
visible boolean опционально — фильтр по видимости
|
||
tags string опционально — теги через запятую
|
||
lang string опционально — код языка (en | ru); влияет на поля names/descriptions в ответе
|
||
|
||
Ответ 200:
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "item1",
|
||
"name": "iPhone 15 Pro",
|
||
"visible": true,
|
||
"priority": 1,
|
||
"quantity": 50,
|
||
"price": 1299,
|
||
"currency": "USD",
|
||
"imgs": ["https://...", "https://..."],
|
||
"tags": ["new", "featured"],
|
||
"badges": ["new", "exclusive"],
|
||
"simpleDescription": "Последний iPhone...",
|
||
"description": [
|
||
{ "key": "Цвет", "value": "Чёрный" },
|
||
{ "key": "Память", "value": "256 ГБ" }
|
||
],
|
||
"subcategoryId": "sub1",
|
||
"translations": {
|
||
"ru": {
|
||
"name": "iPhone 15 Pro",
|
||
"simpleDescription": "Последний iPhone с корпусом из титана",
|
||
"description": [
|
||
{ "key": "Цвет", "value": "Чёрный" },
|
||
{ "key": "Память", "value": "256 ГБ" }
|
||
]
|
||
}
|
||
},
|
||
"comments": [
|
||
{
|
||
"id": "c1",
|
||
"text": "Отличный товар!",
|
||
"author": "Иван",
|
||
"stars": 5,
|
||
"createdAt": "2024-01-10T10:30:00Z"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"total": 150,
|
||
"page": 1,
|
||
"limit": 20,
|
||
"hasMore": true
|
||
}
|
||
```
|
||
|
||
### Получить товар по ID
|
||
```
|
||
GET /api/items/:itemId
|
||
|
||
Query-параметры:
|
||
lang string опционально (en | ru)
|
||
|
||
Ответ 200: (полный объект товара)
|
||
```
|
||
|
||
### Создать товар
|
||
```
|
||
POST /api/subcategories/:subcategoryId/items
|
||
|
||
Тело запроса:
|
||
{
|
||
"name": "Новый товар", // обязательно
|
||
"visible": true,
|
||
"priority": 10,
|
||
"quantity": 100,
|
||
"price": 999,
|
||
"currency": "USD", // USD | EUR | RUB | GBP | UAH
|
||
"imgs": ["https://..."],
|
||
"tags": ["new"],
|
||
"badges": ["new", "exclusive"], // опционально — стандартные или свои метки
|
||
"simpleDescription": "Краткое описание",
|
||
"description": [
|
||
{ "key": "Размер", "value": "Большой" }
|
||
],
|
||
"translations": { // опционально
|
||
"ru": {
|
||
"name": "Новый товар",
|
||
"simpleDescription": "Краткое описание",
|
||
"description": [
|
||
{ "key": "Размер", "value": "Большой" }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
Ответ 201: (созданный объект товара)
|
||
|
||
Побочный эффект: устанавливает hasItems = true на подкатегории.
|
||
Подкатегория больше не может принимать дочерние подкатегории.
|
||
```
|
||
|
||
### Обновить товар
|
||
```
|
||
PATCH /api/items/:itemId
|
||
|
||
Тело запроса: (любое подмножество полей)
|
||
{
|
||
"name": "Новое название",
|
||
"price": 899,
|
||
"quantity": 80,
|
||
"visible": false,
|
||
"translations": {
|
||
"ru": { "name": "Новое название" }
|
||
}
|
||
}
|
||
|
||
Ответ 200: (обновлённый объект товара)
|
||
```
|
||
|
||
**Обновление изображений — всегда передавай полный массив:**
|
||
```
|
||
PATCH /api/items/:itemId
|
||
|
||
Тело запроса:
|
||
{
|
||
"imgs": ["https://new1.jpg", "https://new2.jpg"]
|
||
}
|
||
|
||
Ответ 200: (обновлённый объект товара)
|
||
```
|
||
|
||
### Удалить товар
|
||
```
|
||
DELETE /api/items/:itemId
|
||
|
||
Ответ 204 No Content
|
||
|
||
Побочный эффект: если в подкатегории не осталось товаров — hasItems становится false.
|
||
Подкатегория снова может принимать дочерние подкатегории.
|
||
```
|
||
|
||
### Массовое обновление товаров
|
||
```
|
||
PATCH /api/items/bulk
|
||
|
||
Тело запроса:
|
||
{
|
||
"itemIds": ["item1", "item2", "item3"],
|
||
"data": {
|
||
"visible": true
|
||
}
|
||
}
|
||
|
||
Ответ 204 No Content
|
||
```
|
||
|
||
---
|
||
|
||
## Загрузка файлов
|
||
|
||
### Загрузить изображение
|
||
```
|
||
POST /api/upload
|
||
|
||
Тело запроса: multipart/form-data
|
||
image: File
|
||
|
||
Ответ 201:
|
||
{
|
||
"url": "https://cdn.example.com/uploads/abc123.jpg"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Мультиязычность (i18n)
|
||
|
||
Поля `name`, `simpleDescription` и `description` поддерживают переводы через объект `translations`.
|
||
|
||
### Структура объекта переводов
|
||
```json
|
||
{
|
||
"translations": {
|
||
"ru": {
|
||
"name": "Название на русском",
|
||
"simpleDescription": "Описание на русском",
|
||
"description": [
|
||
{ "key": "Ключ", "value": "Значение" }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> Основные поля (`name`, `simpleDescription`, `description`) хранятся на **английском** (по умолчанию).
|
||
> Переводы хранятся в `translations[langCode].*`.
|
||
|
||
### Запрос конкретного языка
|
||
Передай параметр `?lang=ru` в GET-запросах. Бэкенд должен:
|
||
1. Вернуть `translations.ru.*` там, где перевод заполнен.
|
||
2. Откатиться к основному полю (английскому), если перевод отсутствует.
|
||
|
||
```
|
||
GET /api/items/:itemId?lang=ru
|
||
GET /api/subcategories/:subcategoryId/items?lang=ru&page=1
|
||
```
|
||
|
||
Альтернативно — заголовок `Accept-Language: ru`.
|
||
|
||
### Поддерживаемые языки
|
||
| Код | Язык |
|
||
|-----|----------|
|
||
| en | Английский (по умолчанию) |
|
||
| ru | Русский |
|
||
|
||
---
|
||
|
||
## Примечания
|
||
|
||
- Все ответы — JSON.
|
||
- Используй `PATCH` для частичного обновления — передавай только изменяемые поля.
|
||
- `priority`: меньшее число — выше в списке.
|
||
- Поддерживаемые значения `currency`: `USD`, `EUR`, `RUB`, `GBP`, `UAH`.
|
||
- `badges`: необязательный массив строк. Стандартные значения с цветами в интерфейсе: `new`, `sale`, `exclusive`, `hot`, `limited`, `bestseller`, `featured`. Свои строки тоже допустимы.
|
||
- `imgs`: при обновлении всегда передавай **полный** массив, не отдельные изображения.
|
||
- `description`: массив пар `{ key, value }` — свободные атрибуты товара.
|
||
- Автосохранение из бэкофиса отправляет `PATCH` с одним полем каждые ~500 мс.
|
||
|
||
---
|
||
|
||
## Бизнес-правила
|
||
|
||
### Вложенные подкатегории
|
||
|
||
Иерархия выглядит так:
|
||
|
||
```
|
||
Категория (напр. Электроника)
|
||
Подкатегория L1 (напр. Кухня) <- можно добавлять детей ИЛИ товары, не оба
|
||
Подкатегория L2 (напр. Большая кухня) <- то же правило
|
||
Подкатегория L3 (напр. Духовки) <- если есть товары — это листовой узел
|
||
Товары...
|
||
```
|
||
|
||
Правила:
|
||
- Категория всегда может принимать новые подкатегории (категории никогда не хранят товары напрямую).
|
||
- Подкатегория с товарами (`hasItems: true`) **не может** принимать дочерние подкатегории.
|
||
- `POST /api/subcategories/:id/subcategories` на узел с `hasItems: true` → `400 Bad Request`.
|
||
- Подкатегория с дочерними подкатегориями не может принимать товары (товары только в листовых узлах).
|
||
- При создании **первого товара** в подкатегории → `hasItems` становится `true`.
|
||
- При удалении **последнего товара** → `hasItems` становится `false`; дочерние подкатегории снова можно добавлять.
|
||
|
||
### Структура URL (фронтенд маркетплейса)
|
||
|
||
Поля `id` подкатегорий и товаров используются напрямую в URL маркетплейса:
|
||
|
||
```
|
||
/{categoryId}/{sub1Id}/{sub2Id}/.../{itemId}
|
||
|
||
Примеры:
|
||
/electronics/smartphones/iphone-15
|
||
/electronics/smartphones/apple/iphone-15-pro
|
||
/furniture/living-room/sofas/corner-sofa-modelo
|
||
```
|
||
|
||
Поле `id` подкатегорий редактируется через `PATCH` для переименования слагов.
|
||
|
||
### Комментарии
|
||
|
||
- `stars` — опционально, целое число от 1 до 5.
|
||
- `author` — опционально (анонимные комментарии допустимы).
|
||
- `createdAt` — строка в формате ISO 8601.
|
||
|
||
### Каскадное удаление
|
||
|
||
| Удаляемый объект | Также удаляется |
|
||
|---|---|
|
||
| Категория | все подкатегории (рекурсивно) и их товары |
|
||
| Подкатегория | все вложенные подкатегории (рекурсивно) и их товары |
|
||
| Товар | ничего больше |
|