diff --git a/lib/api.js b/lib/api.js index 0b2df71..9cb3b53 100644 --- a/lib/api.js +++ b/lib/api.js @@ -478,8 +478,10 @@ function createExternalApi(deps) { // --- Settings ------------------------------------------------------ router.get('/settings', async (req, res) => { try { + // A user who never saved their personal details has no settings row; that + // is a valid state, so answer with an empty object instead of no body. const settings = await dbGet('SELECT * FROM settings WHERE user_id = ?', [uid()]); - res.json(settings); + res.json(settings || {}); } catch (error) { console.error('API get settings error:', error); res.status(500).json({ error: 'Serverfehler' }); @@ -489,9 +491,14 @@ function createExternalApi(deps) { router.put('/settings', async (req, res) => { try { const { name, adresse, kundennummer } = req.body || {}; + // Upsert, not UPDATE: a user without a settings row would otherwise match + // zero rows and the write would be silently dropped. await dbRun( - 'UPDATE settings SET name = ?, adresse = ?, kundennummer = ? WHERE user_id = ?', - [sanitizeInput(name), sanitizeInput(adresse), sanitizeInput(kundennummer), uid()] + `INSERT INTO settings (user_id, name, adresse, kundennummer) + VALUES (?, ?, ?, ?) + ON CONFLICT(user_id) DO UPDATE SET + name = excluded.name, adresse = excluded.adresse, kundennummer = excluded.kundennummer`, + [uid(), sanitizeInput(name), sanitizeInput(adresse), sanitizeInput(kundennummer)] ); res.json({ success: true }); } catch (error) { diff --git a/server.js b/server.js index 0816949..9955e9f 100644 --- a/server.js +++ b/server.js @@ -620,6 +620,22 @@ async function loadDesign() { } } +// The user's personal details (Persönliche Angaben). A user who has never saved +// the form has no settings row at all — that is a valid state, not an error, so +// this returns an empty object rather than undefined. Same contract as +// loadPrompts()/loadDesign(): "no row" means "nothing set yet". Every consumer +// (views, PDF generation, KI context) reads individual fields off the result, +// so they all degrade to empty instead of crashing. +async function loadSettings() { + try { + const row = await dbGet('SELECT * FROM settings WHERE user_id = ?', [currentUserId()]); + return row || {}; + } catch (e) { + console.error('Konnte Persönliche Angaben nicht laden:', e.message); + return {}; + } +} + // Match an incoming message to an application: first via In-Reply-To/References // pointing at one of our sent messages, then by sender = a previous recipient. async function matchBewerbung(msg) { @@ -779,7 +795,7 @@ async function runGeneration(bewerbungId, options = {}) { if (!bewerbung) return; const basisDokumente = await dbAll('SELECT * FROM basis_dokumente WHERE user_id = ? ORDER BY id ASC', [U]); const basisAnhaenge = await dbAll('SELECT * FROM basis_anhaenge WHERE user_id = ? ORDER BY id ASC', [U]); - const settings = await dbGet('SELECT * FROM settings WHERE user_id = ?', [U]); + const settings = await loadSettings(); const prompts = await loadPrompts(); const design = await loadDesign(); @@ -1317,8 +1333,8 @@ initializeDatabase().then(async () => { // Get settings app.get('/api/settings', async (req, res) => { try { - const settings = await dbGet('SELECT * FROM settings WHERE user_id = ?', [uid()]); - res.json(settings || {}); + const settings = await loadSettings(); + res.json(settings); } catch (error) { console.error('Error getting settings:', error); res.status(500).json({ error: 'Serverfehler' }); @@ -1817,7 +1833,7 @@ initializeDatabase().then(async () => { if (!bewerbung) return res.status(404).json({ error: 'Bewerbung nicht gefunden' }); const orig = await dbGet('SELECT * FROM emails WHERE id = ? AND bewerbung_id = ? AND user_id = ?', [req.body.email_id, id, uid()]); if (!orig) return res.status(404).json({ error: 'Nachricht nicht gefunden' }); - const settings = await dbGet('SELECT * FROM settings WHERE user_id = ?', [uid()]); + const settings = await loadSettings(); const draft = await generateEmailReply({ incoming: { from: orig.from_addr, subject: orig.subject, text: emailPlainText(orig) }, @@ -2136,7 +2152,7 @@ initializeDatabase().then(async () => { res.render('vorlagen', { basisDokumente, basisAnhaenge, - settings: await dbGet('SELECT * FROM settings WHERE user_id = ?', [uid()]), + settings: await loadSettings(), prompts: promptStore.list(await loadPrompts()), designFelder: designStore.list(design), designAngepasst: designStore.isAngepasst(design), @@ -2276,7 +2292,7 @@ initializeDatabase().then(async () => { // Only known design keys from the query are honoured; design.settings() // drops anything invalid. const design = { ...gespeichert, ...req.query }; - const settings = await dbGet('SELECT * FROM settings WHERE user_id = ?', [uid()]); + const settings = await loadSettings(); const pdfs = renderDesignVorschau({ settings, signatur: loadSignatur(), @@ -2956,7 +2972,7 @@ initializeDatabase().then(async () => { // assistant answer is streamed back via Server-Sent Events. async function gatherChatContext() { const [settings, profilRows, prompts] = await Promise.all([ - dbGet('SELECT name FROM settings WHERE user_id = ?', [uid()]), + loadSettings(), dbAll( `SELECT inhalt FROM basis_dokumente WHERE user_id = ? AND typ IN ('Lebenslauf', 'Profil/Kurzprofil') AND inhalt IS NOT NULL AND inhalt != '' @@ -2970,7 +2986,7 @@ initializeDatabase().then(async () => { // so the system prompt stays tiny regardless of how many bewerbungen exist. const profil = (profilRows.map((r) => (r.inhalt || '').trim()).join('\n\n---\n\n')).slice(0, 1800); const heute = new Date().toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); - return { heute, profil, prompts, settings: settings || {} }; + return { heute, profil, prompts, settings }; } // Ollama tool definitions the assistant can call to look up application data. @@ -3065,7 +3081,7 @@ initializeDatabase().then(async () => { b.interne_notizen, b.stellenbeschreibung, b.quelle_url, j.kontakt_email AS ja_kontakt, j.ansprechpartner AS ja_ansprech, j.beschreibung AS ja_beschreibung FROM bewerbungen b - LEFT JOIN jobangebote j ON j.verknuepfte_bewerbung_id = b.id + LEFT JOIN jobangebote j ON j.verknuepfte_bewerbung_id = b.id AND j.user_id = b.user_id WHERE b.id = ? AND b.user_id = ?`, [id, uid()] );