import * as db from '../../db/company.js'; import * as googleAuth from '../../db/googleAuth.js'; import { clearCachedUser } from '../middleware/auth.js'; function userPayload(authRow) { return { id: authRow.id, email: authRow.email, selectedCompanyId: authRow.selectedCompanyId, }; } function parseCompanyBody(body) { const { iso639, name, taxrate, reducedtaxrate } = body ?? {}; if (!iso639 || !name || taxrate == null || reducedtaxrate == null) { return { error: 'iso639, name, taxrate, and reducedtaxrate are required' }; } if (typeof taxrate !== 'number' || !Number.isFinite(taxrate) || taxrate < 0) { return { error: 'taxrate must be a non-negative number' }; } if (typeof reducedtaxrate !== 'number' || !Number.isFinite(reducedtaxrate) || reducedtaxrate < 0) { return { error: 'reducedtaxrate must be a non-negative number' }; } return { iso639, name, taxrate, reducedtaxrate }; } function parseCompanyId(id) { const companyId = Number(id); if (!Number.isInteger(companyId) || companyId <= 0) { return { error: 'Invalid company id' }; } return { companyId }; } function companyErrorStatus(error) { if (error.message === 'Company not found') return 404; if (error.message === 'Company name not available') return 409; if (error.message === 'Invalid tax rate') return 400; return 500; } /** * Registers company CRUD 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 * /company: * get: * tags: [Company] * summary: List companies * description: Lists companies for the authenticated user. * security: * - cookieAuth: [] * x-markdown: company/list.md * responses: * 200: * description: Company list * content: * application/json: * schema: * type: object * properties: * companies: * type: array * items: * $ref: '#/components/schemas/Company' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 500: * description: Server error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * post: * tags: [Company] * summary: Create company * description: Creates a company and selects it for the current user. * security: * - cookieAuth: [] * x-markdown: company/create.md * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [iso639, name, taxrate, reducedtaxrate] * properties: * iso639: * type: string * name: * type: string * taxrate: * type: number * reducedtaxrate: * type: number * example: * iso639: de * name: Acme GmbH * taxrate: 19 * reducedtaxrate: 7 * responses: * 201: * description: Created; selected company updated * content: * application/json: * schema: * type: object * properties: * company: * $ref: '#/components/schemas/Company' * user: * $ref: '#/components/schemas/User' * 400: * description: Validation error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 409: * description: Company name not available * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.get(`${basePath}/company`, async (req, res) => { try { const companies = await db.listCompanies(req.user.id); res.status(200).json({ companies }); } catch (error) { console.error('List companies failed:', error); res.status(500).json({ error: 'Failed to list companies' }); } }); app.post(`${basePath}/company`, async (req, res) => { const body = parseCompanyBody(req.body); if (body.error) { return res.status(400).json({ error: body.error }); } try { const company = await db.createCompany(req.user.id, body); await googleAuth.setSelectedCompany(req.user.id, company.id); clearCachedUser(req.user.id); const authRow = await googleAuth.getAllowedEmail(req.user.email); res.status(201).json({ company, user: userPayload(authRow) }); } catch (error) { const status = companyErrorStatus(error); if (status === 500) console.error('Create company failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /company/{id}: * put: * tags: [Company] * summary: Update company * description: Updates a company. * security: * - cookieAuth: [] * x-markdown: company/update.md * parameters: * - in: path * name: id * required: true * schema: * type: integer * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [iso639, name, taxrate, reducedtaxrate] * properties: * iso639: * type: string * name: * type: string * taxrate: * type: number * reducedtaxrate: * type: number * responses: * 200: * description: Updated * content: * application/json: * schema: * type: object * properties: * company: * $ref: '#/components/schemas/Company' * 400: * description: Validation error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 404: * description: Company not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 409: * description: Company name not available * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * delete: * tags: [Company] * summary: Delete company * description: Deletes a company. * security: * - cookieAuth: [] * x-markdown: company/delete.md * parameters: * - in: path * name: id * required: true * schema: * type: integer * responses: * 204: * description: Deleted * 400: * description: Invalid id * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 401: * description: Unauthorized * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 500: * description: Server error * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.put(`${basePath}/company/:id`, async (req, res) => { const parsedId = parseCompanyId(req.params.id); if (parsedId.error) { return res.status(400).json({ error: parsedId.error }); } const body = parseCompanyBody(req.body); if (body.error) { return res.status(400).json({ error: body.error }); } try { const company = await db.updateCompany(parsedId.companyId, body); res.status(200).json({ company }); } catch (error) { const status = companyErrorStatus(error); if (status === 500) console.error('Update company failed:', error); res.status(status).json({ error: error.message }); } }); app.delete(`${basePath}/company/:id`, async (req, res) => { const parsedId = parseCompanyId(req.params.id); if (parsedId.error) { return res.status(400).json({ error: parsedId.error }); } try { await db.deleteCompany(parsedId.companyId); res.sendStatus(204); } catch (error) { console.error('Delete company failed:', error); res.status(500).json({ error: 'Failed to delete company' }); } }); }