u
This commit is contained in:
@@ -8949,7 +8949,7 @@
|
|||||||
"Category"
|
"Category"
|
||||||
],
|
],
|
||||||
"summary": "Add category image",
|
"summary": "Add category image",
|
||||||
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`).\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.\nAuth like GET /category/tree — user must be authorized for the category's company.\n",
|
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).\nWebP images are automatically converted to AVIF on the server.\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.\nAuth like GET /category/tree — user must be authorized for the category's company.\n",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"cookieAuth": []
|
"cookieAuth": []
|
||||||
@@ -8978,6 +8978,12 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "binary"
|
"format": "binary"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"image/webp": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10687,7 +10693,7 @@
|
|||||||
"Article"
|
"Article"
|
||||||
],
|
],
|
||||||
"summary": "Add article image",
|
"summary": "Add article image",
|
||||||
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`).\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.\nAuth like GET by SKU.\n",
|
"description": "Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).\nWebP images are automatically converted to AVIF on the server.\nFull: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.\nThumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.\nAuth like GET by SKU.\n",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"cookieAuth": []
|
"cookieAuth": []
|
||||||
@@ -10716,6 +10722,12 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "binary"
|
"format": "binary"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"image/webp": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
|||||||
|
|
||||||
Add article image
|
Add article image
|
||||||
|
|
||||||
Accepts a full-size AVIF body (`Content-Type: image/avif`).
|
Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).
|
||||||
|
WebP images are automatically converted to AVIF on the server.
|
||||||
Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
||||||
Thumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.
|
Thumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.
|
||||||
Auth like GET by SKU.
|
Auth like GET by SKU.
|
||||||
@@ -20,6 +21,8 @@ Auth like GET by SKU.
|
|||||||
|
|
||||||
Raw binary (`image/avif`).
|
Raw binary (`image/avif`).
|
||||||
|
|
||||||
|
Raw binary (`image/webp`).
|
||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
|||||||
|
|
||||||
Add category image
|
Add category image
|
||||||
|
|
||||||
Accepts a full-size AVIF body (`Content-Type: image/avif`).
|
Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).
|
||||||
|
WebP images are automatically converted to AVIF on the server.
|
||||||
Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
||||||
Thumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.
|
Thumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.
|
||||||
Auth like GET /category/tree — user must be authorized for the category's company.
|
Auth like GET /category/tree — user must be authorized for the category's company.
|
||||||
@@ -20,6 +21,8 @@ Auth like GET /category/tree — user must be authorized for the category's comp
|
|||||||
|
|
||||||
Raw binary (`image/avif`).
|
Raw binary (`image/avif`).
|
||||||
|
|
||||||
|
Raw binary (`image/webp`).
|
||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
On success, emits SSE signal: `category` (see [`GET /events`](../events.md)).
|
On success, emits SSE signal: `category` (see [`GET /events`](../events.md)).
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ function parseFullHash(value) {
|
|||||||
|
|
||||||
function articleErrorStatus(error) {
|
function articleErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Unsupported image format' || error.message?.includes('unsupported image format')) return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
if (error.message === 'Parent not found') return 404;
|
if (error.message === 'Parent not found') return 404;
|
||||||
if (error.message === 'Article not found') return 404;
|
if (error.message === 'Article not found') return 404;
|
||||||
@@ -738,7 +739,8 @@ export function register(app, basePath = '') {
|
|||||||
* tags: [Article]
|
* tags: [Article]
|
||||||
* summary: Add article image
|
* summary: Add article image
|
||||||
* description: |
|
* description: |
|
||||||
* Accepts a full-size AVIF body (`Content-Type: image/avif`).
|
* Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).
|
||||||
|
* WebP images are automatically converted to AVIF on the server.
|
||||||
* Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
* Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
||||||
* Thumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.
|
* Thumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.
|
||||||
* Auth like GET by SKU.
|
* Auth like GET by SKU.
|
||||||
@@ -760,6 +762,10 @@ export function register(app, basePath = '') {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* format: binary
|
* format: binary
|
||||||
|
* image/webp:
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* format: binary
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Image stored and linked
|
* description: Image stored and linked
|
||||||
@@ -797,7 +803,7 @@ export function register(app, basePath = '') {
|
|||||||
*/
|
*/
|
||||||
app.post(
|
app.post(
|
||||||
`${basePath}/article/:sku/image`,
|
`${basePath}/article/:sku/image`,
|
||||||
express.raw({ type: ['image/avif', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
express.raw({ type: ['image/avif', 'image/webp', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : '';
|
const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : '';
|
||||||
if (!sku) {
|
if (!sku) {
|
||||||
@@ -806,7 +812,7 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
||||||
if (!imageBuffer?.length) {
|
if (!imageBuffer?.length) {
|
||||||
return res.status(400).json({ error: 'AVIF image body is required' });
|
return res.status(400).json({ error: 'AVIF or WebP image body is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ function requireSelectedCompany(req) {
|
|||||||
|
|
||||||
function brandErrorStatus(error) {
|
function brandErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Unsupported image format' || error.message?.includes('unsupported image format')) return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
if (error.message === 'Company not found') return 404;
|
if (error.message === 'Company not found') return 404;
|
||||||
if (error.message === 'Brand not found') return 404;
|
if (error.message === 'Brand not found') return 404;
|
||||||
@@ -244,7 +245,7 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
`${basePath}/brand/:id/image`,
|
`${basePath}/brand/:id/image`,
|
||||||
express.raw({ type: ['image/avif', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
express.raw({ type: ['image/avif', 'image/webp', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const selected = requireSelectedCompany(req);
|
const selected = requireSelectedCompany(req);
|
||||||
if (selected.error) {
|
if (selected.error) {
|
||||||
@@ -258,7 +259,7 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
||||||
if (!imageBuffer?.length) {
|
if (!imageBuffer?.length) {
|
||||||
return res.status(400).json({ error: 'AVIF image body is required' });
|
return res.status(400).json({ error: 'AVIF or WebP image body is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ function parsePositiveInt(value, field) {
|
|||||||
|
|
||||||
function categoryErrorStatus(error) {
|
function categoryErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Unsupported image format' || error.message?.includes('unsupported image format')) return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
if (error.message === 'Company not found') return 404;
|
if (error.message === 'Company not found') return 404;
|
||||||
if (error.message === 'Parent not found') return 404;
|
if (error.message === 'Parent not found') return 404;
|
||||||
@@ -647,7 +648,8 @@ export function register(app, basePath = '') {
|
|||||||
* tags: [Category]
|
* tags: [Category]
|
||||||
* summary: Add category image
|
* summary: Add category image
|
||||||
* description: |
|
* description: |
|
||||||
* Accepts a full-size AVIF body (`Content-Type: image/avif`).
|
* Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).
|
||||||
|
* WebP images are automatically converted to AVIF on the server.
|
||||||
* Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
* Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
||||||
* Thumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.
|
* Thumb always scaled to max 200×200. Stores hash/thumbhash, links `categorypic`.
|
||||||
* Auth like GET /category/tree — user must be authorized for the category's company.
|
* Auth like GET /category/tree — user must be authorized for the category's company.
|
||||||
@@ -669,6 +671,10 @@ export function register(app, basePath = '') {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* format: binary
|
* format: binary
|
||||||
|
* image/webp:
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* format: binary
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Image stored and linked
|
* description: Image stored and linked
|
||||||
@@ -772,7 +778,7 @@ export function register(app, basePath = '') {
|
|||||||
*/
|
*/
|
||||||
app.post(
|
app.post(
|
||||||
`${basePath}/category/:id/image`,
|
`${basePath}/category/:id/image`,
|
||||||
express.raw({ type: ['image/avif', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
express.raw({ type: ['image/avif', 'image/webp', 'application/octet-stream'], limit: AVIF_BODY_LIMIT }),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const parsed = parsePositiveInt(req.params.id, 'id');
|
const parsed = parsePositiveInt(req.params.id, 'id');
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
@@ -781,7 +787,7 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
const imageBuffer = Buffer.isBuffer(req.body) ? req.body : null;
|
||||||
if (!imageBuffer?.length) {
|
if (!imageBuffer?.length) {
|
||||||
return res.status(400).json({ error: 'AVIF image body is required' });
|
return res.status(400).json({ error: 'AVIF or WebP image body is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user