import * as db from '../../db/articleattributevalue.js'; import { emit } from '../sse.js'; function requireSelectedCompany(req) { const companyId = req.user?.selectedCompanyId; if (companyId == null) { return { error: 'No company selected' }; } return { companyId }; } function parseSku(value) { const sku = typeof value === 'string' ? value.trim() : ''; if (!sku) return { error: 'sku is required' }; return { sku: decodeURIComponent(sku) }; } function emitArticleSignal(req, sku) { const companyId = req.user?.selectedCompanyId; if (companyId == null) return; emit(`article-${sku}`, { companyId }); } function articleAttributeValueErrorStatus(error) { if (error.message === 'No company selected') return 400; if (error.message === 'Not authorized') return 403; if (error.message === 'Company not found' || error.message === 'Article not found') return 404; if (error.message?.startsWith('Attribute value not found')) return 404; if (error.message?.includes('exclusive')) return 400; if (error.message?.includes('parent article') || error.message?.includes('Child article must have')) return 400; if (error.message === 'Invalid attribute values array' || error.message === 'Invalid attributeValueId' || error.message === 'Duplicate attributeValueId') return 400; return 500; } /** * Registers articleattributevalue routes under `/article/:sku/attributevalue`. * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /article/{sku}/attributevalue: * get: * tags: [Article] * summary: List article attribute values * description: Fetches all attribute values assigned to an article. * security: * - cookieAuth: [] * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * responses: * 200: * description: List of attribute values assigned to this article * content: * application/json: * schema: * type: object * properties: * attributevalues: * type: array * items: * $ref: '#/components/schemas/ArticleAttributeValue' * put: * tags: [Article] * summary: Bulk set article attribute values * description: Replaces all attribute values for an article. Enforces exclusive constraint (max one value per exclusive attribute). * security: * - cookieAuth: [] * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * requestBody: * required: true * content: * application/json: * schema: * type: array * description: Array of attribute value IDs. Array order defines sort. * items: * type: integer * example: [166] * responses: * 200: * description: Updated article attribute values * content: * application/json: * schema: * type: object * properties: * attributevalues: * type: array * items: * $ref: '#/components/schemas/ArticleAttributeValue' */ app.get(`${basePath}/article/:sku/attributevalue`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const skuParsed = parseSku(req.params.sku); if (skuParsed.error) { return res.status(400).json({ error: skuParsed.error }); } try { const attributevalues = await db.listByArticleSku(req.user.id, skuParsed.sku); res.json({ attributevalues }); } catch (error) { res.status(articleAttributeValueErrorStatus(error)).json({ error: error.message }); } }); app.put(`${basePath}/article/:sku/attributevalue`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const skuParsed = parseSku(req.params.sku); if (skuParsed.error) { return res.status(400).json({ error: skuParsed.error }); } if (!Array.isArray(req.body)) return res.status(400).json({ error: 'attributevalues array required' }); try { const attributevalues = await db.setByArticleSku(req.user.id, skuParsed.sku, req.body); emitArticleSignal(req, skuParsed.sku); res.json({ attributevalues }); } catch (error) { res.status(articleAttributeValueErrorStatus(error)).json({ error: error.message }); } }); }