Files
market-backOfficce/API.md
2026-02-20 11:01:15 +04:00

509 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API Documentation
Endpoint reference for the Marketplace Backoffice.
Base URL: `https://your-api-domain.com/api`
> 🇷🇺 Документация на русском языке: [API.ru.md](./API.ru.md)
---
## Projects
### Get All Projects
```
GET /api/projects
Response 200:
[
{
"id": "dexar",
"name": "dexar",
"displayName": "Dexar Marketplace",
"active": true,
"logoUrl": "https://..."
}
]
```
---
## Categories
### Get Categories for Project
```
GET /api/projects/:projectId/categories
Response 200:
[
{
"id": "cat1",
"name": "Electronics",
"visible": true,
"priority": 1,
"img": "https://...",
"projectId": "dexar",
"subcategories": [ ...Subcategory[] ]
}
]
```
### Get Single Category
```
GET /api/categories/:categoryId
Response 200:
{
"id": "cat1",
"name": "Electronics",
"visible": true,
"priority": 1,
"img": "https://...",
"projectId": "dexar",
"subcategories": [ ...Subcategory[] ]
}
```
### Create Category
```
POST /api/projects/:projectId/categories
Body:
{
"name": "New Category", // required
"visible": true,
"priority": 10,
"img": "https://..."
}
Response 201: (created category object)
```
### Update Category
```
PATCH /api/categories/:categoryId
Body: (any subset of fields)
{
"name": "Updated Name",
"visible": false,
"priority": 5
}
Response 200: (updated category object)
```
### Delete Category
```
DELETE /api/categories/:categoryId
Response 204 No Content
Note: Cascades and deletes all subcategories and items under this category
```
---
## Subcategories
Subcategories are **recursive** - they can be nested under a root `Category` or under
another `Subcategory`. The nesting depth is unlimited, with one constraint:
**a subcategory that already contains items cannot have child subcategories** (and vice versa).
### Subcategory Object
```json
{
"id": "sub1",
"name": "Smartphones",
"visible": true,
"priority": 1,
"img": "https://...",
"categoryId": "cat1", // always the ROOT category ID
"parentId": "cat1", // direct parent ID (category OR subcategory)
"itemCount": 15,
"hasItems": true,
"subcategories": [] // nested children (empty when hasItems = true)
}
```
> `categoryId` is always the **root-level category** this subtree belongs to.
> `parentId` is the **direct parent** - it can be either a category ID or a subcategory ID.
---
### Get Subcategories Under a Category
```
GET /api/categories/:categoryId/subcategories
Response 200: Subcategory[] (nested subcategories populated recursively)
```
### Get Single Subcategory
```
GET /api/subcategories/:subcategoryId
Response 200: Subcategory object (with nested subcategories if any)
```
### Create Subcategory Under a Category (level 1)
```
POST /api/categories/:categoryId/subcategories
Body:
{
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
"name": "Smartphones", // required
"visible": true,
"priority": 10
}
Response 201: (created subcategory object)
Error 400: if category does not exist
```
### Create Subcategory Under a Parent Subcategory (level 2+, nested)
```
POST /api/subcategories/:parentSubcategoryId/subcategories
Body:
{
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
"name": "Apple", // required
"visible": true,
"priority": 10
}
Response 201: (created subcategory object)
Error 400: if parent subcategory has items (hasItems = true)
Error 404: if parent subcategory does not exist
```
### Update Subcategory
```
PATCH /api/subcategories/:subcategoryId
Body: (any subset of fields)
{
"id": "new-slug", // ID is editable - used for marketplace URL routing
"name": "Updated Name",
"visible": false,
"priority": 3
}
Response 200: (updated subcategory object)
```
### Delete Subcategory
```
DELETE /api/subcategories/:subcategoryId
Response 204 No Content
Note: Cascades and deletes all nested subcategories and items under this subcategory
```
---
## Items
Items always belong to the **deepest subcategory** in the hierarchy (a leaf node).
A subcategory with at least one item has `hasItems: true` and cannot have child subcategories.
### Get Items (Paginated)
```
GET /api/subcategories/:subcategoryId/items
Query Params:
page number (default: 1)
limit number (default: 20)
search string optional - filters by name (case-insensitive)
visible boolean optional - filter by visibility
tags string optional - comma-separated tag list
Response 200:
{
"items": [
{
"id": "item1",
"name": "iPhone 15 Pro",
"visible": true,
"priority": 1,
"quantity": 50,
"price": 1299,
"discount": 10,
"currency": "USD",
"imgs": ["https://...", "https://..."],
"tags": ["new", "featured"],
"badges": ["new", "exclusive"],
"simpleDescription": "Latest iPhone...",
"description": [
{ "key": "Color", "value": "Black" },
{ "key": "Storage", "value": "256GB" }
],
"subcategoryId": "sub1",
"translations": {
"ru": {
"name": "iPhone 15 Про",
"simpleDescription": "Последний iPhone...",
"description": [
{ "key": "Цвет", "value": "Чёрный" },
{ "key": "Память", "value": "256 ГБ" }
]
}
},
"comments": [
{
"id": "c1",
"text": "Great product!",
"author": "John Doe",
"stars": 5,
"createdAt": "2024-01-10T10:30:00Z"
}
]
}
],
"total": 150,
"page": 1,
"limit": 20,
"hasMore": true
}
```
### Get Single Item
```
GET /api/items/:itemId
Response 200: (full item object)
```
### Create Item
```
POST /api/subcategories/:subcategoryId/items
Body:
{
"name": "New Product", // required
"visible": true,
"priority": 10,
"quantity": 100,
"price": 999,
"discount": 0, // 0100 (percentage off price)
"currency": "USD", // USD | EUR | RUB | GBP | UAH
"imgs": ["https://..."],
"tags": ["new"],
"badges": ["new", "exclusive"], // optional - predefined or custom badge labels
"simpleDescription": "Short description",
"description": [
{ "key": "Size", "value": "Large" }
],
"translations": { // optional - localized content for marketplace
"ru": {
"name": "Новый товар",
"simpleDescription": "Краткое описание",
"description": [
{ "key": "Размер", "value": "Большой" }
]
}
}
}
Response 201: (created item object)
Side effect: Sets hasItems = true on the subcategory.
The subcategory can no longer receive child subcategories.
```
### Update Item
```
PATCH /api/items/:itemId
Body: (any subset of fields)
{
"name": "Updated Name",
"price": 899,
"discount": 15,
"quantity": 80,
"visible": false
}
Response 200: (updated item object)
```
**Updating images - always send the full array:**
```
PATCH /api/items/:itemId
Body:
{
"imgs": ["https://new1.jpg", "https://new2.jpg"]
}
Response 200: (updated item object)
```
### Delete Item
```
DELETE /api/items/:itemId
Response 204 No Content
Side effect: If no items remain in the subcategory, sets hasItems = false.
The subcategory can then receive child subcategories again.
```
### Bulk Update Items
```
PATCH /api/items/bulk
Body:
{
"itemIds": ["item1", "item2", "item3"],
"data": {
"visible": true
}
}
Response 204 No Content
```
---
## Upload
### Upload Image
```
POST /api/upload
Body: multipart/form-data
image: File
Response 201:
{
"url": "https://cdn.example.com/uploads/abc123.jpg"
}
```
---
## Notes
- All responses are JSON.
- Use `PATCH` for partial updates - send only the fields you want to change.
- `priority`: lower number = appears first in the list.
- `currency` supported values: `USD`, `EUR`, `RUB`, `GBP`, `UAH`.
- `badges`: optional string array. Predefined values with UI colors: `new`, `sale`, `exclusive`, `hot`, `limited`, `bestseller`, `featured`. Custom strings are also allowed.
- `imgs`: always send the **complete** array on update, not individual images.
- `discount`: integer `0``100` representing a percentage discount. `0` means no discount. The discounted price is calculated as `price * (1 - discount / 100)`.
- `description`: array of `{ key, value }` pairs - free-form attributes per item.
- `translations`: optional object keyed by language code (`"ru"`, `"en"`, etc.) — each value may contain `name`, `simpleDescription`, `description[]`. The marketplace frontend should use these when rendering in the corresponding language, falling back to the default fields if a translation is absent.
- Auto-save from the backoffice fires `PATCH` with a single field every ~500 ms.
---
## Business Rules
### Nested Subcategories
The hierarchy works like this:
```
Category (e.g. Electronics)
Subcategory L1 (e.g. Kitchen) <- can add more children OR items, not both
Subcategory L2 (e.g. Big Kitchen) <- same rule
Subcategory L3 (e.g. Ovens) <- if this has items, it is a leaf
Items...
```
Rules:
- A category can always receive new subcategories (categories never hold items directly).
- A subcategory that **has items** (`hasItems: true`) **cannot** receive child subcategories.
- `POST /api/subcategories/:id/subcategories` on a node with `hasItems: true` -> `400 Bad Request`.
- A subcategory that **has children** cannot have items added to it (items only go to leaf nodes).
- When the **first item** is created in a subcategory -> `hasItems` becomes `true`.
- When the **last item** is deleted -> `hasItems` becomes `false`; child subcategories can be added again.
### URL Structure (Marketplace Frontend)
Subcategory and item `id` fields are used directly in the marketplace URL path:
```
/{categoryId}/{sub1Id}/{sub2Id}/.../{itemId}
Examples:
/electronics/smartphones/iphone-15
/electronics/smartphones/apple/iphone-15-pro
/furniture/living-room/sofas/corner-sofa-modelo
```
The `id` field on subcategories is editable via `PATCH` to allow renaming slugs.
### Comments
- `stars` is optional, integer 1-5.
- `author` is optional (anonymous comments allowed).
- `createdAt` is ISO 8601 string.
### Cascade Deletes
| Delete target | Also deletes |
|---|---|
| Category | all subcategories (recursive) and their items |
| Subcategory | all nested subcategories (recursive) and their items |
| Item | nothing else |
---
## Internationalization (i18n)
The API supports localized content for items and categories via the `translations` field.
### `translations` object structure
Any entity that supports translations accepts the same nested shape:
```json
{
"translations": {
"ru": {
"name": "Название",
"simpleDescription": "Краткое описание",
"description": [
{ "key": "Цвет", "value": "Чёрный" }
]
}
}
}
```
- Keys are ISO 639-1 language codes (`"ru"`, `"en"`, etc.).
- All sub-fields are optional — omit what you don't need.
- Currently supported in the backoffice: `ru` (Russian).
### Requesting a Specific Language
Pass `?lang=ru` to any GET endpoint. The backend will:
1. Return the default top-level fields as-is.
2. Merge the matching `translations[lang]` values into the response, overwriting the default fields.
3. Fall back gracefully to the default language if no translation exists for the requested lang.
```
GET /api/items/:itemId?lang=ru
GET /api/subcategories/:subcategoryId/items?lang=ru
```
Alternatively, you can use the `Accept-Language` header:
```
Accept-Language: ru
```
### Fallback Behaviour
| Scenario | Returned value |
|---|---|
| Translation exists for requested lang | Translated value |
| Translation missing for requested lang | Default (base) field value |
| `lang` param omitted | Default (base) field value |
> 🇷🇺 See [API.ru.md](./API.ru.md) for full documentation in Russian.