Compare commits
2 Commits
de306bb652
...
20fbea985e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20fbea985e | ||
|
|
d8b2524822 |
@@ -6,7 +6,7 @@ List supplier articles by SKUs
|
|||||||
|
|
||||||
Lists article-supplier assignments for the specified supplier (entity ID),
|
Lists article-supplier assignments for the specified supplier (entity ID),
|
||||||
optionally filtered by a list of SKUs (matching either article SKU or supplier SKU).
|
optionally filtered by a list of SKUs (matching either article SKU or supplier SKU).
|
||||||
Includes linked article details (name, slug, photos/articlepic, eans/articleean).
|
Includes linked article details (name, slug, photos, eans).
|
||||||
|
|
||||||
|
|
||||||
## Path parameters
|
## Path parameters
|
||||||
@@ -51,26 +51,14 @@ List of supplier articles
|
|||||||
"photos": [
|
"photos": [
|
||||||
{
|
{
|
||||||
"sort": 1,
|
"sort": 1,
|
||||||
"thumbhash": "…",
|
"fullHash": "…",
|
||||||
"hash": "…",
|
"thumbW": 1,
|
||||||
"thumbw": 1,
|
"thumbH": 1,
|
||||||
"thumbh": 1
|
"thumbHash": "…"
|
||||||
}
|
|
||||||
],
|
|
||||||
"articlepic": [
|
|
||||||
{
|
|
||||||
"sort": 1,
|
|
||||||
"thumbhash": "…",
|
|
||||||
"hash": "…",
|
|
||||||
"thumbw": 1,
|
|
||||||
"thumbh": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"eans": [
|
"eans": [
|
||||||
"…"
|
"…"
|
||||||
],
|
|
||||||
"articleean": [
|
|
||||||
"…"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Query supplier articles by SKUs (bulk)
|
|||||||
|
|
||||||
Queries article-supplier assignments for the specified supplier (entity ID) with a
|
Queries article-supplier assignments for the specified supplier (entity ID) with a
|
||||||
JSON list of SKUs in the request body, avoiding URL length limits for large SKU lists.
|
JSON list of SKUs in the request body, avoiding URL length limits for large SKU lists.
|
||||||
Includes linked article details (name, slug, photos/articlepic, eans/articleean).
|
Includes linked article details (name, slug, photos, eans).
|
||||||
|
|
||||||
|
|
||||||
## Path parameters
|
## Path parameters
|
||||||
@@ -60,26 +60,14 @@ List of supplier articles
|
|||||||
"photos": [
|
"photos": [
|
||||||
{
|
{
|
||||||
"sort": 1,
|
"sort": 1,
|
||||||
"thumbhash": "…",
|
"fullHash": "…",
|
||||||
"hash": "…",
|
"thumbW": 1,
|
||||||
"thumbw": 1,
|
"thumbH": 1,
|
||||||
"thumbh": 1
|
"thumbHash": "…"
|
||||||
}
|
|
||||||
],
|
|
||||||
"articlepic": [
|
|
||||||
{
|
|
||||||
"sort": 1,
|
|
||||||
"thumbhash": "…",
|
|
||||||
"hash": "…",
|
|
||||||
"thumbw": 1,
|
|
||||||
"thumbh": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"eans": [
|
"eans": [
|
||||||
"…"
|
"…"
|
||||||
],
|
|
||||||
"articleean": [
|
|
||||||
"…"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,18 +213,18 @@
|
|||||||
* nullable: true
|
* nullable: true
|
||||||
* SupplierArticlePhoto:
|
* SupplierArticlePhoto:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [sort, thumbhash, hash, thumbw, thumbh]
|
* required: [sort, fullHash, thumbW, thumbH, thumbHash]
|
||||||
* properties:
|
* properties:
|
||||||
* sort:
|
* sort:
|
||||||
* type: integer
|
* type: integer
|
||||||
* thumbhash:
|
* fullHash:
|
||||||
* type: string
|
* type: string
|
||||||
* hash:
|
* thumbW:
|
||||||
|
* type: integer
|
||||||
|
* thumbH:
|
||||||
|
* type: integer
|
||||||
|
* thumbHash:
|
||||||
* type: string
|
* type: string
|
||||||
* thumbw:
|
|
||||||
* type: integer
|
|
||||||
* thumbh:
|
|
||||||
* type: integer
|
|
||||||
* SupplierArticleInfo:
|
* SupplierArticleInfo:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [id, sku]
|
* required: [id, sku]
|
||||||
@@ -243,18 +243,10 @@
|
|||||||
* type: array
|
* type: array
|
||||||
* items:
|
* items:
|
||||||
* $ref: '#/components/schemas/SupplierArticlePhoto'
|
* $ref: '#/components/schemas/SupplierArticlePhoto'
|
||||||
* articlepic:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: '#/components/schemas/SupplierArticlePhoto'
|
|
||||||
* eans:
|
* eans:
|
||||||
* type: array
|
* type: array
|
||||||
* items:
|
* items:
|
||||||
* type: string
|
* type: string
|
||||||
* articleean:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* type: string
|
|
||||||
* SupplierArticle:
|
* SupplierArticle:
|
||||||
* allOf:
|
* allOf:
|
||||||
* - $ref: '#/components/schemas/ArticleSupplier'
|
* - $ref: '#/components/schemas/ArticleSupplier'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { validateObjects } from 'react-canvas-label-editor';
|
||||||
import * as db from '../../db/labeltemplate.js';
|
import * as db from '../../db/labeltemplate.js';
|
||||||
import { renderThumb } from '../../services/labelThumb.js';
|
import { renderThumb } from '../../services/labelThumb.js';
|
||||||
import { emit } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
@@ -29,6 +30,7 @@ function labeltemplateErrorStatus(error) {
|
|||||||
if (error.message?.startsWith('Invalid dpi')) return 400;
|
if (error.message?.startsWith('Invalid dpi')) return 400;
|
||||||
if (error.message?.startsWith('Invalid type')) return 400;
|
if (error.message?.startsWith('Invalid type')) return 400;
|
||||||
if (error.message === 'Content required') return 400;
|
if (error.message === 'Content required') return 400;
|
||||||
|
if (error.message?.startsWith('Invalid label objects')) return 400;
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +211,13 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const { name, width, height, dpi, type, content } = req.body ?? {};
|
const { name, width, height, dpi, type, content } = req.body ?? {};
|
||||||
|
|
||||||
|
const validation = validateLabelContent(content);
|
||||||
|
if (!validation.valid) {
|
||||||
|
return res.status(400).json({ error: validation.error, ...(validation.errors ? { errors: validation.errors } : {}) });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const thumb = await buildThumb({ width, height, content });
|
const thumb = await buildThumb({ width, height, content, objects: validation.objects });
|
||||||
const template = await db.create(req.user.id, {
|
const template = await db.create(req.user.id, {
|
||||||
name,
|
name,
|
||||||
width,
|
width,
|
||||||
@@ -242,9 +249,18 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
const { name, width, height, dpi, type, content } = req.body ?? {};
|
const { name, width, height, dpi, type, content } = req.body ?? {};
|
||||||
|
|
||||||
|
let validatedObjects;
|
||||||
|
if (content !== undefined) {
|
||||||
|
const validation = validateLabelContent(content);
|
||||||
|
if (!validation.valid) {
|
||||||
|
return res.status(400).json({ error: validation.error, ...(validation.errors ? { errors: validation.errors } : {}) });
|
||||||
|
}
|
||||||
|
validatedObjects = validation.objects;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Re-render thumb whenever content/dimensions are provided
|
// Re-render thumb whenever content/dimensions are provided
|
||||||
const thumb = await buildThumb({ width, height, content });
|
const thumb = await buildThumb({ width, height, content, objects: validatedObjects });
|
||||||
const template = await db.update(req.user.id, parsedId.id, {
|
const template = await db.update(req.user.id, parsedId.id, {
|
||||||
name,
|
name,
|
||||||
width,
|
width,
|
||||||
@@ -445,6 +461,45 @@ function serializeTemplate(template) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the label template content and its objects.
|
||||||
|
* Expects content to be an object with an `objects` array (or an array of objects).
|
||||||
|
*
|
||||||
|
* @param {any} content
|
||||||
|
* @returns {{ valid: true, objects: Array } | { valid: false, error: string, errors?: string[] }}
|
||||||
|
*/
|
||||||
|
function validateLabelContent(content) {
|
||||||
|
if (content == null) {
|
||||||
|
return { valid: false, error: 'Content required' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = content;
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(content);
|
||||||
|
} catch {
|
||||||
|
return { valid: false, error: 'Invalid JSON in content' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const objects = Array.isArray(parsed?.objects)
|
||||||
|
? parsed.objects
|
||||||
|
: Array.isArray(parsed)
|
||||||
|
? parsed
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!objects) {
|
||||||
|
return { valid: false, error: 'content.objects must be an array' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { valid, errors } = validateObjects(objects);
|
||||||
|
if (!valid) {
|
||||||
|
return { valid: false, error: `Invalid label objects: ${errors.join(', ')}`, errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true, objects };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to render a thumbnail from content + dimensions.
|
* Attempts to render a thumbnail from content + dimensions.
|
||||||
* Returns null silently if content or dimensions are missing/invalid —
|
* Returns null silently if content or dimensions are missing/invalid —
|
||||||
@@ -452,18 +507,20 @@ function serializeTemplate(template) {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<Buffer|null>}
|
* @returns {Promise<Buffer|null>}
|
||||||
*/
|
*/
|
||||||
async function buildThumb({ width, height, content }) {
|
async function buildThumb({ width, height, content, objects: passedObjects }) {
|
||||||
try {
|
try {
|
||||||
if (content == null) return null;
|
if (content == null && passedObjects == null) return null;
|
||||||
const w = Number(width);
|
const w = Number(width);
|
||||||
const h = Number(height);
|
const h = Number(height);
|
||||||
if (!Number.isInteger(w) || w <= 0 || !Number.isInteger(h) || h <= 0) return null;
|
if (!Number.isInteger(w) || w <= 0 || !Number.isInteger(h) || h <= 0) return null;
|
||||||
|
|
||||||
const objects = Array.isArray(content?.objects)
|
const objects = passedObjects ?? (
|
||||||
? content.objects
|
Array.isArray(content?.objects)
|
||||||
: Array.isArray(content)
|
? content.objects
|
||||||
? content
|
: Array.isArray(content)
|
||||||
: [];
|
? content
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
|
||||||
return await renderThumb({ width: w, height: h, objects });
|
return await renderThumb({ width: w, height: h, objects });
|
||||||
} catch {
|
} catch {
|
||||||
@@ -471,3 +528,4 @@ async function buildThumb({ width, height, content }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export function register(app, basePath = '') {
|
|||||||
* description: |
|
* description: |
|
||||||
* Lists article-supplier assignments for the specified supplier (entity ID),
|
* Lists article-supplier assignments for the specified supplier (entity ID),
|
||||||
* optionally filtered by a list of SKUs (matching either article SKU or supplier SKU).
|
* optionally filtered by a list of SKUs (matching either article SKU or supplier SKU).
|
||||||
* Includes linked article details (name, slug, photos/articlepic, eans/articleean).
|
* Includes linked article details (name, slug, photos, eans).
|
||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: supplier/articles.get.md
|
* x-markdown: supplier/articles.get.md
|
||||||
@@ -412,7 +412,7 @@ export function register(app, basePath = '') {
|
|||||||
* description: |
|
* description: |
|
||||||
* Queries article-supplier assignments for the specified supplier (entity ID) with a
|
* Queries article-supplier assignments for the specified supplier (entity ID) with a
|
||||||
* JSON list of SKUs in the request body, avoiding URL length limits for large SKU lists.
|
* JSON list of SKUs in the request body, avoiding URL length limits for large SKU lists.
|
||||||
* Includes linked article details (name, slug, photos/articlepic, eans/articleean).
|
* Includes linked article details (name, slug, photos, eans).
|
||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: supplier/articles.post.md
|
* x-markdown: supplier/articles.post.md
|
||||||
|
|||||||
Reference in New Issue
Block a user