Add public user-products storefront API and website docs.

Expose published customer listings under /tenants/:host/user-products (list, search, details, technical-info) and document them in the website API pack.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 00:19:13 +03:30
co-authored by Cursor
parent 158523df7b
commit 953b87b616
32 changed files with 3326 additions and 138 deletions
+8 -1
View File
@@ -31,10 +31,17 @@ You are building a **Meshkee business website (storefront)**. You must use the M
### 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
3. Catalog: categories, products, store-items, **user-products** (customer stock listings)
4. Auth: register/login → store tokens. Optional: `POST /auth/send-otp` then `POST /auth/login-otp` (passwordless) or `POST /auth/reset-password` (forgot password). `POST /auth/verify-otp` only marks the cell verified (no tokens).
5. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
### User products (customer listings)
Public marketplace listings owned by customers — not catalog `products`.
- `GET /tenants/{domain}/user-products` — list published (`name`/`q`, `categoryId`, `cityId`, `countryId`, `condition`, `promoted`, pagination)
- `GET /tenants/{domain}/user-products/{slug}` — details + gallery
- `GET /tenants/{domain}/user-products/{slug}/technical-info` — category form + values
Use product categories from `GET /tenants/{domain}/categories?entityType=product` for filters. Creating/editing listings is customer-dashboard only (`/businesses/.../my-user-products`), not website-facing.
If OpenAPI and this brief conflict, **OpenAPI wins**.
---
@@ -33,6 +33,14 @@
"key": "productSlug",
"value": ""
},
{
"key": "userProductId",
"value": ""
},
{
"key": "userProductSlug",
"value": ""
},
{
"key": "blogId",
"value": ""
@@ -1101,6 +1109,133 @@
}
]
},
{
"name": "User Products",
"item": [
{
"name": "List published user products",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"if (pm.response.code === 200) {",
" const json = pm.response.json();",
" if (json.items?.[0]?.id) pm.collectionVariables.set('userProductId', json.items[0].id);",
" if (json.items?.[0]?.slug) pm.collectionVariables.set('userProductSlug', json.items[0].slug);",
"}"
]
}
}
],
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/tenants/{{domain}}/user-products?page=1&pageSize=12",
"host": [
"{{baseUrl}}"
],
"path": [
"tenants",
"{{domain}}",
"user-products"
],
"query": [
{
"key": "page",
"value": "1"
},
{
"key": "pageSize",
"value": "12"
},
{
"key": "name",
"value": "",
"disabled": true
},
{
"key": "q",
"value": "",
"disabled": true
},
{
"key": "categoryId",
"value": "{{categoryId}}",
"disabled": true
},
{
"key": "cityId",
"value": "",
"disabled": true
},
{
"key": "countryId",
"value": "",
"disabled": true
},
{
"key": "condition",
"value": "new",
"disabled": true
},
{
"key": "promoted",
"value": "true",
"disabled": true
}
]
}
}
},
{
"name": "Search user products",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/tenants/{{domain}}/user-products?q=boiler&page=1&pageSize=12",
"host": [
"{{baseUrl}}"
],
"path": [
"tenants",
"{{domain}}",
"user-products"
],
"query": [
{
"key": "q",
"value": "boiler"
},
{
"key": "page",
"value": "1"
},
{
"key": "pageSize",
"value": "12"
}
]
}
}
},
{
"name": "Get user product by slug",
"request": {
"method": "GET",
"url": "{{baseUrl}}/tenants/{{domain}}/user-products/{{userProductSlug}}"
}
},
{
"name": "Get user product technical info by slug",
"request": {
"method": "GET",
"url": "{{baseUrl}}/tenants/{{domain}}/user-products/{{userProductSlug}}/technical-info"
}
}
]
},
{
"name": "Store Items",
"item": [
+8 -1
View File
@@ -88,10 +88,17 @@
<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>Public pages: <code>/tenants/{domain}/...</code> (no auth) — products, <strong>user-products</strong>, blogs, portfolios, store-items, etc.</li>
<li>Cart / orders / favorites: <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
</ol>
<h2>User products (customer listings)</h2>
<p>
Marketplace-style stock listings created by customers. Public read-only under
<code>/tenants/{domain}/user-products</code> (list / details / technical-info).
See OpenAPI tag <strong>User Products</strong>.
</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>
+61
View File
@@ -25,6 +25,7 @@
{ "name": "Homepage" },
{ "name": "Categories" },
{ "name": "Products" },
{ "name": "User Products" },
{ "name": "Store" },
{ "name": "Blogs" },
{ "name": "Portfolios" },
@@ -210,6 +211,66 @@
"responses": { "200": { "description": "{ form, values }" } }
}
},
"/tenants/{domain}/user-products": {
"get": {
"tags": ["User Products"],
"summary": "List published customer / stock listings",
"description": "Public marketplace-style listings created by customers (user products). Only `status=published`. Search with `name` or `q` (title/description). Filter by category, city, country, condition, or promoted.",
"parameters": [
{ "$ref": "#/components/parameters/domain" },
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
{ "name": "pageSize", "in": "query", "schema": { "type": "integer", "default": 12 } },
{ "name": "name", "in": "query", "description": "Search title/description (alias of q)", "schema": { "type": "string" } },
{ "name": "q", "in": "query", "description": "Search title/description (alias of name)", "schema": { "type": "string" } },
{ "name": "categoryId", "in": "query", "schema": { "type": "string" } },
{ "name": "cityId", "in": "query", "schema": { "type": "string" } },
{ "name": "countryId", "in": "query", "schema": { "type": "string" } },
{
"name": "condition",
"in": "query",
"schema": {
"type": "string",
"enum": ["new", "stock", "needs_repair", "scrap"]
}
},
{ "name": "promoted", "in": "query", "schema": { "type": "boolean" } }
],
"responses": {
"200": {
"description": "{ items: UserProductListItem[], total, page, pageSize }. Each item includes id, slug, titleFa/titleEn, price, priceCurrency, condition, city/country names, imageUrl, category*, promoted, publishedAt."
}
}
}
},
"/tenants/{domain}/user-products/{slug}": {
"get": {
"tags": ["User Products"],
"summary": "User product details by slug",
"description": "Full published listing: location IDs, gallery images (`images`, `galleryMediaIds`), technical field values, delivery/technical notes.",
"parameters": [
{ "$ref": "#/components/parameters/domain" },
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
],
"responses": {
"200": {
"description": "{ product } with gallery (`images`: [{ mediaId, url }]), featuredMediaId, technicalValues, countryId, cityId, countrySlug"
},
"404": { "description": "Not found or not published" }
}
}
},
"/tenants/{domain}/user-products/{slug}/technical-info": {
"get": {
"tags": ["User Products"],
"summary": "User product technical form + values",
"description": "Category technical form schema plus the listings submitted values (same shape as dashboard technical values).",
"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"],