From fcb75f412fafd767f3b07872d84c4233b3ea9d12 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 10 Feb 2025 14:28:50 +0530 Subject: [PATCH 1/5] feat: notify based on error status --- src/components/api/ApiKey.tsx | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index 9d54fe5c..e92bd368 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -43,8 +43,33 @@ const ApiKeyManager = () => { try { const { data } = await axios.get(`${apiUrl}/auth/api-key`); setApiKey(data.api_key); + notify('success', t('apikey.notifications.success.fetch')); } catch (error: any) { - notify('error', t('apikey.notifications.fetch_error', { error: error.message })); + const status = error.response?.status; + let errorKey = 'unknown'; + + switch (status) { + case 401: + errorKey = 'unauthorized'; + break; + case 404: + errorKey = 'not_found'; + break; + case 500: + errorKey = 'server'; + break; + default: + if (error.message?.includes('Network Error')) { + errorKey = 'network'; + } + } + + notify( + 'error', + t(`apikey.notifications.errors.fetch.${errorKey}`, { + error: error.response?.data?.message || error.message + }) + ); } finally { setLoading(false); } From 744778a4cecdfd77977bc0397aa7575a9b0eeb1d Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 10 Feb 2025 14:37:58 +0530 Subject: [PATCH 2/5] feat: return code for better error handling --- server/src/routes/auth.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index b7d884a4..39efbede 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -261,7 +261,11 @@ router.get( const authenticatedReq = req as AuthenticatedRequest; try { if (!authenticatedReq.user) { - return res.status(401).json({ ok: false, error: "Unauthorized" }); + return res.status(401).json({ + ok: false, + error: "Unauthorized", + code: "unauthorized" + }); } const user = await User.findByPk(authenticatedReq.user.id, { @@ -270,15 +274,25 @@ router.get( }); if (!user) { - return res.status(404).json({ message: "User not found" }); + return res.status(404).json({ + ok: false, + error: "User not found", + code: "not_found" + }); } return res.status(200).json({ + ok: true, message: "API key fetched successfully", api_key: user.api_key || null, }); } catch (error) { - return res.status(500).json({ message: "Error fetching API key", error }); + console.error('API Key fetch error:', error); + return res.status(500).json({ + ok: false, + error: "Error fetching API key", + code: "server", + }); } } ); From 85132c3d509e25d2b7a619432f6fe84ec0825a4a Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 10 Feb 2025 15:05:49 +0530 Subject: [PATCH 3/5] feat: add translation for api key notifications --- public/locales/de.json | 41 +++++++++++++++++++++++++++++++++++------ public/locales/en.json | 41 +++++++++++++++++++++++++++++++++++------ public/locales/es.json | 41 +++++++++++++++++++++++++++++++++++------ public/locales/ja.json | 41 +++++++++++++++++++++++++++++++++++------ public/locales/zh.json | 41 +++++++++++++++++++++++++++++++++++------ 5 files changed, 175 insertions(+), 30 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index a4c02b1d..aa25f0ce 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -139,12 +139,41 @@ "no_key_message": "Sie haben noch keinen API-Schlüssel generiert.", "generate_button": "API-Schlüssel generieren", "notifications": { - "fetch_error": "API-Schlüssel konnte nicht abgerufen werden - ${error}", - "generate_success": "API-Schlüssel erfolgreich generiert", - "generate_error": "API-Schlüssel konnte nicht generiert werden - ${error}", - "delete_success": "API-Schlüssel erfolgreich gelöscht", - "delete_error": "API-Schlüssel konnte nicht gelöscht werden - ${error}", - "copy_success": "API-Schlüssel erfolgreich kopiert" + "errors": { + "fetch": { + "network": "Netzwerkfehler beim Abrufen des API-Schlüssels: ${error}", + "unauthorized": "Sie müssen angemeldet sein, um auf den API-Schlüssel zuzugreifen", + "not_found": "API-Schlüssel für Ihr Konto wurde nicht gefunden", + "server": "Serverfehler beim Abrufen des API-Schlüssels. Bitte versuchen Sie es später erneut", + "unknown": "Unbekannter Fehler beim Abrufen des API-Schlüssels: ${error}" + }, + "generate": { + "network": "Netzwerkfehler bei der Generierung des API-Schlüssels: ${error}", + "unauthorized": "Sie müssen angemeldet sein, um einen API-Schlüssel zu generieren", + "key_exists": "Sie haben bereits einen API-Schlüssel. Bitte löschen Sie zuerst den vorhandenen", + "not_found": "Benutzerkonto nicht gefunden", + "server": "Serverfehler bei der Generierung des API-Schlüssels. Bitte versuchen Sie es später erneut", + "unknown": "Unbekannter Fehler bei der Generierung des API-Schlüssels: ${error}" + }, + "delete": { + "network": "Netzwerkfehler beim Löschen des API-Schlüssels: ${error}", + "unauthorized": "Sie müssen angemeldet sein, um den API-Schlüssel zu löschen", + "not_found": "Benutzerkonto nicht gefunden", + "key_not_found": "Kein API-Schlüssel zum Löschen gefunden", + "server": "Serverfehler beim Löschen des API-Schlüssels. Bitte versuchen Sie es später erneut", + "unknown": "Unbekannter Fehler beim Löschen des API-Schlüssels: ${error}" + }, + "copy": { + "failed": "Fehler beim Kopieren des API-Schlüssels in die Zwischenablage", + "no_key": "Kein API-Schlüssel zum Kopieren verfügbar" + } + }, + "success": { + "fetch": "API-Schlüssel erfolgreich abgerufen", + "generate": "Neuer API-Schlüssel erfolgreich generiert", + "delete": "API-Schlüssel erfolgreich gelöscht", + "copy": "API-Schlüssel in die Zwischenablage kopiert" + } } }, "action_description": { diff --git a/public/locales/en.json b/public/locales/en.json index f072dcc9..0cf1cf35 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -140,12 +140,41 @@ "no_key_message": "You haven't generated an API key yet.", "generate_button": "Generate API Key", "notifications": { - "fetch_error": "Failed to fetch API Key - ${error}", - "generate_success": "Generated API Key successfully", - "generate_error": "Failed to generate API Key - ${error}", - "delete_success": "API Key deleted successfully", - "delete_error": "Failed to delete API Key - ${error}", - "copy_success": "Copied API Key successfully" + "errors": { + "fetch": { + "network": "Network error while fetching API key: ${error}", + "unauthorized": "You must be logged in to access API key", + "not_found": "Unable to find API key for your account", + "server": "Server error while fetching API key. Please try again later", + "unknown": "Unknown error occurred while fetching API key: ${error}" + }, + "generate": { + "network": "Network error while generating API key: ${error}", + "unauthorized": "You must be logged in to generate an API key", + "key_exists": "You already have an API key. Please delete the existing one first", + "not_found": "User account not found", + "server": "Server error while generating API key. Please try again later", + "unknown": "Unknown error occurred while generating API key: ${error}" + }, + "delete": { + "network": "Network error while deleting API key: ${error}", + "unauthorized": "You must be logged in to delete API key", + "not_found": "User account not found", + "key_not_found": "No API key found to delete", + "server": "Server error while deleting API key. Please try again later", + "unknown": "Unknown error occurred while deleting API key: ${error}" + }, + "copy": { + "failed": "Failed to copy API key to clipboard", + "no_key": "No API key available to copy" + } + }, + "success": { + "fetch": "API key retrieved successfully", + "generate": "New API key generated successfully", + "delete": "API key deleted successfully", + "copy": "API key copied to clipboard" + } } }, "action_description": { diff --git a/public/locales/es.json b/public/locales/es.json index 09f6d703..fb0c4e54 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -140,12 +140,41 @@ "no_key_message": "Aún no has generado una clave API.", "generate_button": "Generar Clave API", "notifications": { - "fetch_error": "Error al obtener la clave API - ${error}", - "generate_success": "Clave API generada con éxito", - "generate_error": "Error al generar la clave API - ${error}", - "delete_success": "Clave API eliminada con éxito", - "delete_error": "Error al eliminar la clave API - ${error}", - "copy_success": "Clave API copiada con éxito" + "errors": { + "fetch": { + "network": "Error de red al obtener la clave API: ${error}", + "unauthorized": "Debes iniciar sesión para acceder a la clave API", + "not_found": "No se pudo encontrar la clave API para tu cuenta", + "server": "Error del servidor al obtener la clave API. Por favor, inténtalo más tarde", + "unknown": "Error desconocido al obtener la clave API: ${error}" + }, + "generate": { + "network": "Error de red al generar la clave API: ${error}", + "unauthorized": "Debes iniciar sesión para generar una clave API", + "key_exists": "Ya tienes una clave API. Por favor, elimina la existente primero", + "not_found": "Cuenta de usuario no encontrada", + "server": "Error del servidor al generar la clave API. Por favor, inténtalo más tarde", + "unknown": "Error desconocido al generar la clave API: ${error}" + }, + "delete": { + "network": "Error de red al eliminar la clave API: ${error}", + "unauthorized": "Debes iniciar sesión para eliminar la clave API", + "not_found": "Cuenta de usuario no encontrada", + "key_not_found": "No se encontró ninguna clave API para eliminar", + "server": "Error del servidor al eliminar la clave API. Por favor, inténtalo más tarde", + "unknown": "Error desconocido al eliminar la clave API: ${error}" + }, + "copy": { + "failed": "Error al copiar la clave API al portapapeles", + "no_key": "No hay clave API disponible para copiar" + } + }, + "success": { + "fetch": "Clave API obtenida exitosamente", + "generate": "Nueva clave API generada exitosamente", + "delete": "Clave API eliminada exitosamente", + "copy": "Clave API copiada al portapapeles" + } } }, "action_description": { diff --git a/public/locales/ja.json b/public/locales/ja.json index d2fe42ea..9f852836 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -140,12 +140,41 @@ "no_key_message": "APIキーはまだ生成されていません。", "generate_button": "APIキーを生成", "notifications": { - "fetch_error": "APIキーの取得に失敗しました - ${error}", - "generate_success": "APIキーが正常に生成されました", - "generate_error": "APIキーの生成に失敗しました - ${error}", - "delete_success": "APIキーが正常に削除されました", - "delete_error": "APIキーの削除に失敗しました - ${error}", - "copy_success": "APIキーがコピーされました" + "errors": { + "fetch": { + "network": "APIキーの取得中にネットワークエラーが発生しました:${error}", + "unauthorized": "APIキーにアクセスするにはログインが必要です", + "not_found": "アカウントのAPIキーが見つかりません", + "server": "APIキーの取得中にサーバーエラーが発生しました。後でもう一度お試しください", + "unknown": "APIキーの取得中に不明なエラーが発生しました:${error}" + }, + "generate": { + "network": "APIキーの生成中にネットワークエラーが発生しました:${error}", + "unauthorized": "APIキーを生成するにはログインが必要です", + "key_exists": "APIキーが既に存在します。既存のキーを先に削除してください", + "not_found": "ユーザーアカウントが見つかりません", + "server": "APIキーの生成中にサーバーエラーが発生しました。後でもう一度お試しください", + "unknown": "APIキーの生成中に不明なエラーが発生しました:${error}" + }, + "delete": { + "network": "APIキーの削除中にネットワークエラーが発生しました:${error}", + "unauthorized": "APIキーを削除するにはログインが必要です", + "not_found": "ユーザーアカウントが見つかりません", + "key_not_found": "削除するAPIキーが見つかりません", + "server": "APIキーの削除中にサーバーエラーが発生しました。後でもう一度お試しください", + "unknown": "APIキーの削除中に不明なエラーが発生しました:${error}" + }, + "copy": { + "failed": "APIキーのクリップボードへのコピーに失敗しました", + "no_key": "コピーできるAPIキーがありません" + } + }, + "success": { + "fetch": "APIキーの取得に成功しました", + "generate": "新しいAPIキーの生成に成功しました", + "delete": "APIキーの削除に成功しました", + "copy": "APIキーをクリップボードにコピーしました" + } } }, "action_description": { diff --git a/public/locales/zh.json b/public/locales/zh.json index e7c58660..80fec31e 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -140,12 +140,41 @@ "no_key_message": "您还未生成API密钥。", "generate_button": "生成API密钥", "notifications": { - "fetch_error": "获取API密钥失败 - ${error}", - "generate_success": "API密钥生成成功", - "generate_error": "生成API密钥失败 - ${error}", - "delete_success": "API密钥删除成功", - "delete_error": "删除API密钥失败 - ${error}", - "copy_success": "API密钥复制成功" + "errors": { + "fetch": { + "network": "获取API密钥时发生网络错误:${error}", + "unauthorized": "您必须登录才能访问API密钥", + "not_found": "找不到您账户的API密钥", + "server": "获取API密钥时发生服务器错误。请稍后重试", + "unknown": "获取API密钥时发生未知错误:${error}" + }, + "generate": { + "network": "生成API密钥时发生网络错误:${error}", + "unauthorized": "您必须登录才能生成API密钥", + "key_exists": "您已经有一个API密钥。请先删除现有的密钥", + "not_found": "找不到用户账户", + "server": "生成API密钥时发生服务器错误。请稍后重试", + "unknown": "生成API密钥时发生未知错误:${error}" + }, + "delete": { + "network": "删除API密钥时发生网络错误:${error}", + "unauthorized": "您必须登录才能删除API密钥", + "not_found": "找不到用户账户", + "key_not_found": "找不到要删除的API密钥", + "server": "删除API密钥时发生服务器错误。请稍后重试", + "unknown": "删除API密钥时发生未知错误:${error}" + }, + "copy": { + "failed": "复制API密钥到剪贴板失败", + "no_key": "没有可复制的API密钥" + } + }, + "success": { + "fetch": "成功获取API密钥", + "generate": "成功生成新的API密钥", + "delete": "成功删除API密钥", + "copy": "已将API密钥复制到剪贴板" + } } }, "action_description": { From ed5e96b476c8787aecdc1565c95d9bc2045dc7e9 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 10 Feb 2025 15:06:21 +0530 Subject: [PATCH 4/5] feat: better notifications for api key page --- src/components/api/ApiKey.tsx | 81 +++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/src/components/api/ApiKey.tsx b/src/components/api/ApiKey.tsx index e92bd368..5afc6da1 100644 --- a/src/components/api/ApiKey.tsx +++ b/src/components/api/ApiKey.tsx @@ -83,11 +83,36 @@ const ApiKeyManager = () => { setLoading(true); try { const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); - setApiKey(data.api_key); - - notify('success', t('apikey.notifications.generate_success')); + if (data.ok && data.api_key) { + setApiKey(data.api_key); + notify('success', t('apikey.notifications.success.generate')); + } } catch (error: any) { - notify('error', t('apikey.notifications.generate_error', { error: error.message })); + const status = error.response?.status; + let errorKey = 'unknown'; + + switch (status) { + case 401: + errorKey = 'unauthorized'; + break; + case 403: + errorKey = 'limit_reached'; + break; + case 500: + errorKey = 'server'; + break; + default: + if (error.message?.includes('Network Error')) { + errorKey = 'network'; + } + } + + notify( + 'error', + t(`apikey.notifications.errors.generate.${errorKey}`, { + error: error.response?.data?.message || error.message + }) + ); } finally { setLoading(false); } @@ -96,22 +121,54 @@ const ApiKeyManager = () => { const deleteApiKey = async () => { setLoading(true); try { - await axios.delete(`${apiUrl}/auth/delete-api-key`); - setApiKey(null); - notify('success', t('apikey.notifications.delete_success')); + const response = await axios.delete(`${apiUrl}/auth/delete-api-key`); + if (response.data.ok) { + setApiKey(null); + notify('success', t('apikey.notifications.success.delete')); + } } catch (error: any) { - notify('error', t('apikey.notifications.delete_error', { error: error.message })); + const status = error.response?.status; + let errorKey = 'unknown'; + + switch (status) { + case 401: + errorKey = 'unauthorized'; + break; + case 404: + errorKey = 'not_found'; + break; + case 500: + errorKey = 'server'; + break; + default: + if (error.message?.includes('Network Error')) { + errorKey = 'network'; + } + } + + notify( + 'error', + t(`apikey.notifications.errors.delete.${errorKey}`, { + error: error.response?.data?.message || error.message + }) + ); } finally { setLoading(false); } }; - const copyToClipboard = () => { - if (apiKey) { - navigator.clipboard.writeText(apiKey); + const copyToClipboard = async () => { + if (!apiKey) return; + + try { + await navigator.clipboard.writeText(apiKey); setCopySuccess(true); + notify('success', t('apikey.notifications.success.copy')); + + // Reset copy success state after 2 seconds setTimeout(() => setCopySuccess(false), 2000); - notify('info', t('apikey.notifications.copy_success')); + } catch (error) { + notify('error', t('apikey.notifications.errors.copy.failed')); } }; From d800158f9980f8369c40a2d328d16f78d1e51a26 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 10 Feb 2025 15:06:47 +0530 Subject: [PATCH 5/5] feat: add error codes for api key routes --- server/src/routes/auth.ts | 98 +++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 39efbede..0980b400 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -220,36 +220,56 @@ router.post( const authenticatedReq = req as AuthenticatedRequest; try { if (!authenticatedReq.user) { - return res.status(401).json({ ok: false, error: "Unauthorized" }); + return res.status(401).json({ + ok: false, + message: "Unauthorized", + code: "unauthorized" + }); } + const user = await User.findByPk(authenticatedReq.user.id, { attributes: { exclude: ["password"] }, }); if (!user) { - return res.status(404).json({ message: "User not found" }); + return res.status(404).json({ + ok: false, + message: "User not found", + code: "not_found" + }); } if (user.api_key) { - return res.status(400).json({ message: "API key already exists" }); + return res.status(400).json({ + ok: false, + message: "API key already exists", + code: "key_exists" + }); } - const apiKey = genAPIKey(); + const apiKey = genAPIKey(); await user.update({ api_key: apiKey }); + // Capture analytics event capture("maxun-oss-api-key-created", { user_id: user.id, created_at: new Date().toISOString(), }); return res.status(200).json({ + ok: true, message: "API key generated successfully", - api_key: apiKey, + api_key: apiKey }); + } catch (error) { - return res - .status(500) - .json({ message: "Error generating API key", error }); + console.error('API Key generation error:', error); + return res.status(500).json({ + ok: false, + message: "Error generating API key", + code: "server", + error: process.env.NODE_ENV === 'development' ? error : undefined + }); } } ); @@ -263,7 +283,7 @@ router.get( if (!authenticatedReq.user) { return res.status(401).json({ ok: false, - error: "Unauthorized", + message: "Unauthorized", code: "unauthorized" }); } @@ -276,7 +296,7 @@ router.get( if (!user) { return res.status(404).json({ ok: false, - error: "User not found", + message: "User not found", code: "not_found" }); } @@ -284,14 +304,16 @@ router.get( return res.status(200).json({ ok: true, message: "API key fetched successfully", - api_key: user.api_key || null, + api_key: user.api_key || null }); + } catch (error) { console.error('API Key fetch error:', error); return res.status(500).json({ ok: false, - error: "Error fetching API key", + message: "Error fetching API key", code: "server", + error: process.env.NODE_ENV === 'development' ? error : undefined }); } } @@ -302,33 +324,59 @@ router.delete( requireSignIn, async (req: Request, res) => { const authenticatedReq = req as AuthenticatedRequest; - if (!authenticatedReq.user) { - return res.status(401).send({ error: "Unauthorized" }); - } - try { - const user = await User.findByPk(authenticatedReq.user.id, { raw: true }); + if (!authenticatedReq.user) { + return res.status(401).json({ + ok: false, + message: "Unauthorized", + code: "unauthorized" + }); + } + + const user = await User.findByPk(authenticatedReq.user.id, { + raw: true, + attributes: ["id", "api_key"] + }); if (!user) { - return res.status(404).json({ message: "User not found" }); + return res.status(404).json({ + ok: false, + message: "User not found", + code: "not_found" + }); } if (!user.api_key) { - return res.status(404).json({ message: "API Key not found" }); + return res.status(404).json({ + ok: false, + message: "API Key not found", + code: "key_not_found" + }); } - await User.update({ api_key: null }, { where: { id: authenticatedReq.user.id } }); + await User.update( + { api_key: null }, + { where: { id: authenticatedReq.user.id } } + ); capture("maxun-oss-api-key-deleted", { user_id: user.id, deleted_at: new Date().toISOString(), }); - return res.status(200).json({ message: "API Key deleted successfully" }); - } catch (error: any) { - return res - .status(500) - .json({ message: "Error deleting API key", error: error.message }); + return res.status(200).json({ + ok: true, + message: "API Key deleted successfully" + }); + + } catch (error) { + console.error('API Key deletion error:', error); + return res.status(500).json({ + ok: false, + message: "Error deleting API key", + code: "server", + error: process.env.NODE_ENV === 'development' ? error : undefined + }); } } );