import * as db from '../../db/article.js'; function parsePositiveInt(value, field) { const id = Number(value); if (!Number.isInteger(id) || id <= 0) { return { error: `${field} is required` }; } return { id }; } function articleErrorStatus(error) { if (error.message === 'Not authorized') return 403; if (error.message === 'Parent not found') return 404; if (error.message === 'Article not found') return 404; if (error.message === 'Name required') return 400; return 500; } /** * Registers article routes (requires auth middleware). * @param {import('express').Application} app - Express app * @param {string} [basePath=''] - Optional URL prefix for all routes in this module */ export function register(app, basePath = '') { /** * @openapi * /article: * post: * tags: [Article] * summary: Create article * description: Creates an article under an authorized category (`parentId`). * security: * - cookieAuth: [] * x-markdown: article/create.md * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [parentId, name] * properties: * parentId: * type: integer * description: Category id * name: * type: string * example: * parentId: 10 * name: New article * responses: * 201: * description: Created * content: * application/json: * schema: * type: object * properties: * article: * $ref: '#/components/schemas/ArticleSummary' * 400: * description: Validation error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 403: * description: Not authorized for parent category * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 404: * description: Parent not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.post(`${basePath}/article`, async (req, res) => { const parent = parsePositiveInt(req.body?.parentId, 'parentId'); if (parent.error) { return res.status(400).json({ error: parent.error }); } const name = typeof req.body?.name === 'string' ? req.body.name.trim() : ''; if (!name) { return res.status(400).json({ error: 'name is required' }); } try { const article = await db.createUnderParent({ googleAuthId: req.user.id, parentId: parent.id, name, }); res.status(201).json({ article }); } catch (error) { const status = articleErrorStatus(error); if (status === 500) console.error('Create article failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /article/{sku}: * get: * tags: [Article] * summary: Get article by SKU * description: Returns article detail by SKU if the user may access its category. * security: * - cookieAuth: [] * x-markdown: article/get.md * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * responses: * 200: * description: Article detail * content: * application/json: * schema: * type: object * properties: * article: * $ref: '#/components/schemas/ArticleDetail' * 400: * description: Missing sku * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 403: * description: Not authorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 404: * description: Article not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * patch: * tags: [Article] * summary: Update article by SKU * description: Updates an article name by SKU; returns the same detail shape as GET. * security: * - cookieAuth: [] * x-markdown: article/update.md * parameters: * - in: path * name: sku * required: true * schema: * type: string * description: Article SKU * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [name] * properties: * name: * type: string * example: * name: Updated name * responses: * 200: * description: Updated article detail * content: * application/json: * schema: * type: object * properties: * article: * $ref: '#/components/schemas/ArticleDetail' * 400: * description: Validation error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 403: * description: Not authorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 404: * description: Article not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.get(`${basePath}/article/:sku`, async (req, res) => { const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : ''; if (!sku) { return res.status(400).json({ error: 'sku is required' }); } try { const article = await db.getBySku(req.user.id, decodeURIComponent(sku)); res.status(200).json({ article }); } catch (error) { const status = articleErrorStatus(error); if (status === 500) console.error('Get article by sku failed:', error); res.status(status).json({ error: error.message }); } }); app.patch(`${basePath}/article/:sku`, async (req, res) => { const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : ''; if (!sku) { return res.status(400).json({ error: 'sku is required' }); } const name = typeof req.body?.name === 'string' ? req.body.name.trim() : ''; if (!name) { return res.status(400).json({ error: 'name is required' }); } try { const article = await db.updateBySku(req.user.id, decodeURIComponent(sku), { name }); res.status(200).json({ article }); } catch (error) { const status = articleErrorStatus(error); if (status === 500) console.error('Update article by sku failed:', error); res.status(status).json({ error: error.message }); } }); }