diff --git a/docs/api/routes.md b/docs/api/routes.md index 60355b7..3c442aa 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -1,6 +1,6 @@ # Dash API Routes Overview -Overview of all 188 API endpoints across all modules. +Overview of all 186 API endpoints across all modules. ### Notation @@ -188,9 +188,6 @@ _none_ #### Delete attribute value image — `DELETE /attributevalue/{id}/image` - Params: `id: int` - Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T28[] } }` -#### Add or replace attribute value image — `POST /attributevalue/{id}/image` -- Params: `id: int, image/avif` -- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T28[] } }` ### Section: /brand @@ -209,9 +206,6 @@ _none_ #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` - Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T28[] } }` -#### Add or replace brand image — `POST /brand/{id}/image` -- Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T28[] } }` ### Section: /carrier @@ -691,15 +685,19 @@ _none_ - Returns: `200 { warehouse: { id: int, name: str, sort: int } }` ### Binary & streaming routes (reachable only via `fetch()` in javascript_execute) -These routes are NOT in the table above because their responses are binary or long-lived streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write them to the VFS and embed them in your reply: +These routes are NOT in the table above because their payloads (binary request body or binary response) or long-lived streams cannot be handled usefully by the `dashback_request` tool. They ARE reachable from inside `javascript_execute` with `fetch("/route")`: | Method | Route | Returns | Notes | |:---|:---|:---|:---| | `GET` | `/image/{fullHash}` | `image/avif` | Full-size article/brand/category image by SHA-256 hash. Use for inspecting or re-encoding an existing image. | `GET` | `/thumb/{fullHash}` | `image/avif` | Thumb AVIF by SHA-256 hash — cheaper than the full image. Takes the same `fullHash` as `/image/{fullHash}`. | `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. Body: `{ url }`. +| `POST` | `/article/{sku}/image` | `201 { image: { picId, sort, full, thumb, hash, thumbHash } }` | Upload full-size AVIF or WebP image for article SKU. WebP is auto-converted to AVIF. Body: raw binary (`image/avif` or `image/webp`). +| `POST` | `/attributevalue/{id}/image` | `201 { attributevalue: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for attribute value ID. Body: raw binary (`image/avif` or `image/webp`). +| `POST` | `/brand/{id}/image` | `201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for brand ID. Body: raw binary (`image/avif` or `image/webp`). +| `POST` | `/category/{id}/image` | `201 { category: { id, thumbW, thumbH, thumbHash } }` | Upload or replace AVIF/WebP image for category ID. Body: raw binary (`image/avif` or `image/webp`). -Save the bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`: +Save downloaded bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`: ```js module.exports = (async () => { @@ -711,4 +709,18 @@ module.exports = (async () => { })(); ``` +Upload an image from the VFS: + +```js +module.exports = (async () => { + const buf = fs.readFileSync("images/product.avif"); + const res = await fetch("/article/SKU123/image", { + method: "POST", + headers: { "Content-Type": "image/avif" }, + body: buf, + }); + return await res.json(); +})(); +``` + `fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, not a content hash, and is not accepted here. diff --git a/scripts/generateApiDocs.js b/scripts/generateApiDocs.js index df69003..f715d7d 100644 --- a/scripts/generateApiDocs.js +++ b/scripts/generateApiDocs.js @@ -20,8 +20,10 @@ const PUBLIC_PATHS = new Set(['/auth/login', '/auth/logout', '/health', '/v1/cli const OMITTED_ROUTES = new Set([ 'GET /article/export.zip', 'POST /article/{sku}/image', + 'POST /attributevalue/{id}/image', 'GET /auth/me', 'PUT /auth/selected-company', + 'POST /brand/{id}/image', 'GET /category/export.zip', 'DELETE /company/{id}', 'POST /company', @@ -95,6 +97,10 @@ const JS_FETCH_ONLY_ROUTES = [ 'GET /image/{fullHash}', 'GET /thumb/{fullHash}', 'POST /serper/image', + 'POST /article/{sku}/image', + 'POST /attributevalue/{id}/image', + 'POST /brand/{id}/image', + 'POST /category/{id}/image', ]; const spec = getOpenApiSpec(true); @@ -889,10 +895,9 @@ function renderJsFetchSection(allOperations) { lines.push('### Binary & streaming routes (reachable only via `fetch()` in javascript_execute)'); lines.push(''); lines.push( - 'These routes are NOT in the table above because their responses are binary or long-lived ' + - 'streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from ' + - 'inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write ' + - 'them to the VFS and embed them in your reply:', + 'These routes are NOT in the table above because their payloads (binary request body or binary ' + + 'response) or long-lived streams cannot be handled usefully by the `dashback_request` tool. They ARE ' + + 'reachable from inside `javascript_execute` with `fetch("/route")`:', ); lines.push(''); lines.push('| Method | Route | Returns | Notes |'); @@ -909,8 +914,24 @@ function renderJsFetchSection(allOperations) { '| `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. ' + 'Body: `{ url }`.', ); + lines.push( + '| `POST` | `/article/{sku}/image` | `201 { image: { picId, sort, full, thumb, hash, thumbHash } }` | ' + + 'Upload full-size AVIF or WebP image for article SKU. WebP is auto-converted to AVIF. Body: raw binary (`image/avif` or `image/webp`).', + ); + lines.push( + '| `POST` | `/attributevalue/{id}/image` | `201 { attributevalue: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | ' + + 'Upload or replace AVIF/WebP image for attribute value ID. Body: raw binary (`image/avif` or `image/webp`).', + ); + lines.push( + '| `POST` | `/brand/{id}/image` | `201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | ' + + 'Upload or replace AVIF/WebP image for brand ID. Body: raw binary (`image/avif` or `image/webp`).', + ); + lines.push( + '| `POST` | `/category/{id}/image` | `201 { category: { id, thumbW, thumbH, thumbHash } }` | ' + + 'Upload or replace AVIF/WebP image for category ID. Body: raw binary (`image/avif` or `image/webp`).', + ); lines.push(''); - lines.push('Save the bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`:'); + lines.push('Save downloaded bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`:'); lines.push(''); lines.push('```js'); lines.push('module.exports = (async () => {'); @@ -922,6 +943,20 @@ function renderJsFetchSection(allOperations) { lines.push('})();'); lines.push('```'); lines.push(''); + lines.push('Upload an image from the VFS:'); + lines.push(''); + lines.push('```js'); + lines.push('module.exports = (async () => {'); + lines.push(' const buf = fs.readFileSync("images/product.avif");'); + lines.push(' const res = await fetch("/article/SKU123/image", {'); + lines.push(' method: "POST",'); + lines.push(' headers: { "Content-Type": "image/avif" },'); + lines.push(' body: buf,'); + lines.push(' });'); + lines.push(' return await res.json();'); + lines.push('})();'); + lines.push('```'); + lines.push(''); lines.push( '`fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, ' + 'not a content hash, and is not accepted here.',