diff --git a/src/routes/activeSession.ts b/src/routes/activeSession.ts
index 8ff8aa7..7b7c598 100644
--- a/src/routes/activeSession.ts
+++ b/src/routes/activeSession.ts
@@ -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));
}
});
diff --git a/src/routes/getCountryCode.ts b/src/routes/getCountryCode.ts
index 9158f8a..80ee161 100644
--- a/src/routes/getCountryCode.ts
+++ b/src/routes/getCountryCode.ts
@@ -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));
}
});
diff --git a/src/routes/sendInvite.ts b/src/routes/sendInvite.ts
index 8a178df..351b5be 100644
--- a/src/routes/sendInvite.ts
+++ b/src/routes/sendInvite.ts
@@ -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: `
+ Link to connect to the demonstration:
${link}
+
`,
+ },
+ RU: {
+ subject: "Приглашение на демонстрацию - stream.graff.tech",
+ body: `
+ Ссылка для подключения к демонстрации:
${link}
+
`,
+ },
+ };
+
// 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: `
- Ссылка для подключения к демонстрации:
${link}
-
`,
+ subject: emailContent[lang].subject, // Subject line
+ html: emailContent[lang].body,
});
+
+ res.json({ ok: 1 });
} catch (error) {
console.log("error", (error as Error).message);
+ res.json(createError(ErrorCode.EMAIL_SEND_ERROR, req));
}
-
- res.json({ ok: 1 });
});
const sendInviteRouter = router;
diff --git a/src/types/errorCodes.ts b/src/types/errorCodes.ts
new file mode 100644
index 0000000..1db9367
--- /dev/null
+++ b/src/types/errorCodes.ts
@@ -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";
+}
diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts
new file mode 100644
index 0000000..31db81b
--- /dev/null
+++ b/src/utils/i18n.ts
@@ -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 };