import * as db from '../../db/companysetting.js'; // #region Helpers const ALLOWED_KEYS = [ 'ui.theme', 'keys.tavily', 'keys.openrouter', 'keys.serper', 'systemprompt-prefix', 'systemprompt-suffix', 'default-model', 'modelslug', 'modelslugflag', 'modelreasoning', 'modelslugimage', ]; export const DEFAULT_SETTINGS = { 'modelslug': 'openai/gpt-6-luna', 'modelslugflag': 'floor', 'modelreasoning': 'medium', 'modelslugimage': 'meta/muse-image', }; const WRITEONLY_KEYS = new Set([ 'keys.tavily', 'keys.openrouter', 'keys.serper', ]); 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 }; } // #endregion /** * 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' */ // #region GET /company/setting 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) => { if (byKey.has(key)) { return shapeSetting(byKey.get(key)); } return shapeSetting({ key, value: DEFAULT_SETTINGS[key] ?? 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 }); } }); // #endregion /** * @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' */ // #region GET /company/setting/:key 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); // Stored setting that is frontend-visible if (setting && setting.frontend) { return res.status(200).json({ setting: shapeSetting(setting) }); } // If unset, but it's an allowed key with a default or allowed frontend key if (!setting && ALLOWED_KEYS.includes(key) && !WRITEONLY_KEYS.has(key)) { return res.status(200).json({ setting: shapeSetting({ key, value: DEFAULT_SETTINGS[key] ?? null }), }); } // Only frontend-visible settings are exposed; others are treated as absent. return res.status(404).json({ error: 'Company setting not found' }); } catch (error) { const status = settingErrorStatus(error); if (status === 500) console.error('Get company setting failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion /** * @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`, `modelslug`, * `modelslugflag`, `modelreasoning`, `modelslugimage`. * 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' */ // #region PUT /company/setting/:key 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' }); } if (!ALLOWED_KEYS.includes(key)) { return res.status(400).json({ error: 'Invalid setting' }); } const value = req.body?.value; const frontend = req.body?.frontend; if (typeof value !== 'string') { return res.status(400).json({ error: 'Invalid setting' }); } const trimmedVal = value.trim(); if (key === 'modelslugflag' && !['', 'floor', 'nitro'].includes(trimmedVal)) { return res.status(400).json({ error: 'Invalid setting' }); } if (key === 'modelreasoning' && !['none', 'low', 'medium', 'high'].includes(trimmedVal)) { return res.status(400).json({ error: 'Invalid setting' }); } let valToSave = value; if (key === 'modelslug' && !trimmedVal) { valToSave = DEFAULT_SETTINGS.modelslug; } const input = { key, value: valToSave }; if (typeof frontend === 'boolean') { input.frontend = frontend; } else if (!WRITEONLY_KEYS.has(key)) { input.frontend = true; } 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 }); } }); // #endregion }