import * as db from '../../db/articleproperty.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 articlePropertyErrorStatus(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('Property not found')) return 404; if (error.message?.startsWith('Exclusive property cannot be overwritten')) return 409; if (error.message === 'Invalid properties array' || error.message === 'Invalid propertyId' || error.message === 'Duplicate propertyId') return 400; return 500; } /** * Registers articleproperty routes under `/article/:sku/property`. * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /article/{sku}/property: * get: * tags: [Article] * summary: List article properties * description: Fetches all free-form properties set on an article. * security: * - cookieAuth: [] * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * responses: * 200: * description: List of properties for this article * content: * application/json: * schema: * type: object * properties: * properties: * type: array * items: * $ref: '#/components/schemas/ArticleProperty' * put: * tags: [Article] * summary: Bulk set article properties * description: Replaces all properties for an article. * security: * - cookieAuth: [] * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [properties] * properties: * properties: * type: array * items: * type: object * required: [propertyId, value] * properties: * propertyId: * type: integer * value: * type: string * sort: * type: integer * responses: * 200: * description: Updated article properties * content: * application/json: * schema: * type: object * properties: * properties: * type: array * items: * $ref: '#/components/schemas/ArticleProperty' */ app.get(`${basePath}/article/:sku/property`, 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 properties = await db.listByArticleSku(req.user.id, skuParsed.sku); res.json({ properties }); } catch (error) { res.status(articlePropertyErrorStatus(error)).json({ error: error.message }); } }); app.put(`${basePath}/article/:sku/property`, 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 }); } const propertiesInput = req.body?.properties ?? req.body; if (!Array.isArray(propertiesInput)) { return res.status(400).json({ error: 'properties array required' }); } try { const properties = await db.setByArticleSku(req.user.id, skuParsed.sku, propertiesInput); emitArticleSignal(req, skuParsed.sku); res.json({ properties }); } catch (error) { res.status(articlePropertyErrorStatus(error)).json({ error: error.message }); } }); /** * @openapi * /article/{sku}/property/{propertyId}: * delete: * tags: [Article] * summary: Delete article property * description: Removes a single property from an article. * security: * - cookieAuth: [] * parameters: * - in: path * name: sku * required: true * schema: * type: string * - in: path * name: propertyId * required: true * schema: * type: integer * responses: * 200: * description: Deleted successfully * content: * application/json: * schema: * $ref: '#/components/schemas/SuccessResponse' */ app.delete(`${basePath}/article/:sku/property/:propertyId`, 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 propertyId = Number(req.params.propertyId); await db.remove(req.user.id, skuParsed.sku, propertyId); emitArticleSignal(req, skuParsed.sku); res.json({ ok: true }); } catch (error) { res.status(articlePropertyErrorStatus(error)).json({ error: error.message }); } }); }