updated api docs
This commit is contained in:
264
API.md
264
API.md
@@ -1,6 +1,9 @@
|
|||||||
# API Documentation #
|
# API Documentation
|
||||||
|
|
||||||
Simple endpoint reference for the Marketplace Backoffice.
|
Endpoint reference for the Marketplace Backoffice.
|
||||||
|
Base URL: `https://your-api-domain.com/api`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Projects
|
## Projects
|
||||||
|
|
||||||
@@ -8,7 +11,7 @@ Simple endpoint reference for the Marketplace Backoffice.
|
|||||||
```
|
```
|
||||||
GET /api/projects
|
GET /api/projects
|
||||||
|
|
||||||
Response:
|
Response 200:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "dexar",
|
"id": "dexar",
|
||||||
@@ -20,13 +23,15 @@ Response:
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Categories
|
## Categories
|
||||||
|
|
||||||
### Get Categories for Project
|
### Get Categories for Project
|
||||||
```
|
```
|
||||||
GET /api/projects/:projectId/categories
|
GET /api/projects/:projectId/categories
|
||||||
|
|
||||||
Response:
|
Response 200:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "cat1",
|
"id": "cat1",
|
||||||
@@ -35,7 +40,7 @@ Response:
|
|||||||
"priority": 1,
|
"priority": 1,
|
||||||
"img": "https://...",
|
"img": "https://...",
|
||||||
"projectId": "dexar",
|
"projectId": "dexar",
|
||||||
"subcategories": [...]
|
"subcategories": [ ...Subcategory[] ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -44,7 +49,7 @@ Response:
|
|||||||
```
|
```
|
||||||
GET /api/categories/:categoryId
|
GET /api/categories/:categoryId
|
||||||
|
|
||||||
Response:
|
Response 200:
|
||||||
{
|
{
|
||||||
"id": "cat1",
|
"id": "cat1",
|
||||||
"name": "Electronics",
|
"name": "Electronics",
|
||||||
@@ -52,7 +57,7 @@ Response:
|
|||||||
"priority": 1,
|
"priority": 1,
|
||||||
"img": "https://...",
|
"img": "https://...",
|
||||||
"projectId": "dexar",
|
"projectId": "dexar",
|
||||||
"subcategories": [...]
|
"subcategories": [ ...Subcategory[] ]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -62,123 +67,157 @@ POST /api/projects/:projectId/categories
|
|||||||
|
|
||||||
Body:
|
Body:
|
||||||
{
|
{
|
||||||
"name": "New Category",
|
"name": "New Category", // required
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"img": "https://..."
|
"img": "https://..."
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (created category object)
|
Response 201: (created category object)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Category
|
### Update Category
|
||||||
```
|
```
|
||||||
PATCH /api/categories/:categoryId
|
PATCH /api/categories/:categoryId
|
||||||
|
|
||||||
Body: (any field to update)
|
Body: (any subset of fields)
|
||||||
{
|
{
|
||||||
"name": "Updated Name",
|
"name": "Updated Name",
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"priority": 5
|
"priority": 5
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (updated category object)
|
Response 200: (updated category object)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Category
|
### Delete Category
|
||||||
```
|
```
|
||||||
DELETE /api/categories/:categoryId
|
DELETE /api/categories/:categoryId
|
||||||
|
|
||||||
Response: 204 No Content
|
Response 204 No Content
|
||||||
|
|
||||||
|
Note: Cascades and deletes all subcategories and items under this category
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Subcategories
|
## Subcategories
|
||||||
|
|
||||||
### Get 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
|
GET /api/categories/:categoryId/subcategories
|
||||||
|
|
||||||
Response:
|
Response 200: Subcategory[] (nested subcategories populated recursively)
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "sub1",
|
|
||||||
"name": "Smartphones",
|
|
||||||
"visible": true,
|
|
||||||
"priority": 1,
|
|
||||||
"img": "https://...",
|
|
||||||
"categoryId": "cat1",
|
|
||||||
"itemCount": 15,
|
|
||||||
"hasItems": true,
|
|
||||||
"subcategories": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Note:
|
|
||||||
- Subcategories can have nested subcategories (recursive)
|
|
||||||
- If hasItems is true, cannot create child subcategories
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Single Subcategory
|
### Get Single Subcategory
|
||||||
```
|
```
|
||||||
GET /api/subcategories/:subcategoryId
|
GET /api/subcategories/:subcategoryId
|
||||||
|
|
||||||
Response: (subcategory object with nested subcategories if any)
|
Response 200: Subcategory object (with nested subcategories if any)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create Subcategory
|
### Create Subcategory Under a Category (level 1)
|
||||||
```
|
```
|
||||||
POST /api/categories/:categoryId/subcategories
|
POST /api/categories/:categoryId/subcategories
|
||||||
|
|
||||||
Note: categoryId can be either a category ID or a parent subcategory ID for nested structure
|
|
||||||
|
|
||||||
Body:
|
Body:
|
||||||
{
|
{
|
||||||
"id": "custom-id", // Optional, auto-generated if not provided
|
"id": "custom-id", // optional, auto-generated if omitted (used in URL routing)
|
||||||
"name": "New Subcategory",
|
"name": "Smartphones", // required
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (created subcategory object)
|
Response 201: (created subcategory object)
|
||||||
|
Error 400: if category does not exist
|
||||||
|
```
|
||||||
|
|
||||||
Error: Returns 400 if parent subcategory already has items
|
### 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
|
### Update Subcategory
|
||||||
```
|
```
|
||||||
PATCH /api/subcategories/:subcategoryId
|
PATCH /api/subcategories/:subcategoryId
|
||||||
|
|
||||||
Body: (any field)
|
Body: (any subset of fields)
|
||||||
{
|
{
|
||||||
"id": "new-id", // ID is now editable (used for routing)
|
"id": "new-slug", // ID is editable - used for marketplace URL routing
|
||||||
"name": "Updated Name",
|
"name": "Updated Name",
|
||||||
"visible": false
|
"visible": false,
|
||||||
|
"priority": 3
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (updated subcategory object)
|
Response 200: (updated subcategory object)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Subcategory
|
### Delete Subcategory
|
||||||
```
|
```
|
||||||
DELETE /api/subcategories/:subcategoryId
|
DELETE /api/subcategories/:subcategoryId
|
||||||
|
|
||||||
Response: 204 No Content
|
Response 204 No Content
|
||||||
|
|
||||||
|
Note: Cascades and deletes all nested subcategories and items under this subcategory
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Items
|
## 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 Items (Paginated)
|
||||||
```
|
```
|
||||||
GET /api/subcategories/:subcategoryId/items?page=1&limit=20&search=phone&visible=true
|
GET /api/subcategories/:subcategoryId/items
|
||||||
|
|
||||||
Query Params:
|
Query Params:
|
||||||
- page: number (default: 1)
|
page number (default: 1)
|
||||||
- limit: number (default: 20)
|
limit number (default: 20)
|
||||||
- search: string (optional - filters by name)
|
search string optional - filters by name (case-insensitive)
|
||||||
- visible: boolean (optional - filters by visibility)
|
visible boolean optional - filter by visibility
|
||||||
- tags: string (optional - comma separated tags)
|
tags string optional - comma-separated tag list
|
||||||
|
|
||||||
Response:
|
Response 200:
|
||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
@@ -219,7 +258,7 @@ Response:
|
|||||||
```
|
```
|
||||||
GET /api/items/:itemId
|
GET /api/items/:itemId
|
||||||
|
|
||||||
Response: (item object with all fields)
|
Response 200: (full item object)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create Item
|
### Create Item
|
||||||
@@ -228,28 +267,31 @@ POST /api/subcategories/:subcategoryId/items
|
|||||||
|
|
||||||
Body:
|
Body:
|
||||||
{
|
{
|
||||||
"name": "New Product",
|
"name": "New Product", // required
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"quantity": 100,
|
"quantity": 100,
|
||||||
"price": 999,
|
"price": 999,
|
||||||
"currency": "USD",
|
"currency": "USD", // USD | EUR | RUB | GBP | UAH
|
||||||
"imgs": ["https://..."],
|
"imgs": ["https://..."],
|
||||||
"tags": ["new"],
|
"tags": ["new"],
|
||||||
"simpleDescription": "Product description",
|
"simpleDescription": "Short description",
|
||||||
"description": [
|
"description": [
|
||||||
{ "key": "Size", "value": "Large" }
|
{ "key": "Size", "value": "Large" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (created item object)
|
Response 201: (created item object)
|
||||||
|
|
||||||
|
Side effect: Sets hasItems = true on the subcategory.
|
||||||
|
The subcategory can no longer receive child subcategories.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Item
|
### Update Item
|
||||||
```
|
```
|
||||||
PATCH /api/items/:itemId
|
PATCH /api/items/:itemId
|
||||||
|
|
||||||
Body: (any field to update)
|
Body: (any subset of fields)
|
||||||
{
|
{
|
||||||
"name": "Updated Name",
|
"name": "Updated Name",
|
||||||
"price": 899,
|
"price": 899,
|
||||||
@@ -257,27 +299,29 @@ Body: (any field to update)
|
|||||||
"visible": false
|
"visible": false
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: (updated item object)
|
Response 200: (updated item object)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example: Update only images**
|
**Updating images - always send the full array:**
|
||||||
```
|
```
|
||||||
PATCH /api/items/item123
|
PATCH /api/items/:itemId
|
||||||
|
|
||||||
Body:
|
Body:
|
||||||
{
|
{
|
||||||
"imgs": ["https://new-image1.jpg", "https://new-image2.jpg"]
|
"imgs": ["https://new1.jpg", "https://new2.jpg"]
|
||||||
}
|
}
|
||||||
|
|
||||||
Note: Send the complete array of images (not just changed ones)
|
Response 200: (updated item object)
|
||||||
Response: (updated item object)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Delete Item
|
### Delete Item
|
||||||
```
|
```
|
||||||
DELETE /api/items/:itemId
|
DELETE /api/items/:itemId
|
||||||
|
|
||||||
Response: 204 No Content
|
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
|
### Bulk Update Items
|
||||||
@@ -292,9 +336,11 @@ Body:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Response: 204 No Content
|
Response 204 No Content
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Upload
|
## Upload
|
||||||
|
|
||||||
### Upload Image
|
### Upload Image
|
||||||
@@ -302,41 +348,75 @@ Response: 204 No Content
|
|||||||
POST /api/upload
|
POST /api/upload
|
||||||
|
|
||||||
Body: multipart/form-data
|
Body: multipart/form-data
|
||||||
- image: File
|
image: File
|
||||||
|
|
||||||
Response:
|
Response 201:
|
||||||
{
|
{
|
||||||
"url": "https://cdn.example.com/image.jpg"
|
"url": "https://cdn.example.com/uploads/abc123.jpg"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- All endpoints return JSON
|
- All responses are JSON.
|
||||||
- Use PATCH for partial updates
|
- Use `PATCH` for partial updates - send only the fields you want to change.
|
||||||
- Priority: lower numbers = appears first
|
- `priority`: lower number = appears first in the list.
|
||||||
- Currency codes: USD, EUR, RUB, GBP, UAH
|
- `currency` supported values: `USD`, `EUR`, `RUB`, `GBP`, `UAH`.
|
||||||
- Images: array of URLs
|
- `imgs`: always send the **complete** array on update, not individual images.
|
||||||
- Description: array of key-value pairs
|
- `description`: array of `{ key, value }` pairs - free-form attributes per item.
|
||||||
- Auto-save triggers PATCH with single field every 500ms
|
- Auto-save from the backoffice fires `PATCH` with a single field every ~500 ms.
|
||||||
### Business Rules
|
|
||||||
|
|
||||||
1. **Nested Subcategories**
|
---
|
||||||
- Subcategories can have unlimited nesting levels
|
|
||||||
- A subcategory with items (`hasItems: true`) cannot have child subcategories
|
|
||||||
- Creating a subcategory under a parent with items will fail
|
|
||||||
|
|
||||||
2. **Item Management**
|
## Business Rules
|
||||||
- When first item is created in a subcategory, `hasItems` is set to true
|
|
||||||
- When last item is deleted, `hasItems` is set to false
|
|
||||||
- Items belong to the deepest subcategory in the hierarchy
|
|
||||||
|
|
||||||
3. **Comments**
|
### Nested Subcategories
|
||||||
- Stars field is optional (1-5 rating)
|
|
||||||
- createdAt is ISO 8601 timestamp
|
|
||||||
- author is optional (can be anonymous)
|
|
||||||
|
|
||||||
4. **URL Structure for Marketplace**
|
The hierarchy works like this:
|
||||||
- Items: `/{categoryId}/{subcategoryId}/.../{itemId}`
|
|
||||||
- Example: `/electronics/smartphones/iphone-15`
|
```
|
||||||
- Example nested: `/electronics/smartphones/apple/iphone-15`
|
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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user