import * as db from '../../db/companysetting.js'; const ALLOWED_KEYS = [ 'ui.theme', 'keys.tavily', 'keys.openrouter', 'keys.serper', 'systemprompt-prefix', 'systemprompt-suffix', 'default-model', ]; function requireSelectedCompany(req) { const companyId = req.user.selectedCompanyId; if (companyId == null) { return { error: 'No company selected' }; } return { companyId }; } function settingErrorStatus(error) { if (error.message === 'No company selected') return 400; if (error.message === 'Not authorized') return 403; if (error.message === 'Company not found') return 404; if (error.message === 'Invalid setting') return 400; return 500; } /** * Shapes a setting for the API response, stripping the internal `frontend` flag. * @param {{key: string, value: string|null, frontend?: boolean}|undefined} s * @returns {{key: string, value: string|null}} */ function shapeSetting(s) { return { key: s.key, value: s.value ?? null }; } /** * Registers company-settings routes for the authenticated user's selected company. * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /company/setting: * get: * tags: [CompanySetting] * summary: List company settings * description: | * Returns the visible settings of the authenticated user's selected * company: the full set of allowed keys (with `value: null` when unset) * plus any stored settings that are frontend-visible. The `frontend` * flag is stripped from responses. * security: * - cookieAuth: [] * x-markdown: companysetting/list.md * responses: * 200: * description: Company settings * content: * application/json: * schema: * type: object * properties: * settings: * type: array * items: * $ref: '#/components/schemas/CompanySetting' * 400: * description: No company selected * 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: Company not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.get(`${basePath}/company/setting`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } try { const stored = await db.list(req.user.id, {}); const byKey = new Map(stored.map((s) => [s.key, s])); // Visible = named/allowed keys plus any stored setting with frontend=true. const visibleKeys = new Set(ALLOWED_KEYS); for (const s of stored) { if (s.frontend) visibleKeys.add(s.key); } const settings = [...visibleKeys].map((key) => byKey.has(key) ? shapeSetting(byKey.get(key)) : shapeSetting({ key, value: null }) ); res.status(200).json({ settings }); } catch (error) { const status = settingErrorStatus(error); if (status === 500) console.error('List company settings failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /company/setting/{key}: * get: * tags: [CompanySetting] * summary: Get company setting by key * description: | * Returns a single company setting for the authenticated user's selected * company, or `404` if it does not exist. * security: * - cookieAuth: [] * x-markdown: companysetting/get.md * parameters: * - in: path * name: key * required: true * schema: * type: string * responses: * 200: * description: Company setting * content: * application/json: * schema: * type: object * properties: * setting: * $ref: '#/components/schemas/CompanySetting' * 400: * description: No company selected * 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: Company setting or company not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.get(`${basePath}/company/setting/:key`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const key = String(req.params.key ?? ''); if (!key.trim()) { return res.status(400).json({ error: 'key is required' }); } try { const setting = await db.getByKey(req.user.id, key); // Only frontend-visible settings are exposed; others are treated as absent. if (!setting || !setting.frontend) { return res.status(404).json({ error: 'Company setting not found' }); } res.status(200).json({ setting: shapeSetting(setting) }); } catch (error) { const status = settingErrorStatus(error); if (status === 500) console.error('Get company setting failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /company/setting/{key}: * put: * tags: [CompanySetting] * summary: Upsert company setting * description: | * Sets (upserts) a company setting for the selected company. * Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`, * `keys.openrouter`, `keys.serper`, `systemprompt-prefix`, * `systemprompt-suffix`, `default-model`. * security: * - cookieAuth: [] * x-markdown: companysetting/set.md * parameters: * - in: path * name: key * required: true * schema: * type: string * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [value] * properties: * value: * type: string * frontend: * type: boolean * example: * value: dark * frontend: true * responses: * 200: * description: Upserted company setting * content: * application/json: * schema: * type: object * properties: * setting: * $ref: '#/components/schemas/CompanySetting' * 400: * description: Invalid setting, invalid key, or no company selected * 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: Company not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ app.put(`${basePath}/company/setting/:key`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const key = String(req.params.key ?? ''); if (!key.trim()) { return res.status(400).json({ error: 'key is required' }); } const value = req.body?.value; const frontend = req.body?.frontend; const input = { key }; if (typeof value === 'string') { input.value = value; } if (typeof frontend === 'boolean') { input.frontend = frontend; } try { const setting = await db.set(req.user.id, input); res.status(200).json({ setting: shapeSetting(setting) }); } catch (error) { const status = settingErrorStatus(error); if (status === 500) console.error('Set company setting failed:', error); res.status(status).json({ error: error.message }); } }); }