Add website favicon API and manual favicon profile updates.

Expose GET /tenants/{domain}/website/favicon for storefront tab icons, allow faviconMediaId on profile PATCH, and sync website API docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 00:51:11 +03:30
co-authored by Cursor
parent 6ce6f959ac
commit ead16080b8
13 changed files with 318 additions and 13 deletions
+11 -4
View File
@@ -30,16 +30,23 @@ You are building a **Meshkee business website (storefront)**. You must use the M
### Typical bootstrap sequence
1. `GET /tenants/{domain}` → branding + `businessId` + `specialProductsSource` (`product` or `store_item`)
2. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
3. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc); each portfolio may include nullable `projectUrl` (external website link). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
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`
2. `GET /tenants/{domain}/website/favicon` → `faviconUrl` for `<link rel="icon">` / Next.js `metadata.icons` (falls back to `logoUrl` when no dedicated favicon). Also returns `logoUrl`, `logoDarkUrl`, `hasDedicatedFavicon`.
3. Homepage: business-info, **static-images**, sliders, category-groups, brand-groups, store-specials (`source` repeats the tenant setting; items are store listings when `source` is `store_item`)
4. Catalog: categories, products (`GET /products/{slug}` includes `relatedProducts`: same category then same brand, in-stock first), store-items (`name` instant search: in-stock first, then `updatedAt`), **user-products** (customer stock listings). Portfolios list newest first (`sortOrder` desc, then `publishedAt` / `createdAt` desc); each portfolio may include nullable `projectUrl` (external website link). List filters: products/blogs/portfolios/videos accept `?tag=` (exact match on `metadata.tags`).
5. 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).
6. Cart checkout with `addressId` or inline `shippingAddress` + `payment`
- For online pay: `payment.type = "e_payment_gate"`, `gatewayType` (e.g. `"mellat"` or `"zarinpal"`), and absolute `returnUrl`
- Response includes `payment.redirect` `{ method, url, fields }` — POST/redirect shopper to the bank
- Meshkee registers the bank `callback_url` on the **store apex** (`https://YOUR_WEBSITE_DOMAIN/meshkee/payments/{gateway}/callback`), which nginx proxies to the API. ZarinPal/Mellat domain checks must match the store domain, not `api.meshkee.com`.
- After verify, API redirects the browser to `returnUrl?status=success|failed&orderId=…`
- Enabled gateways: `GET /tenants/{domain}` → `ePayment`, or `GET /businesses/{businessId}/payments/methods`
### Favicon + logos
- `GET /tenants/{domain}/website/favicon` → `{ faviconUrl, logoUrl, logoDarkUrl, hasDedicatedFavicon }`
- Use `faviconUrl` in layout metadata (Next.js: `icons: [{ url: faviconUrl, type: 'image/png' }]` when set).
- When `hasDedicatedFavicon` is false, `faviconUrl` equals the light `logoUrl` — still safe to use as tab icon.
- Header/footer logos: `logoUrl` on light backgrounds, `logoDarkUrl` on dark (fallback to `logoUrl` in CSS when null).
### 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)
@@ -1494,6 +1494,13 @@
"url": "{{baseUrl}}/tenants/{{domain}}/website/business-info"
}
},
{
"name": "Get favicon + logos (website)",
"request": {
"method": "GET",
"url": "{{baseUrl}}/tenants/{{domain}}/website/favicon"
}
},
{
"name": "List category groups (website)",
"request": {
+9
View File
@@ -92,6 +92,15 @@
<li>Cart / orders / favorites: <code>/businesses/{businessId}/...</code> + Bearer JWT.</li>
</ol>
<h2>Branding (favicon + logos)</h2>
<p>
<code>GET /tenants/{domain}/website/favicon</code> returns
<code>faviconUrl</code>, <code>logoUrl</code>, <code>logoDarkUrl</code>, and
<code>hasDedicatedFavicon</code>. Use <code>faviconUrl</code> for the browser tab icon
(Next.js <code>metadata.icons</code>). When no dedicated favicon is uploaded,
<code>faviconUrl</code> falls back to <code>logoUrl</code>.
</p>
<h2>User products (customer listings)</h2>
<p>
Marketplace-style stock listings created by customers. Public read-only under
+95 -2
View File
@@ -176,7 +176,8 @@
},
"faviconUrl": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Tab icon URL. Dedicated favicon when uploaded; otherwise same as logoUrl."
},
"specialProductsSource": {
"type": "string",
@@ -332,7 +333,99 @@
],
"responses": {
"200": {
"description": "Business public profile"
"description": "Business public profile",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"nameFa": { "type": "string" },
"about": { "type": "string" },
"vision": { "type": "string" },
"logoUrl": { "type": "string", "nullable": true },
"logoDarkUrl": {
"type": "string",
"nullable": true,
"description": "Logo for dark backgrounds; null when not set."
},
"faviconUrl": {
"type": "string",
"nullable": true,
"description": "Dedicated favicon when uploaded; otherwise same as logoUrl."
},
"emails": {
"type": "array",
"items": { "type": "string" }
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["landline", "cell"] },
"number": { "type": "string" }
}
}
},
"socialMedia": { "type": "object" },
"addresses": { "type": "array", "items": { "type": "object" } }
}
}
}
}
}
}
}
},
"/tenants/{domain}/website/favicon": {
"get": {
"tags": [
"Homepage"
],
"summary": "Favicon and logo URLs for site chrome",
"description": "Use `faviconUrl` for `<link rel=\"icon\">` or Next.js `metadata.icons`. When no dedicated favicon is uploaded, `faviconUrl` equals `logoUrl`. Prefer this endpoint over tenant resolve when you only need branding assets.",
"parameters": [
{
"$ref": "#/components/parameters/domain"
}
],
"responses": {
"200": {
"description": "Branding asset URLs",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"faviconUrl": {
"type": "string",
"nullable": true,
"description": "Use for browser tab icon. Falls back to logoUrl when no dedicated favicon exists."
},
"logoUrl": {
"type": "string",
"nullable": true,
"description": "Light-theme logo."
},
"logoDarkUrl": {
"type": "string",
"nullable": true,
"description": "Dark-theme logo; null when not set."
},
"hasDedicatedFavicon": {
"type": "boolean",
"description": "True when the business uploaded a separate favicon (not auto-derived from logo)."
}
},
"required": ["faviconUrl", "logoUrl", "logoDarkUrl", "hasDedicatedFavicon"]
}
}
}
},
"404": {
"description": "Unknown domain"
}
}
}