Add Torob Product API v3 for store-module sites with a store setting to enable it.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
6f1b13b6dc
commit
42cf29bc4e
@@ -1,7 +1,7 @@
|
||||
# Meshkee CMS API — Project Context
|
||||
|
||||
> Living reference for developers and AI assistants working on this codebase.
|
||||
> Last updated: August 21, 2026
|
||||
> Last updated: September 3, 2026
|
||||
|
||||
## What This Project Is
|
||||
|
||||
@@ -272,6 +272,7 @@ All routes are prefixed with `/api/v1`.
|
||||
| GET | `/tenants/:host/sitemap-workshops.xml` | Published workshops `/workshops/{slug}` |
|
||||
| GET | `/tenants/:host/sitemap-user-products.xml` | Published user products `/user-products/{slug}` (module `customer_products`) |
|
||||
| GET | `/tenants/:host/robots.txt` | robots.txt pointing to apex `/sitemap.xml` |
|
||||
| POST | `/tenants/:host/torob_api/v3/products` | Torob Product API v3 (JWT). Requires store module + `settings.store.torobEnabled`. Nginx: `POST https://{host}/torob_api/v3/products` |
|
||||
| GET | `/tenants/:host/categories/by-id/:categoryId` | Public category by id (for category landing pages) |
|
||||
| GET | `/tenants/:host/categories/by-slug/:slug` | Public category by CMS slug |
|
||||
| GET | `/tenants/:host/products/by-id/:productId` | Public product detail by id (for `{id}/{nameFaSlug}` storefront routes) |
|
||||
@@ -643,7 +644,7 @@ See `.env.example` for the full list. Key groups:
|
||||
- Multi-tenant auth (register, login, passwordless OTP login, reset password via SMS, profile)
|
||||
- Super admin: users, businesses, domains, system business categories
|
||||
- Super admin add/update domain upserts tenant DNS via ArvanCloud or Cloudflare (`@` ANAME/CNAME, `www`/`business`/`customer`/`api` CNAMEs; Cloudflare DNS-only)
|
||||
- Super admin Websites ⋯ **Email DNS** upserts Stalwart mail records (mail A `185.214.101.41`, MX `mail.meshkee.com`, SPF, two DKIM TXT, autoconfig/autodiscover) on Arvan or Cloudflare
|
||||
- Super admin Websites ⋯ **Email DNS** upserts Stalwart mail records (mail A `185.214.101.41`, MX `mail.meshkee.com`, SPF, two DKIM TXT, autoconfig/autodiscover) on Arvan or Cloudflare; removes extra MX, `dkim._domainkey`, and `_dmarc`
|
||||
- Super admin: selective migrate-from-old + purge-data (portfolio categories + portfolios; oversized images resized to max 1280×1280; purge removes portfolios + images)
|
||||
- Business team management
|
||||
- Media upload (S3 + Sharp)
|
||||
@@ -651,6 +652,7 @@ See `.env.example` for the full list. Key groups:
|
||||
- Products CRUD
|
||||
- Product variation values (which options a product offers)
|
||||
- Store items / product variants (price, stock, SKU)
|
||||
- Torob Product API v3 (`POST /tenants/:host/torob_api/v3/products`) for store-module websites; nginx proxies `/torob_api/v3/products` on the shop apex
|
||||
- Shopping cart + checkout + orders (customer + admin)
|
||||
- Online e-payment (Mellat + ZarinPal; stubs for SEP / Snapp Pay / DigiPay)
|
||||
- Product technical info
|
||||
|
||||
@@ -28,6 +28,7 @@ You are building a **Meshkee business website (storefront)**. You must use the M
|
||||
6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
|
||||
7. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
|
||||
8. **Technical details (labels + values):** Product/user-product detail responses may include `technicalValues` with **values only** (`fieldId` + `textValue` / `optionId` / `optionIds` — **no field labels**). To render a label→value specs table you **must** call the matching `.../technical-info` endpoint and join `form.fields[].id` ↔ `values[].fieldId`. Never invent a separate “variation fields” or “category fields” public route — those do not exist on the website API.
|
||||
9. **Torob:** Do **not** add a Next.js route for `/torob_api`. Meshkee nginx on the store apex proxies `POST /torob_api/v3/products` to the API. Only businesses with the **store** module **and** Store settings → Torob switch on return products (otherwise 404). Storefront UI must not call this endpoint.
|
||||
|
||||
### Typical bootstrap sequence
|
||||
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
|
||||
|
||||
@@ -1487,6 +1487,37 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Torob",
|
||||
"item": [
|
||||
{
|
||||
"name": "Product API v3 (store module)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "X-Torob-Token",
|
||||
"value": "{{torobJwt}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Torob-Token-Version",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"page\": 1,\n \"sort\": \"date_added_desc\"\n}"
|
||||
},
|
||||
"url": "{{baseUrl}}/tenants/{{domain}}/torob_api/v3/products",
|
||||
"description": "Torob-only. 404 if the tenant does not have the store module. Prefer the shop-domain URL POST https://{domain}/torob_api/v3/products after nginx is patched."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Homepage",
|
||||
"item": [
|
||||
|
||||
@@ -118,6 +118,17 @@
|
||||
There is no public <code>product-category-variation-fields</code> route.
|
||||
</p>
|
||||
|
||||
<h2>Torob (price comparison)</h2>
|
||||
<p>
|
||||
<code>POST /tenants/{domain}/torob_api/v3/products</code> is for
|
||||
<strong>Torob</strong>, not storefront JavaScript. On the live shop,
|
||||
nginx proxies <code>POST https://{domain}/torob_api/v3/products</code>
|
||||
to that API. It only returns catalog store items when the business has
|
||||
the <strong>store</strong> module enabled <em>and</em> Store settings →
|
||||
Torob is on; otherwise 404.
|
||||
Do not implement this path in Next.js.
|
||||
</p>
|
||||
|
||||
<h2>For a new website AI / designer</h2>
|
||||
<ol>
|
||||
<li>Open <a href="/docs/website/AI_PROMPT.md">AI_PROMPT.md</a> and paste it into the AI chat.</li>
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
{
|
||||
"name": "Store"
|
||||
},
|
||||
{
|
||||
"name": "Torob",
|
||||
"description": "Product API v3 for Torob. Called by Torob (not storefront JS). Nginx on the shop apex proxies POST /torob_api/v3/products. Only tenants with the store module enabled; otherwise 404."
|
||||
},
|
||||
{
|
||||
"name": "Blogs"
|
||||
},
|
||||
@@ -1422,6 +1426,111 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/torob_api/v3/products": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Torob"
|
||||
],
|
||||
"summary": "Torob Product API v3 (store module only)",
|
||||
"description": "Torob POSTs here (or to `https://{domain}/torob_api/v3/products` which nginx proxies). JWT in `X-Torob-Token` (EdDSA, aud = shop host). Returns 404 when the tenant does not have the **store** module. Page size is 100.",
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/domain"
|
||||
},
|
||||
{
|
||||
"name": "X-Torob-Token",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "X-Torob-Token-Version",
|
||||
"in": "header",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"page",
|
||||
"sort"
|
||||
],
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"date_added_desc",
|
||||
"date_updated_desc"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"page_urls"
|
||||
],
|
||||
"properties": {
|
||||
"page_urls": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"page_uniques"
|
||||
],
|
||||
"properties": {
|
||||
"page_uniques": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{ api_version: torob_api_v3, current_page, total, max_pages, products[] }"
|
||||
},
|
||||
"400": {
|
||||
"description": "{ error: string }"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid or missing Torob JWT"
|
||||
},
|
||||
"404": {
|
||||
"description": "Unknown domain, store module off, or Torob switch off in store settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/tenants/{domain}/blogs": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
Reference in New Issue
Block a user