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.
|
|||
|
|
|
|||
|
|
### Каскадное удаление
|
|||
|
|
|
|||
|
|
| Удаляемый объект | Также удаляется |
|
|||
|
|
|---|---|
|
|||
|
|
| Категория | все подкатегории (рекурсивно) и их товары |
|
|||
|
|
| Подкатегория | все вложенные подкатегории (рекурсивно) и их товары |
|
|||
|
|
| Товар | ничего больше |
|