Refactor error handling in routes to use centralized error creation. Added localized error messages for IP address and email validation in respective routes. Improved session retrieval error handling.

This commit is contained in:
2026-01-13 16:16:01 +05:00
parent b6ea9107e8
commit 6a5754e8c2
5 changed files with 210 additions and 13 deletions
+8 -2
View File
@@ -1,5 +1,6 @@
import { Router } from "express";
import ActiveSession from "../models/ActiveSession";
import { createError, ErrorCode } from "../utils/i18n";
// import { isValidObjectId } from "mongoose";
const router = Router();
@@ -8,18 +9,23 @@ router.get("/:id", async (req, res) => {
const activeSessionId = req.params.id;
// if (!isValidObjectId(activeSessionId)) {
// return res.json({ status: "error", message: "Invalid ObjectId" });
// return res.json(createError(ErrorCode.INVALID_OBJECT_ID, req));
// }
try {
const activeSession = await ActiveSession.findById(activeSessionId);
if (!activeSession) {
return res.json(createError(ErrorCode.SESSION_NOT_FOUND, req));
}
await ActiveSession.findByIdAndUpdate(activeSessionId, {
updatedAt: new Date(),
});
res.json(activeSession);
} catch (error) {
res.json({ error: (error as Error).message });
res.json(createError(ErrorCode.SESSION_FETCH_ERROR, req));
}
});
+5 -5
View File
@@ -1,15 +1,15 @@
import { Router } from "express";
import got from "got-cjs";
import { createError, ErrorCode } from "../utils/i18n";
const router = Router();
router.get("/", async (req, res) => {
const ip = req.headers["x-real-ip"];
if (!ip)
return res.json({
error: "An error occurred while obtaining an IP address",
});
if (!ip) {
return res.json(createError(ErrorCode.IP_ADDRESS_ERROR, req));
}
try {
const { countryCode }: { countryCode: string } = await got
@@ -18,7 +18,7 @@ router.get("/", async (req, res) => {
res.json({ countryCode });
} catch (error) {
res.json({ error: (error as Error).message });
res.json(createError(ErrorCode.COUNTRY_CODE_FETCH_ERROR, req));
}
});
+34 -7
View File
@@ -1,5 +1,6 @@
import { Router } from "express";
import nodemailer from "nodemailer";
import { createError, ErrorCode, getLanguageFromRequest } from "../utils/i18n";
const router = Router();
@@ -9,6 +10,33 @@ router.post("/", async (req, res) => {
console.log("email", email);
console.log("link", link);
// Валидация входных данных
if (!email) {
return res.json(createError(ErrorCode.EMAIL_REQUIRED, req));
}
if (!link) {
return res.json(createError(ErrorCode.LINK_REQUIRED, req));
}
const lang = getLanguageFromRequest(req);
// Локализованные тексты письма
const emailContent = {
EN: {
subject: "Invitation to demonstration - stream.graff.tech",
body: `<div>
Link to connect to the demonstration: <a href="${link}" target="_blank">${link}</a>
</div>`,
},
RU: {
subject: "Приглашение на демонстрацию - stream.graff.tech",
body: `<div>
Ссылка для подключения к демонстрации: <a href="${link}" target="_blank">${link}</a>
</div>`,
},
};
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "mail.netangels.ru",
@@ -25,16 +53,15 @@ router.post("/", async (req, res) => {
await transporter.sendMail({
from: "stream@graff.tech", // sender address
to: email, // list of receivers
subject: "Приглашение на демонстрацию - stream.graff.tech", // Subject line
html: `<div>
Ссылка для подключения к демонстрации: <a href="${link}" target="_blank">${link}</a>
</div>`,
subject: emailContent[lang].subject, // Subject line
html: emailContent[lang].body,
});
} catch (error) {
console.log("error", (error as Error).message);
}
res.json({ ok: 1 });
} catch (error) {
console.log("error", (error as Error).message);
res.json(createError(ErrorCode.EMAIL_SEND_ERROR, req));
}
});
const sendInviteRouter = router;
+38
View File
@@ -0,0 +1,38 @@
/**
* Коды ошибок API
* Этот файл можно скопировать на клиент для типизации ошибок
*/
export enum ErrorCode {
// General errors
INTERNAL_ERROR = "INTERNAL_ERROR",
// Active Session errors
INVALID_OBJECT_ID = "INVALID_OBJECT_ID",
SESSION_NOT_FOUND = "SESSION_NOT_FOUND",
SESSION_FETCH_ERROR = "SESSION_FETCH_ERROR",
// Country Code errors
IP_ADDRESS_ERROR = "IP_ADDRESS_ERROR",
COUNTRY_CODE_FETCH_ERROR = "COUNTRY_CODE_FETCH_ERROR",
// Email/Invite errors
EMAIL_REQUIRED = "EMAIL_REQUIRED",
LINK_REQUIRED = "LINK_REQUIRED",
EMAIL_SEND_ERROR = "EMAIL_SEND_ERROR",
}
/**
* Интерфейс ответа с ошибкой
*/
export interface ErrorResponse {
error: string;
errorCode: ErrorCode;
}
/**
* Type guard для проверки, является ли ответ ошибкой
*/
export function isErrorResponse(response: any): response is ErrorResponse {
return response && typeof response.error === "string" && typeof response.errorCode === "string";
}
+126
View File
@@ -0,0 +1,126 @@
import { Request } from "express";
import { ErrorCode, ErrorResponse } from "../types/errorCodes";
type Language = "RU" | "EN";
interface Translations {
[key: string]: {
EN: string;
RU: string;
};
}
const translations: Translations = {
// General errors
INTERNAL_ERROR: {
EN: "Internal server error",
RU: "Внутренняя ошибка сервера",
},
// Active Session errors
INVALID_OBJECT_ID: {
EN: "Invalid ObjectId",
RU: "Неверный идентификатор объекта",
},
SESSION_NOT_FOUND: {
EN: "Session not found",
RU: "Сессия не найдена",
},
SESSION_FETCH_ERROR: {
EN: "Error fetching session",
RU: "Ошибка получения сессии",
},
// Country Code errors
IP_ADDRESS_ERROR: {
EN: "An error occurred while obtaining an IP address",
RU: "Произошла ошибка при получении IP-адреса",
},
COUNTRY_CODE_FETCH_ERROR: {
EN: "Error fetching country code",
RU: "Ошибка получения кода страны",
},
// Email/Invite errors
EMAIL_REQUIRED: {
EN: "Email is required",
RU: "Требуется адрес электронной почты",
},
LINK_REQUIRED: {
EN: "Link is required",
RU: "Требуется ссылка",
},
EMAIL_SEND_ERROR: {
EN: "Error sending email",
RU: "Ошибка отправки письма",
},
EMAIL_SENT_SUCCESS: {
EN: "Invitation sent successfully",
RU: "Приглашение успешно отправлено",
},
};
/**
* Получает язык из заголовка X-User-Region
* @param req - Express Request объект
* @returns Код языка (EN или RU)
*/
export function getLanguageFromRequest(req: Request): Language {
const region = req.headers["x-user-region"] as string;
// Поддерживаемые русскоязычные регионы
const russianRegions = ["RU", "BY", "KZ", "UA"];
if (region && russianRegions.includes(region.toUpperCase())) {
return "RU";
}
return "EN";
}
/**
* Получает переведенное сообщение
* @param key - Ключ сообщения из translations
* @param req - Express Request объект
* @returns Переведенное сообщение
*/
export function t(key: string, req: Request): string {
const lang = getLanguageFromRequest(req);
if (translations[key]) {
return translations[key][lang];
}
// Возвращаем ключ, если перевод не найден
return key;
}
/**
* Получает переведенное сообщение для конкретного языка
* @param key - Ключ сообщения из translations
* @param lang - Код языка
* @returns Переведенное сообщение
*/
export function tLang(key: string, lang: Language = "EN"): string {
if (translations[key]) {
return translations[key][lang];
}
return key;
}
/**
* Создает объект ошибки с кодом и переведенным сообщением
* @param errorCode - Код ошибки из ErrorCode enum
* @param req - Express Request объект
* @returns Объект с ошибкой и кодом ошибки
*/
export function createError(errorCode: ErrorCode, req: Request): ErrorResponse {
return {
error: t(errorCode, req),
errorCode: errorCode,
};
}
export { ErrorCode } from "../types/errorCodes";
export default { t, getLanguageFromRequest, tLang, createError };