|
@@ -1,60 +1,104 @@
|
|
|
import "../src/plugins/dotenv.js";
|
|
|
import { z } from "zod";
|
|
|
|
|
|
-// Создаем схему конфигурации с проверками и преобразованиями
|
|
|
-const ZConfigShema = z.object({
|
|
|
- PORT: z
|
|
|
- .string()
|
|
|
- .min(1, "PORT must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- LOGS_LEVEL: z.enum(["error", "warn", "info", "verbose", "debug", "silly"]),
|
|
|
- API_URL: z.string().min(1, "API_URL must be defined"),
|
|
|
+// Функция для обязательных строковых переменных
|
|
|
+const requiredString = (envVar: string, defaultValue?: string) => {
|
|
|
+ return z.preprocess(
|
|
|
+ (val) => {
|
|
|
+ if (typeof val !== "string" || val.trim() === "") {
|
|
|
+ if (defaultValue !== undefined) return defaultValue;
|
|
|
+ return z.NEVER;
|
|
|
+ }
|
|
|
+ return val.trim();
|
|
|
+ },
|
|
|
+ z
|
|
|
+ .string()
|
|
|
+ .min(
|
|
|
+ 1,
|
|
|
+ `${envVar} не определен. Пожалуйста, задайте переменную в файле ${process.env.NODE_ENV === "production" ? ".env" : ".dev.env"} или переменных окружения.`,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// Функция для обязательных числовых переменных
|
|
|
+const requiredNumber = (envVar: string, defaultValue?: number) => {
|
|
|
+ return z.preprocess(
|
|
|
+ (val) => {
|
|
|
+ if (val === undefined || val === null || val === "") {
|
|
|
+ if (defaultValue !== undefined) return defaultValue;
|
|
|
+ return z.NEVER;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Если значение передано не строкой и не числом — ошибка
|
|
|
+ if (typeof val !== "string" && typeof val !== "number") {
|
|
|
+ return z.NEVER;
|
|
|
+ }
|
|
|
+
|
|
|
+ const parsed = Number(val);
|
|
|
+ if (!isFinite(parsed)) {
|
|
|
+ return z.NEVER;
|
|
|
+ }
|
|
|
+ return parsed;
|
|
|
+ },
|
|
|
+ z.number().min(1, `${envVar} должен быть числом. Например: 5432.`),
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// Описание схемы переменных окружения
|
|
|
+const ConfigSchema = z.object({
|
|
|
+ PORT: requiredNumber("PORT", 3000),
|
|
|
+ LOGS_LEVEL: z
|
|
|
+ .enum(["error", "warn", "info", "verbose", "debug", "silly"])
|
|
|
+ .default("info"),
|
|
|
+ API_URL: requiredString("API_URL"),
|
|
|
|
|
|
// DB
|
|
|
- DB_USER: z.string().min(1, "DB_USER must be defined"),
|
|
|
- DB_HOST: z.string().min(1, "DB_HOST must be defined"),
|
|
|
- DB_PORT: z
|
|
|
- .string()
|
|
|
- .min(1, "DB_PORT must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- DB_NAME: z.string().min(1, "DB_NAME must be defined"),
|
|
|
- DB_PASS: z.string().min(1, "DB_PASS must be defined"),
|
|
|
+ DB_USER: requiredString("DB_USER"),
|
|
|
+ DB_HOST: requiredString("DB_HOST"),
|
|
|
+ DB_PORT: requiredNumber("DB_PORT"),
|
|
|
+ DB_NAME: requiredString("DB_NAME"),
|
|
|
+ DB_PASS: requiredString("DB_PASS"),
|
|
|
|
|
|
// MAIL
|
|
|
- SMTP_HOST: z.string().min(1, "SMTP_HOST must be defined"),
|
|
|
- SMTP_PORT: z
|
|
|
- .string()
|
|
|
- .min(1, "SMTP_PORT must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- SMTP_USER: z.string().min(1, "SMTP_USER must be defined"),
|
|
|
- SMTP_PASS: z.string().min(1, "SMTP_PASS must be defined"),
|
|
|
+ SMTP_HOST: requiredString("SMTP_HOST"),
|
|
|
+ SMTP_PORT: requiredNumber("SMTP_PORT"),
|
|
|
+ SMTP_USER: requiredString("SMTP_USER"),
|
|
|
+ SMTP_PASS: requiredString("SMTP_PASS"),
|
|
|
|
|
|
// AUTH
|
|
|
- JWT_ACCESS_SECRET: z.string().min(1, "JWT_ACCESS_SECRET must be defined"),
|
|
|
- JWT_REFRESH_SECRET: z.string().min(1, "JWT_REFRESH_SECRET must be defined"),
|
|
|
- PASSWORD_MAX_TRIES: z
|
|
|
- .string()
|
|
|
- .min(1, "PASSWORD_MAX_TRIES must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- ACCESS_TOKEN_LIFETIME_MINS: z
|
|
|
- .string()
|
|
|
- .min(1, "ACCESS_TOKEN_LIFETIME_MINS must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- REFRESH_TOKEN_LIFETIME_DAYS: z
|
|
|
- .string()
|
|
|
- .min(1, "REFRESH_TOKEN_LIFETIME_DAYS must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
+ JWT_ACCESS_SECRET: requiredString("JWT_ACCESS_SECRET"),
|
|
|
+ JWT_REFRESH_SECRET: requiredString("JWT_REFRESH_SECRET"),
|
|
|
+ PASSWORD_MAX_TRIES: requiredNumber("PASSWORD_MAX_TRIES", 5),
|
|
|
+ ACCESS_TOKEN_LIFETIME_MINS: requiredNumber("ACCESS_TOKEN_LIFETIME_MINS", 15),
|
|
|
+ REFRESH_TOKEN_LIFETIME_DAYS: requiredNumber("REFRESH_TOKEN_LIFETIME_DAYS", 7),
|
|
|
|
|
|
// CONFIRM PINS
|
|
|
- CONFIRM_PIN_LIFETIME_MINS: z
|
|
|
- .string()
|
|
|
- .min(1, "CONFIRM_PIN_LIFETIME_MINS must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
- CONFIRM_PIN_MAX_TRIES: z
|
|
|
- .string()
|
|
|
- .min(1, "CONFIRM_PIN_MAX_TRIES must be defined")
|
|
|
- .transform((val) => parseInt(val, 10)),
|
|
|
+ CONFIRM_PIN_LIFETIME_MINS: requiredNumber("CONFIRM_PIN_LIFETIME_MINS", 10),
|
|
|
+ CONFIRM_PIN_MAX_TRIES: requiredNumber("CONFIRM_PIN_MAX_TRIES", 3),
|
|
|
});
|
|
|
|
|
|
-// Парсим и валидируем переменные окружения
|
|
|
-export const config = ZConfigShema.parse(process.env);
|
|
|
+// Проверяем валидность конфигурации
|
|
|
+const result = ConfigSchema.safeParse(process.env);
|
|
|
+
|
|
|
+if (!result.success) {
|
|
|
+ console.error(
|
|
|
+ "❌ Невалидные переменные окружения:\n",
|
|
|
+ JSON.stringify(result.error.format(), null, 2),
|
|
|
+ );
|
|
|
+ process.exit(1);
|
|
|
+}
|
|
|
+
|
|
|
+export const config = result.data;
|
|
|
+
|
|
|
+// Экспорт типа конфигурации
|
|
|
+export type Config = z.infer<typeof ConfigSchema>;
|
|
|
+
|
|
|
+// Логируем успешную валидацию без чувствительных данных
|
|
|
+const safeConfig = {
|
|
|
+ ...config,
|
|
|
+ DB_PASS: "********",
|
|
|
+ SMTP_PASS: "********",
|
|
|
+ JWT_ACCESS_SECRET: "********",
|
|
|
+ JWT_REFRESH_SECRET: "********",
|
|
|
+};
|
|
|
+console.debug("✅ Валидация env прошла успешно:", safeConfig);
|