From 2e6eb53918da555715ad651ab0048f57c4edac9c Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 25 Mar 2026 17:46:18 +0100 Subject: [PATCH] Enhance API key handling in settings and vocab services: Update key retrieval logic to improve validation and status reporting. Introduce new localization strings for key status messages in English, German, and Spanish. Update LanguageAssistantView to display key status dynamically, enhancing user feedback on API key management. --- backend/services/settingsService.js | 8 +++--- backend/services/vocabService.js | 3 +-- frontend/src/i18n/locales/de/settings.json | 5 +++- frontend/src/i18n/locales/en/settings.json | 7 ++++-- frontend/src/i18n/locales/es/settings.json | 5 +++- .../views/settings/LanguageAssistantView.vue | 25 +++++++++++++++++++ 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/backend/services/settingsService.js b/backend/services/settingsService.js index 1d3bafe..10ad35a 100644 --- a/backend/services/settingsService.js +++ b/backend/services/settingsService.js @@ -428,14 +428,16 @@ class SettingsService extends BaseService{ } } - const hasKey = Boolean(keyRow && keyRow.value && String(keyRow.value).trim()); + const hasStoredKey = Boolean(keyRow && keyRow.getDataValue('value') && String(keyRow.getDataValue('value')).trim()); + const hasReadableKey = Boolean(keyRow && keyRow.value && String(keyRow.value).trim()); return { enabled: parsed.enabled !== false, baseUrl: parsed.baseUrl || '', model: parsed.model || 'gpt-4o-mini', - hasKey, - keyLast4: parsed.keyLast4 || null + hasKey: hasStoredKey, + keyLast4: parsed.keyLast4 || null, + keyStatus: hasStoredKey ? (hasReadableKey ? 'stored' : 'invalid') : 'missing' }; } diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index cd46739..8dc01c2 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -12,7 +12,6 @@ import UserParam from '../models/community/user_param.js'; import { sequelize } from '../utils/sequelize.js'; import { notifyUser } from '../utils/socket.js'; import { Op } from 'sequelize'; -import { decrypt } from '../utils/encryption.js'; export default class VocabService { async _getUserByHashedId(hashedUserId) { @@ -56,7 +55,7 @@ export default class VocabService { } } - const decryptedKey = keyRow?.value ? decrypt(keyRow.value) : null; + const decryptedKey = keyRow?.value ? String(keyRow.value).trim() : null; const hasKey = Boolean(decryptedKey && String(decryptedKey).trim()); const enabled = parsed.enabled !== false; const baseUrl = String(parsed.baseUrl || '').trim(); diff --git a/frontend/src/i18n/locales/de/settings.json b/frontend/src/i18n/locales/de/settings.json index 1f9b868..01d5439 100644 --- a/frontend/src/i18n/locales/de/settings.json +++ b/frontend/src/i18n/locales/de/settings.json @@ -169,7 +169,10 @@ "save": "Speichern", "saved": "Einstellungen gespeichert.", "saveError": "Speichern fehlgeschlagen.", - "confirmClear": "API-Schlüssel wirklich löschen?" + "confirmClear": "API-Schlüssel wirklich löschen?", + "keyStatusStored": "API-Schlüssel gespeichert.", + "keyStatusInvalid": "Ein gespeicherter API-Schlüssel ist vorhanden, kann aber nicht gelesen werden. Bitte neu speichern.", + "keyStatusMissing": "Derzeit ist kein API-Schlüssel gespeichert." }, "interests": { "title": "Interessen", diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json index 053e7f0..d3a9f00 100644 --- a/frontend/src/i18n/locales/en/settings.json +++ b/frontend/src/i18n/locales/en/settings.json @@ -169,7 +169,10 @@ "save": "Save", "saved": "Settings saved.", "saveError": "Could not save.", - "confirmClear": "Really delete the API key?" + "confirmClear": "Really delete the API key?", + "keyStatusStored": "API key stored.", + "keyStatusInvalid": "A stored API key exists, but it cannot be read. Please save it again.", + "keyStatusMissing": "No API key is currently stored." }, "interests": { "title": "Interests", @@ -198,4 +201,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/src/i18n/locales/es/settings.json b/frontend/src/i18n/locales/es/settings.json index 09ea7f3..9055e60 100644 --- a/frontend/src/i18n/locales/es/settings.json +++ b/frontend/src/i18n/locales/es/settings.json @@ -169,7 +169,10 @@ "save": "Guardar", "saved": "Ajustes guardados.", "saveError": "No se pudo guardar.", - "confirmClear": "¿Eliminar realmente la clave API?" + "confirmClear": "¿Eliminar realmente la clave API?", + "keyStatusStored": "Clave API guardada.", + "keyStatusInvalid": "Existe una clave API guardada, pero no se puede leer. Guárdala de nuevo.", + "keyStatusMissing": "Actualmente no hay ninguna clave API guardada." }, "interests": { "title": "Intereses", diff --git a/frontend/src/views/settings/LanguageAssistantView.vue b/frontend/src/views/settings/LanguageAssistantView.vue index 4845cf4..71e7d06 100644 --- a/frontend/src/views/settings/LanguageAssistantView.vue +++ b/frontend/src/views/settings/LanguageAssistantView.vue @@ -51,6 +51,9 @@ :placeholder="apiKeyPlaceholder" /> {{ $t('settings.languageAssistant.apiKeyHint') }} + + {{ keyStatusLabel }} + @@ -84,6 +87,7 @@ export default { }, hasKey: false, keyLast4: null, + keyStatus: 'missing', saving: false, loadError: null }; @@ -100,6 +104,14 @@ export default { }); } return this.$t('settings.languageAssistant.apiKeyPlaceholderNew'); + }, + keyStatusLabel() { + const statusKey = { + stored: 'settings.languageAssistant.keyStatusStored', + invalid: 'settings.languageAssistant.keyStatusInvalid', + missing: 'settings.languageAssistant.keyStatusMissing' + }[this.keyStatus] || 'settings.languageAssistant.keyStatusMissing'; + return this.$t(statusKey); } }, async mounted() { @@ -115,6 +127,7 @@ export default { this.form.model = data.model || 'gpt-4o-mini'; this.hasKey = data.hasKey; this.keyLast4 = data.keyLast4; + this.keyStatus = data.keyStatus || (data.hasKey ? 'stored' : 'missing'); this.form.apiKey = ''; this.form.clearKey = false; } catch (e) { @@ -208,6 +221,18 @@ export default { font-size: 0.8rem; opacity: 0.8; } +.language-assistant-settings__key-status { + font-weight: 600; +} +.language-assistant-settings__key-status--stored { + color: #2e7d32; +} +.language-assistant-settings__key-status--invalid { + color: #c62828; +} +.language-assistant-settings__key-status--missing { + color: #8a6d3b; +} .language-assistant-settings__toggle { display: flex; align-items: center;