Merge pull request #439 from getmaxun/error-handle
feat: better error messages
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -238,36 +238,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
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -279,7 +299,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,
|
||||
message: "Unauthorized",
|
||||
code: "unauthorized"
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findByPk(authenticatedReq.user.id, {
|
||||
@@ -288,15 +312,27 @@ router.get(
|
||||
});
|
||||
|
||||
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"
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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,
|
||||
message: "Error fetching API key",
|
||||
code: "server",
|
||||
error: process.env.NODE_ENV === 'development' ? error : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -306,33 +342,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
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -58,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);
|
||||
}
|
||||
@@ -71,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'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user