From cbb2fdeb6dc693be47205926cf412ff38c7b0d27 Mon Sep 17 00:00:00 2001 From: seb Date: Sat, 26 Sep 2026 14:42:29 +0200 Subject: [PATCH] u --- docs/api/openapi.json | 2 +- docs/api/routes/delivery/parse.md | 3 ++- src/api/routes/deliveryParse.js | 14 +++++++++++--- src/config/env.js | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 9320f84..90ab992 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -4609,7 +4609,7 @@ "Delivery" ], "summary": "Parse document (SSE)", - "description": "Accepts an invoice or delivery note as PDF or text file\n(`multipart/form-data`, field `file`) and streams extraction results as\nServer-Sent Events until parsing is complete.\n\nPDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.\nPages are separated by the form feed character (`\\f`). If no form feed\nis present, the input is treated as a single page.\n\nThe extracted pages are fed to an LLM (OpenRouter) which fills a structured\nmanifest of header data and line items. The OpenRouter API key is read from\nthe authenticated user's company setting `keys.openrouter`; the endpoint\nreturns `400` if the key is not configured.\n\nThe stream ends after the final `result` event (or an `error` event on failure).\n", + "description": "Accepts an invoice or delivery note as PDF or text file\n(`multipart/form-data`, field `file`) and streams extraction results as\nServer-Sent Events until parsing is complete.\n\nPDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.\nPages are separated by the form feed character (`\\f`). If no form feed\nis present, the input is treated as a single page.\n\nThe extracted pages are fed to an LLM (OpenRouter) which fills a structured\nmanifest of header data and line items. The OpenRouter API key is read from\nthe authenticated user's company setting `keys.openrouter` (or the server's\n`OPENROUTER_FREE_KEY` if not configured); the endpoint\nreturns `400` if the key is not configured.\n\nThe stream ends after the final `result` event (or an `error` event on failure).\n", "security": [ { "cookieAuth": [] diff --git a/docs/api/routes/delivery/parse.md b/docs/api/routes/delivery/parse.md index c8cf3f5..0b08503 100644 --- a/docs/api/routes/delivery/parse.md +++ b/docs/api/routes/delivery/parse.md @@ -14,7 +14,8 @@ is present, the input is treated as a single page. The extracted pages are fed to an LLM (OpenRouter) which fills a structured manifest of header data and line items. The OpenRouter API key is read from -the authenticated user's company setting `keys.openrouter`; the endpoint +the authenticated user's company setting `keys.openrouter` (or the server's +`OPENROUTER_FREE_KEY` if not configured); the endpoint returns `400` if the key is not configured. The stream ends after the final `result` event (or an `error` event on failure). diff --git a/src/api/routes/deliveryParse.js b/src/api/routes/deliveryParse.js index 28b91f3..b424dc7 100644 --- a/src/api/routes/deliveryParse.js +++ b/src/api/routes/deliveryParse.js @@ -5,6 +5,7 @@ import multer from 'multer'; import { LLMOrbiter, OpenRouterProvider, defineTool, DirectReturnError } from 'libllmorbiter'; import * as dbCompanySetting from '../../db/companysetting.js'; import * as dbSupplier from '../../db/supplier.js'; +import env from '../../config/env.js'; // ─── Multer — memory storage (no temp files); accepts PDF or text ─────────────── const upload = multer({ @@ -113,14 +114,20 @@ ${supplierList}`; * @param {string} opts.sourceLabel e.g. 'PDF' or 'Text' */ async function parseDocumentManifest({ req, res, pages, sourceLabel }) { - // ── Fetch OpenRouter API key from company settings ──────────────── + // ── Fetch OpenRouter API key from company settings (or fallback) ── let apiKey; try { const setting = await dbCompanySetting.getByKey(req.user.id, 'keys.openrouter', { maskWriteonly: false }); - apiKey = setting?.value; + apiKey = setting?.value?.trim(); } catch (err) { console.error('delivery parse: failed to read OpenRouter key:', err); } + if (!apiKey) { + apiKey = env.openrouterFreeKey?.trim(); + if (apiKey) { + console.log('delivery parse: using fallback OPENROUTER_FREE_KEY'); + } + } if (!apiKey) { return res.status(400).json({ error: 'OpenRouter API key not configured (companysetting: keys.openrouter)' }); } @@ -447,7 +454,8 @@ export function register(app, basePath = '') { * * The extracted pages are fed to an LLM (OpenRouter) which fills a structured * manifest of header data and line items. The OpenRouter API key is read from - * the authenticated user's company setting `keys.openrouter`; the endpoint + * the authenticated user's company setting `keys.openrouter` (or the server's + * `OPENROUTER_FREE_KEY` if not configured); the endpoint * returns `400` if the key is not configured. * * The stream ends after the final `result` event (or an `error` event on failure). diff --git a/src/config/env.js b/src/config/env.js index 4378b6a..727e1be 100644 --- a/src/config/env.js +++ b/src/config/env.js @@ -16,6 +16,7 @@ export default { implicitCreate: process.env.IMPLICIT_CREATE === 'true', jwtSecret: process.env.JWT_SECRET || 'fallback_secret_please_change_in_production', dbProvider: process.env.DB_PROVIDER || (process.env.SQLITE_PATH ? 'sqlite' : 'mssql'), + openrouterFreeKey: process.env.OPENROUTER_FREE_KEY, sqlite: { filename: process.env.SQLITE_PATH || process.env.SQLITE_FILENAME || resolve(__dirname, '../../data/dash.db'), },