mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Add website API docs, SSL api-hosts, and git-only deploy workflow.
Serve public storefront docs at /docs/website, expose api.{domain} hosts for API SSL sync, and require push-then-pull deploys instead of rsync.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
bb59d5e9ba
commit
016cc15bf0
+65
-8
@@ -6,7 +6,9 @@ App path on server: `/opt/meshkee/app`
|
||||
|
||||
API domain: `api.meshkee.com` → `https://api.meshkee.com/api/v1`
|
||||
|
||||
> **Note:** Until the Git remote is accessible from the VM (deploy key / credentials), updates can be synced with `rsync` from your laptop. Pin `sharp@0.33.5` — this VM CPU lacks x64-v2 required by sharp 0.35+.
|
||||
Per-business API aliases (same Nest app on this VM): `api.{apex}` e.g. `api.sanihome.ir` → `https://api.sanihome.ir/api/v1`. Storefronts still pass the **website apex** in paths (`/tenants/sanihome.ir/...`); only the API hostname changes.
|
||||
|
||||
> **Deploy path:** push to `git.meshkee.com` (`Meshkee/backend`), then on the API VM `git fetch` + `reset --hard origin/main` + build + `pm2 restart`. Do **not** rsync the app as the normal update path. The API VM uses a read-only SSH deploy key (`meshkee-api-vm-deploy`). Pin `sharp@0.33.5` — this VM CPU lacks x64-v2 required by sharp 0.35+.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -126,12 +128,28 @@ curl -s http://127.0.0.1:3000/api/v1/ | head
|
||||
|
||||
## 6. Nginx + HTTPS
|
||||
|
||||
### DNS (per business website domain)
|
||||
|
||||
On the **business domain** DNS (e.g. zone `sanihome.ir`), add a subdomain that points at this **API VM** (same target as `api.meshkee.com`):
|
||||
|
||||
| Type | Name / host | Value | Notes |
|
||||
|------|-------------|-------|--------|
|
||||
| **A** (preferred) | `api` | `<API_VM_PUBLIC_IP>` | Resolves `api.sanihome.ir` → API server |
|
||||
| **CNAME** (alternative) | `api` | `api.meshkee.com` | Same effect if your DNS panel allows CNAME on subdomains |
|
||||
|
||||
Do **not** point `api.{apex}` at the websites VM or dashboards VM — only the Nest API VM.
|
||||
|
||||
Repeat for each storefront apex (`api.ali-mohammadi.ir`, etc.). Central Meshkee DNS already has `api.meshkee.com` → this VM.
|
||||
|
||||
### Nginx
|
||||
|
||||
Create `/etc/nginx/sites-available/meshkee-api`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.example.com; # replace with your domain
|
||||
# Central + per-business aliases (add more api.{apex} as domains go live)
|
||||
server_name api.meshkee.com api.sanihome.ir;
|
||||
|
||||
client_max_body_size 15M;
|
||||
|
||||
@@ -146,29 +164,68 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
Enable and get a certificate:
|
||||
Enable and get certificates:
|
||||
|
||||
```bash
|
||||
sudo ln -sf /etc/nginx/sites-available/meshkee-api /etc/nginx/sites-enabled/
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
sudo certbot --nginx -d api.example.com
|
||||
sudo certbot --nginx -d api.meshkee.com -d api.sanihome.ir
|
||||
# later, when another business goes live:
|
||||
# sudo certbot --nginx -d api.meshkee.com -d api.sanihome.ir -d api.other-site.ir
|
||||
```
|
||||
|
||||
API base URL: `https://api.example.com/api/v1`
|
||||
API base URLs (identical Nest routes):
|
||||
|
||||
## Ongoing updates
|
||||
- Central: `https://api.meshkee.com/api/v1`
|
||||
- Alias example: `https://api.sanihome.ir/api/v1`
|
||||
|
||||
### Website API docs (global link for storefront teams)
|
||||
|
||||
After deploy, these are public (no auth):
|
||||
|
||||
- Hub: `https://api.meshkee.com/docs/website`
|
||||
- OpenAPI: `https://api.meshkee.com/docs/website/openapi.json`
|
||||
- Postman: `https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json`
|
||||
- AI brief: `https://api.meshkee.com/docs/website/AI_PROMPT.md`
|
||||
|
||||
Files live in `docs/website-api/` and are served by Nest from process cwd. Keep that folder on the VM when deploying.
|
||||
|
||||
### Automated cert host list (API VPS)
|
||||
|
||||
After deploy, a sync agent on this VM can pull names to cover:
|
||||
|
||||
```bash
|
||||
curl -s -H "X-SSL-Sync-Token: $SSL_SYNC_TOKEN" \
|
||||
https://api.meshkee.com/api/v1/internal/ssl/api-hosts
|
||||
# → { "hosts": ["api.ali-mohammadi.ir", "api.meshkee.com", "api.sanihome.ir", ...] }
|
||||
```
|
||||
|
||||
Dashboards VPS keeps using `GET /api/v1/internal/ssl/hosts` (`business.` / `customer.` / `manage`) — do not mix the two lists.
|
||||
|
||||
## Ongoing updates (git only)
|
||||
|
||||
From your laptop:
|
||||
|
||||
1. Commit and **push** to `origin/main` (`git.meshkee.com/Meshkee/backend`).
|
||||
2. Deploy on the API VM from that commit (never rsync the tree as the primary path):
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 root@185.164.72.119 'bash -s' <<'REMOTE'
|
||||
set -euo pipefail
|
||||
cd /opt/meshkee/app
|
||||
git pull
|
||||
./database/migrate.sh # if there are new SQL migrations
|
||||
git fetch origin
|
||||
git reset --hard origin/main
|
||||
./database/migrate.sh
|
||||
npm ci
|
||||
npm run prisma:generate
|
||||
npm run build
|
||||
pm2 restart meshkee-api
|
||||
REMOTE
|
||||
```
|
||||
|
||||
Preserve `/opt/meshkee/app/.env` on the server. App remote must be SSH: `git@git.meshkee.com:Meshkee/backend.git` with the VM deploy key registered as a **read-only deploy key** on the repo.
|
||||
|
||||
## Useful commands
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Meshkee Website API — AI / designer brief
|
||||
|
||||
Copy everything below into a new AI chat when building a Meshkee storefront.
|
||||
|
||||
---
|
||||
|
||||
## System context (paste this)
|
||||
|
||||
You are building a **Meshkee business website (storefront)**. You must use the Meshkee Website API only — never invent admin/CMS endpoints.
|
||||
|
||||
**Canonical docs (always prefer these):**
|
||||
- Hub: https://api.meshkee.com/docs/website
|
||||
- OpenAPI: https://api.meshkee.com/docs/website/openapi.json
|
||||
- Postman: https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json
|
||||
|
||||
**API base URL:** `https://api.meshkee.com/api/v1`
|
||||
(Optional alias if configured: `https://api.<WEBSITE_DOMAIN>/api/v1` — same backend.)
|
||||
|
||||
**This website’s apex domain:** `<WEBSITE_DOMAIN>`
|
||||
(example: `sanihome.ir` — no `www.`, no `api.`, no `customer.`, no `business.`)
|
||||
|
||||
### Hard rules
|
||||
1. Resolve tenant first: `GET /tenants/<WEBSITE_DOMAIN>` → save `businessId` from `id`.
|
||||
2. All public content uses `/tenants/<WEBSITE_DOMAIN>/...` (no auth).
|
||||
3. Cart, orders, favorites use `/businesses/<businessId>/...` with `Authorization: Bearer <accessToken>`.
|
||||
4. Customer register body must include `"domain": "<WEBSITE_DOMAIN>"`.
|
||||
5. Cell numbers are E.164 (`+98912...`).
|
||||
6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId`
|
||||
2. Homepage: business-info, sliders, category-groups, brand-groups, store-specials
|
||||
3. Catalog: categories, products, store-items
|
||||
4. Auth: register/login → store tokens
|
||||
5. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
|
||||
|
||||
If OpenAPI and this brief conflict, **OpenAPI wins**.
|
||||
|
||||
---
|
||||
|
||||
## What to tell each website team
|
||||
|
||||
Replace `<WEBSITE_DOMAIN>` once per project. Everything else is global — same Postman, same OpenAPI, same base URL.
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "meshkee-website-api-global",
|
||||
"name": "Meshkee Website API — Global",
|
||||
"values": [
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"value": "https://api.meshkee.com/api/v1",
|
||||
"type": "default",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "domain",
|
||||
"value": "YOUR_WEBSITE_DOMAIN",
|
||||
"type": "default",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "businessId",
|
||||
"value": "",
|
||||
"type": "default",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "accessToken",
|
||||
"value": "",
|
||||
"type": "secret",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "refreshToken",
|
||||
"value": "",
|
||||
"type": "secret",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"_postman_variable_scope": "environment"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Meshkee Website API</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f1419;
|
||||
--panel: #1a222c;
|
||||
--text: #e8eef4;
|
||||
--muted: #9aa8b5;
|
||||
--accent: #3d9cf0;
|
||||
--line: #2a3542;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||
background: radial-gradient(1200px 600px at 10% -10%, #1b3a57 0%, var(--bg) 55%);
|
||||
color: var(--text);
|
||||
line-height: 1.55;
|
||||
}
|
||||
main {
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.25rem 4rem;
|
||||
}
|
||||
h1 { font-size: 2rem; margin: 0 0 0.5rem; letter-spacing: -0.02em; }
|
||||
h2 { font-size: 1.15rem; margin: 2rem 0 0.75rem; }
|
||||
p, li { color: var(--muted); }
|
||||
strong { color: var(--text); }
|
||||
code {
|
||||
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||||
background: #0b1015;
|
||||
padding: 0.1rem 0.35rem;
|
||||
border-radius: 4px;
|
||||
color: #cde3f7;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 1rem 1.1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
a.btn {
|
||||
display: inline-block;
|
||||
margin: 0.35rem 0.5rem 0.35rem 0;
|
||||
padding: 0.65rem 1rem;
|
||||
border-radius: 8px;
|
||||
background: var(--accent);
|
||||
color: #061018;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
a.btn.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.eyebrow { color: var(--accent); font-size: 0.85rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="eyebrow">Meshkee · Global storefront contract</div>
|
||||
<h1>Website API</h1>
|
||||
<p>
|
||||
One API for <strong>every</strong> Meshkee business website. Not tied to a single domain.
|
||||
Set your site’s apex host (e.g. <code>sanihome.ir</code>) and reuse the same endpoints.
|
||||
</p>
|
||||
|
||||
<div class="panel">
|
||||
<p style="margin:0 0 0.75rem"><strong>Global links</strong> (share these with designers & AI tools):</p>
|
||||
<a class="btn" href="./openapi.json">OpenAPI JSON</a>
|
||||
<a class="btn secondary" href="./Meshkee-Website-API.postman_collection.json">Download Postman</a>
|
||||
<a class="btn secondary" href="./AI_PROMPT.md">AI prompt</a>
|
||||
</div>
|
||||
|
||||
<h2>Base URL</h2>
|
||||
<p><code>https://api.meshkee.com/api/v1</code></p>
|
||||
<p>Optional per-site alias (same backend): <code>https://api.<domain>/api/v1</code></p>
|
||||
|
||||
<h2>How tenants work</h2>
|
||||
<ol>
|
||||
<li>Variable <code>domain</code> = website apex only (no <code>www</code>/<code>api</code>/<code>customer</code>/<code>business</code>).</li>
|
||||
<li><code>GET /tenants/{domain}</code> → <code>businessId</code>.</li>
|
||||
<li>Public pages: <code>/tenants/{domain}/...</code> (no auth).</li>
|
||||
<li>Cart / orders / favorites: <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
|
||||
</ol>
|
||||
|
||||
<h2>For a new website AI / designer</h2>
|
||||
<ol>
|
||||
<li>Open <a href="./AI_PROMPT.md">AI_PROMPT.md</a> and paste it into the AI chat.</li>
|
||||
<li>Replace <code><WEBSITE_DOMAIN></code> with that site’s apex.</li>
|
||||
<li>Import the Postman collection (set <code>domain</code>, run Resolve tenant).</li>
|
||||
<li>Or feed <code>openapi.json</code> to the AI / codegen tool.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Import Postman</h2>
|
||||
<p>
|
||||
Postman → Import → Link → paste<br />
|
||||
<code>https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json</code>
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,901 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Meshkee Website API",
|
||||
"version": "1.0.0",
|
||||
"description": "Global storefront API for every Meshkee business website.\n\n**Not domain-specific.** Replace `{domain}` with the website apex (e.g. `sanihome.ir`).\n\n**Base URL:** `https://api.meshkee.com/api/v1` (or `https://api.{domain}/api/v1` if that alias is configured).\n\n**Tenant rule:** public content uses `/tenants/{domain}/...`. After login, cart/orders/favorites use `/businesses/{businessId}/...` with Bearer JWT.\n\n**Docs:** https://api.meshkee.com/docs/website"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://api.meshkee.com/api/v1",
|
||||
"description": "Production (central) — use this for all websites"
|
||||
},
|
||||
{
|
||||
"url": "https://api.{domain}/api/v1",
|
||||
"description": "Optional per-site alias (same backend). {domain} = website apex",
|
||||
"variables": {
|
||||
"domain": {
|
||||
"default": "example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{ "name": "Tenant" },
|
||||
{ "name": "Homepage" },
|
||||
{ "name": "Categories" },
|
||||
{ "name": "Products" },
|
||||
{ "name": "Store" },
|
||||
{ "name": "Blogs" },
|
||||
{ "name": "Portfolios" },
|
||||
{ "name": "Comments" },
|
||||
{ "name": "Expert Reviews" },
|
||||
{ "name": "Contact" },
|
||||
{ "name": "Auth" },
|
||||
{ "name": "Addresses" },
|
||||
{ "name": "Cities" },
|
||||
{ "name": "Cart" },
|
||||
{ "name": "Orders" },
|
||||
{ "name": "Favorites" }
|
||||
],
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"domain": {
|
||||
"name": "domain",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "Website apex host only (e.g. sanihome.ir). No www/api/customer/business prefix.",
|
||||
"schema": { "type": "string", "example": "example.com" }
|
||||
},
|
||||
"businessId": {
|
||||
"name": "businessId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "From GET /tenants/{domain} → id",
|
||||
"schema": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/tenants/{domain}": {
|
||||
"get": {
|
||||
"tags": ["Tenant"],
|
||||
"summary": "Resolve website domain → business",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Business branding",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"nameFa": { "type": "string" },
|
||||
"slug": { "type": "string" },
|
||||
"domain": { "type": "string" },
|
||||
"primaryColor": { "type": "string", "nullable": true },
|
||||
"logoUrl": { "type": "string", "nullable": true },
|
||||
"faviconUrl": { "type": "string", "nullable": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/website/business-info": {
|
||||
"get": {
|
||||
"tags": ["Homepage"],
|
||||
"summary": "About, contacts, addresses, social",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": { "200": { "description": "Business public profile" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/website/sliders": {
|
||||
"get": {
|
||||
"tags": ["Homepage"],
|
||||
"summary": "Homepage sliders + slides",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": { "200": { "description": "{ items: Slider[] }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/website/category-groups": {
|
||||
"get": {
|
||||
"tags": ["Homepage"],
|
||||
"summary": "Homepage category groups",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": { "200": { "description": "{ items: CategoryGroup[] }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/website/brand-groups": {
|
||||
"get": {
|
||||
"tags": ["Homepage"],
|
||||
"summary": "Homepage brand groups",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": { "200": { "description": "{ items: BrandGroup[] }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/store-specials": {
|
||||
"get": {
|
||||
"tags": ["Homepage", "Store"],
|
||||
"summary": "Active store specials",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"responses": { "200": { "description": "{ items: StoreSpecial[] }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/categories": {
|
||||
"get": {
|
||||
"tags": ["Categories"],
|
||||
"summary": "Public categories",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{
|
||||
"name": "entityType",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["product", "blog", "portfolio"],
|
||||
"default": "product"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": { "200": { "description": "{ items: Category[] }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/products": {
|
||||
"get": {
|
||||
"tags": ["Products"],
|
||||
"summary": "List published products",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 12 } },
|
||||
{ "name": "name", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "categoryId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "brandId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "tag", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "inStore", "in": "query", "schema": { "type": "boolean" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/products/{slug}": {
|
||||
"get": {
|
||||
"tags": ["Products"],
|
||||
"summary": "Product by slug",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ product }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/products/{slug}/variations": {
|
||||
"get": {
|
||||
"tags": ["Products"],
|
||||
"summary": "Product variation options",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ variations }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/products/{slug}/technical-info": {
|
||||
"get": {
|
||||
"tags": ["Products"],
|
||||
"summary": "Product technical specs",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ form, values }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/store-items": {
|
||||
"get": {
|
||||
"tags": ["Store"],
|
||||
"summary": "List sellable variants",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
||||
{ "name": "categoryId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "brandId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "productId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "name", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "inStock", "in": "query", "schema": { "type": "boolean" } },
|
||||
{ "name": "isFestival", "in": "query", "schema": { "type": "boolean" } },
|
||||
{ "name": "minPrice", "in": "query", "schema": { "type": "number" } },
|
||||
{ "name": "maxPrice", "in": "query", "schema": { "type": "number" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/store-items/by-product/{productId}": {
|
||||
"get": {
|
||||
"tags": ["Store"],
|
||||
"summary": "Variants for one product",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "productId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ storeItem }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/store-items/{variantId}": {
|
||||
"get": {
|
||||
"tags": ["Store"],
|
||||
"summary": "One variant",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "variantId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ variant }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/blogs": {
|
||||
"get": {
|
||||
"tags": ["Blogs"],
|
||||
"summary": "List published blogs",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 12 } },
|
||||
{ "name": "type", "in": "query", "schema": { "type": "string", "enum": ["news", "article", "blog"] } },
|
||||
{ "name": "categoryId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "title", "in": "query", "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/blogs/{slug}": {
|
||||
"get": {
|
||||
"tags": ["Blogs"],
|
||||
"summary": "Blog by slug",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ blog }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/blogs/{blogId}/comments": {
|
||||
"get": {
|
||||
"tags": ["Blogs", "Comments"],
|
||||
"summary": "Approved blog comments",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "blogId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Blogs", "Comments"],
|
||||
"summary": "Submit blog comment",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "blogId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["authorName", "text"],
|
||||
"properties": {
|
||||
"authorName": { "type": "string" },
|
||||
"authorEmail": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ comment, message }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/portfolios": {
|
||||
"get": {
|
||||
"tags": ["Portfolios"],
|
||||
"summary": "List published portfolios",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 12 } },
|
||||
{ "name": "categoryId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "title", "in": "query", "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/portfolios/{slug}": {
|
||||
"get": {
|
||||
"tags": ["Portfolios"],
|
||||
"summary": "Portfolio by slug",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ portfolio }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/portfolios/{portfolioId}/comments": {
|
||||
"get": {
|
||||
"tags": ["Portfolios", "Comments"],
|
||||
"summary": "Approved portfolio comments",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "portfolioId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Portfolios", "Comments"],
|
||||
"summary": "Submit portfolio comment",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "portfolioId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["authorName", "text"],
|
||||
"properties": {
|
||||
"authorName": { "type": "string" },
|
||||
"authorEmail": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ comment, message }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/comments": {
|
||||
"get": {
|
||||
"tags": ["Comments"],
|
||||
"summary": "List approved comments for any entity",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{
|
||||
"name": "entityType",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": { "type": "string", "enum": ["product", "blog", "portfolio"] }
|
||||
},
|
||||
{ "name": "entityId", "in": "query", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Comments"],
|
||||
"summary": "Submit comment (product/blog/portfolio)",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["entityType", "entityId", "authorName", "text"],
|
||||
"properties": {
|
||||
"entityType": { "type": "string", "enum": ["product", "blog", "portfolio"] },
|
||||
"entityId": { "type": "string" },
|
||||
"authorName": { "type": "string" },
|
||||
"authorEmail": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ comment, message }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/expert-reviews": {
|
||||
"get": {
|
||||
"tags": ["Expert Reviews"],
|
||||
"summary": "Approved expert reviews for a product",
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/domain" },
|
||||
{ "name": "productId", "in": "query", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Expert Reviews"],
|
||||
"summary": "Submit expert review",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["productId", "authorName", "rate", "positivePoints", "negativePoints", "text"],
|
||||
"properties": {
|
||||
"productId": { "type": "string" },
|
||||
"authorName": { "type": "string" },
|
||||
"authorEmail": { "type": "string" },
|
||||
"rate": { "type": "integer", "minimum": 1, "maximum": 10 },
|
||||
"positivePoints": { "type": "array", "items": { "type": "string" } },
|
||||
"negativePoints": { "type": "array", "items": { "type": "string" } },
|
||||
"text": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ review, message }" } }
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/contact-submissions": {
|
||||
"post": {
|
||||
"tags": ["Contact"],
|
||||
"summary": "Contact form",
|
||||
"parameters": [{ "$ref": "#/components/parameters/domain" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["title", "name", "text"],
|
||||
"properties": {
|
||||
"title": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"cellNumber": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ submission, message }" } }
|
||||
}
|
||||
},
|
||||
"/auth/register": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Register customer on a website",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["cellNumber", "password", "firstName", "lastName", "domain"],
|
||||
"properties": {
|
||||
"cellNumber": { "type": "string", "description": "E.164 e.g. +98912..." },
|
||||
"password": { "type": "string", "minLength": 8 },
|
||||
"firstName": { "type": "string" },
|
||||
"lastName": { "type": "string" },
|
||||
"email": { "type": "string" },
|
||||
"domain": { "type": "string", "description": "Same website apex as {domain}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ user, accessToken, refreshToken, registeredBusiness }" } }
|
||||
}
|
||||
},
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Login",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["cellNumber", "password"],
|
||||
"properties": {
|
||||
"cellNumber": { "type": "string" },
|
||||
"password": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ user, accessToken, refreshToken }" } }
|
||||
}
|
||||
},
|
||||
"/auth/refresh": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Refresh tokens",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["refreshToken"],
|
||||
"properties": { "refreshToken": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ user, accessToken, refreshToken }" } }
|
||||
}
|
||||
},
|
||||
"/auth/me": {
|
||||
"get": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Current user",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"responses": { "200": { "description": "{ user }" } }
|
||||
}
|
||||
},
|
||||
"/auth/profile": {
|
||||
"patch": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Update profile",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"responses": { "200": { "description": "{ message, user }" } }
|
||||
}
|
||||
},
|
||||
"/auth/change-password": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Change password",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["currentPassword", "newPassword"],
|
||||
"properties": {
|
||||
"currentPassword": { "type": "string" },
|
||||
"newPassword": { "type": "string", "minLength": 8 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ message }" } }
|
||||
}
|
||||
},
|
||||
"/auth/send-otp": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Send OTP SMS",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["cellNumber"],
|
||||
"properties": { "cellNumber": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ enabled, message, expiresInSeconds? }" } }
|
||||
}
|
||||
},
|
||||
"/auth/verify-otp": {
|
||||
"post": {
|
||||
"tags": ["Auth"],
|
||||
"summary": "Verify OTP",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["cellNumber", "code"],
|
||||
"properties": {
|
||||
"cellNumber": { "type": "string" },
|
||||
"code": { "type": "string", "minLength": 6, "maxLength": 6 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ enabled, verified, message }" } }
|
||||
}
|
||||
},
|
||||
"/auth/addresses": {
|
||||
"get": {
|
||||
"tags": ["Addresses"],
|
||||
"summary": "List my shipping addresses",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Addresses"],
|
||||
"summary": "Create address",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["province", "city", "address"],
|
||||
"properties": {
|
||||
"label": { "type": "string" },
|
||||
"province": { "type": "string" },
|
||||
"city": { "type": "string" },
|
||||
"address": { "type": "string" },
|
||||
"postalCode": { "type": "string" },
|
||||
"landline": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ address }" } }
|
||||
}
|
||||
},
|
||||
"/auth/addresses/{addressId}": {
|
||||
"patch": {
|
||||
"tags": ["Addresses"],
|
||||
"summary": "Update address",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "name": "addressId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ address }" } }
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["Addresses"],
|
||||
"summary": "Delete address",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "name": "addressId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ message }" } }
|
||||
}
|
||||
},
|
||||
"/cities": {
|
||||
"get": {
|
||||
"tags": ["Cities"],
|
||||
"summary": "Location tree (countries / provinces / cities)",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "level",
|
||||
"in": "query",
|
||||
"schema": { "type": "string", "enum": ["country", "province", "city"] }
|
||||
},
|
||||
{ "name": "parentId", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "parentSlug", "in": "query", "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items }" } }
|
||||
}
|
||||
},
|
||||
"/cities/{cityId}": {
|
||||
"get": {
|
||||
"tags": ["Cities"],
|
||||
"summary": "Get one location node",
|
||||
"parameters": [
|
||||
{ "name": "cityId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ city }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/cart": {
|
||||
"get": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Get cart",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [{ "$ref": "#/components/parameters/businessId" }],
|
||||
"responses": { "200": { "description": "{ cart }" } }
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Clear cart",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [{ "$ref": "#/components/parameters/businessId" }],
|
||||
"responses": { "200": { "description": "{ message, cart }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/cart/items": {
|
||||
"post": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Add variant to cart",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [{ "$ref": "#/components/parameters/businessId" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["storeItemVariantId"],
|
||||
"properties": {
|
||||
"storeItemVariantId": { "type": "string" },
|
||||
"quantity": { "type": "integer", "minimum": 1, "default": 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ message, cart }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/cart/items/{itemId}": {
|
||||
"patch": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Update cart line quantity",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "itemId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["quantity"],
|
||||
"properties": { "quantity": { "type": "integer", "minimum": 1 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "200": { "description": "{ message, cart }" } }
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Remove cart line",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "itemId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ message, cart }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/cart/checkout": {
|
||||
"post": {
|
||||
"tags": ["Cart"],
|
||||
"summary": "Checkout → create order",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [{ "$ref": "#/components/parameters/businessId" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["payment"],
|
||||
"properties": {
|
||||
"addressId": { "type": "string" },
|
||||
"shippingAddress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"province": { "type": "string" },
|
||||
"city": { "type": "string" },
|
||||
"address": { "type": "string" },
|
||||
"postalCode": { "type": "string" },
|
||||
"landline": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"customerNotes": { "type": "string" },
|
||||
"payment": {
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["pos", "cash", "transfer", "e_payment_gate"]
|
||||
},
|
||||
"posType": { "type": "string" },
|
||||
"transferAccount": { "type": "string" },
|
||||
"transferRefNumber": { "type": "string" },
|
||||
"gatewayType": { "type": "string" },
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ message, order }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/orders": {
|
||||
"get": {
|
||||
"tags": ["Orders"],
|
||||
"summary": "My orders",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
||||
{
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "confirmed", "processing", "shipped", "delivered", "cancelled"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/orders/{orderId}": {
|
||||
"get": {
|
||||
"tags": ["Orders"],
|
||||
"summary": "My order detail",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "orderId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ order }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/favorites": {
|
||||
"get": {
|
||||
"tags": ["Favorites"],
|
||||
"summary": "List favorites",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ items, total, page, pageSize }" } }
|
||||
},
|
||||
"post": {
|
||||
"tags": ["Favorites"],
|
||||
"summary": "Add favorite",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [{ "$ref": "#/components/parameters/businessId" }],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["productId"],
|
||||
"properties": { "productId": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": { "201": { "description": "{ favorite, message }" } }
|
||||
}
|
||||
},
|
||||
"/businesses/{businessId}/favorites/{productId}": {
|
||||
"delete": {
|
||||
"tags": ["Favorites"],
|
||||
"summary": "Remove favorite",
|
||||
"security": [{ "bearerAuth": [] }],
|
||||
"parameters": [
|
||||
{ "$ref": "#/components/parameters/businessId" },
|
||||
{ "name": "productId", "in": "path", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": { "200": { "description": "{ message }" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user