Mom-Portal/backend/src/config/env.js
KevinB-T 30894e7f27 Configure VM services and fix auth flow
- point MySQL and Whisper settings to the VM
- add VM MySQL bootstrap scripts and docs
- allow LAN Vite origins for CORS
- fix Express 5 validation assignment crash
- allow login with username or email
- prevent recursive auth refresh retries
2026-05-15 15:19:49 +05:30

130 lines
5.0 KiB
JavaScript

import path from "node:path";
import { fileURLToPath } from "node:url";
import dotenv from "dotenv";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const backendRoot = path.resolve(__dirname, "../..");
const repoRoot = path.resolve(backendRoot, "..");
dotenv.config({ path: path.join(repoRoot, ".env"), quiet: true });
dotenv.config({ path: path.join(backendRoot, ".env"), override: true, quiet: true });
function stringEnv(name, fallback = "") {
const value = process.env[name];
return value && value.trim().length > 0 ? value.trim() : fallback;
}
function numberEnv(name, fallback) {
const value = Number(process.env[name]);
return Number.isFinite(value) ? value : fallback;
}
function boolEnv(name, fallback = false) {
const value = process.env[name];
if (value === undefined) return fallback;
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
}
function firstString(names, fallback = "") {
for (const name of names) {
const value = stringEnv(name);
if (value) return value;
}
return fallback;
}
function normalizeBaseUrl(value) {
return value.replace(/\/+$/, "");
}
const serviceHost = stringEnv("ORPHION_SERVICE_HOST", "127.0.0.1");
const whisperVmIp = firstString(["WHISPER_VM_IP", "WHISPER_HOST"], serviceHost);
const whisperVmPort = numberEnv("WHISPER_VM_PORT", numberEnv("WHISPER_PORT", 8000));
const whisperApiUrl = normalizeBaseUrl(
firstString(["WHISPER_API_URL"], `http://${whisperVmIp}:${whisperVmPort}`),
);
export const env = {
appName: "Orphion",
nodeEnv: stringEnv("NODE_ENV", "development"),
isProduction: stringEnv("NODE_ENV") === "production",
host: stringEnv("HOST", "127.0.0.1"),
port: numberEnv("PORT", 4000),
backendRoot,
repoRoot,
apiPrefix: "/api/v1",
clientOrigin: stringEnv("CLIENT_ORIGIN", "http://localhost:5173"),
trustProxy: boolEnv("TRUST_PROXY", false),
requestBodyLimit: stringEnv("REQUEST_BODY_LIMIT", "2mb"),
database: {
host: firstString(["DB_HOST", "MYSQL_HOST"], serviceHost),
port: numberEnv("DB_PORT", numberEnv("MYSQL_PORT", 3306)),
name: firstString(["DB_NAME", "MYSQL_DATABASE"], "orphion"),
user: firstString(["DB_USER", "MYSQL_USER"], "root"),
password: firstString(["DB_PASSWORD", "MYSQL_PASSWORD"], ""),
connectionLimit: numberEnv("DB_CONNECTION_LIMIT", numberEnv("MYSQL_CONNECTION_LIMIT", 10)),
autoMigrate: boolEnv("DB_AUTO_MIGRATE", boolEnv("MYSQL_AUTO_MIGRATE", true)),
},
auth: {
accessTokenSecret: firstString(["JWT_ACCESS_SECRET", "JWT_SECRET"], "replace-access-secret"),
refreshTokenSecret: firstString(
["JWT_REFRESH_SECRET"],
"replace-refresh-secret-with-a-different-long-value",
),
accessTokenTtl: stringEnv("JWT_ACCESS_EXPIRES_IN", stringEnv("JWT_EXPIRES_IN", "15m")),
refreshTokenTtl: stringEnv("JWT_REFRESH_EXPIRES_IN", "30d"),
accessCookieName: stringEnv("JWT_COOKIE_NAME", "orphion_access_token"),
refreshCookieName: stringEnv("JWT_REFRESH_COOKIE_NAME", "orphion_refresh_token"),
cookieSecure: boolEnv("JWT_COOKIE_SECURE", false),
cookieSameSite: stringEnv("JWT_COOKIE_SAME_SITE", "lax"),
},
upload: {
tempDir: path.resolve(backendRoot, stringEnv("UPLOAD_TEMP_DIR", ".tmp/uploads")),
maxAudioSizeMb: numberEnv("MAX_AUDIO_SIZE_MB", 200),
allowedMimeTypes: stringEnv(
"AUDIO_ALLOWED_MIME_TYPES",
"audio/webm,audio/wav,audio/mpeg,audio/mp4,audio/ogg,audio/x-m4a,video/webm",
)
.split(",")
.map((item) => item.trim())
.filter(Boolean),
},
storage: {
driver: stringEnv("STORAGE_DRIVER", "smb"),
basePath: path.resolve(
backendRoot,
stringEnv("STORAGE_BASE_PATH", stringEnv("REMOTE_STORAGE_PATH", "../storage/audio")),
),
publicBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_PUBLIC_BASE_URL", "")),
httpBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_HTTP_BASE_URL", "")),
httpToken: stringEnv("STORAGE_HTTP_TOKEN", ""),
s3: {
endpoint: stringEnv("S3_ENDPOINT", ""),
region: stringEnv("S3_REGION", "us-east-1"),
bucket: stringEnv("S3_BUCKET", ""),
accessKeyId: stringEnv("S3_ACCESS_KEY_ID", ""),
secretAccessKey: stringEnv("S3_SECRET_ACCESS_KEY", ""),
forcePathStyle: boolEnv("S3_FORCE_PATH_STYLE", true),
},
},
whisper: {
vmIp: whisperVmIp,
vmPort: whisperVmPort,
apiUrl: whisperApiUrl,
transcribePath: stringEnv("WHISPER_TRANSCRIBE_PATH", "/transcribe"),
healthPath: stringEnv("WHISPER_HEALTH_PATH", "/health"),
fileField: stringEnv("WHISPER_FILE_FIELD", "file"),
timeoutMs: numberEnv("WHISPER_TIMEOUT_MS", 900000),
retries: numberEnv("WHISPER_RETRIES", 2),
retryDelayMs: numberEnv("WHISPER_RETRY_DELAY_MS", 1500),
modelName: stringEnv("WHISPER_MODEL_NAME", "faster-whisper-large-v3"),
allowMock: boolEnv("WHISPER_ALLOW_MOCK", false),
queueConcurrency: numberEnv("TRANSCRIPTION_QUEUE_CONCURRENCY", 1),
},
rateLimit: {
windowMs: numberEnv("RATE_LIMIT_WINDOW_MS", 15 * 60 * 1000),
max: numberEnv("RATE_LIMIT_MAX", 300),
authMax: numberEnv("AUTH_RATE_LIMIT_MAX", 30),
},
};