const path = require("node:path"); const ONE_DAY_SECONDS = 24 * 60 * 60; const ONE_MINUTE_SECONDS = 60; function requiredEnv(name) { const value = process.env[name]; if (!value) { throw new Error(`Missing required env: ${name}`); } return value; } function getConfig() { const nodeEnv = process.env.NODE_ENV ?? "development"; const serverRoot = path.resolve(__dirname, ".."); const storageDir = process.env.STORAGE_DIR ?? "generated/storage"; const clientDistDir = process.env.CLIENT_DIST_DIR ?? "../client/dist"; return { nodeEnv, port: Number(process.env.PORT ?? 3000), host: process.env.HOST ?? "0.0.0.0", metricsEnabled: process.env.METRICS_ENABLED !== "false", appOrigin: process.env.APP_ORIGIN ?? "", jwtSecret: requiredEnv("JWT_SECRET"), accessTokenTtl: "15m", refreshTtlSeconds: 7 * ONE_DAY_SECONDS, refreshCookieName: "refresh_token", cookieSecure: process.env.COOKIE_SECURE != null ? process.env.COOKIE_SECURE === "true" : nodeEnv === "production", passwordResetCodeTtlSeconds: Number( process.env.PASSWORD_RESET_CODE_TTL_SECONDS ?? 10 * 60, ), devShowPasswordResetCode: process.env.DEV_SHOW_PASSWORD_RESET_CODE === "true", loginFailWindowSeconds: Number( process.env.AUTH_LOGIN_FAIL_WINDOW_SECONDS ?? 15 * ONE_MINUTE_SECONDS, ), loginFailMaxAttempts: Number(process.env.AUTH_LOGIN_FAIL_MAX_ATTEMPTS ?? 5), loginLockSeconds: Number(process.env.AUTH_LOGIN_LOCK_SECONDS ?? 30 * ONE_MINUTE_SECONDS), appTimezone: process.env.APP_TIMEZONE ?? "Asia/Shanghai", storageDir: path.resolve(serverRoot, storageDir), storageDriver: process.env.STORAGE_DRIVER ?? "local", s3Endpoint: process.env.S3_ENDPOINT ?? "", s3Region: process.env.S3_REGION ?? "us-east-1", s3Bucket: process.env.S3_BUCKET ?? "", s3AccessKeyId: process.env.S3_ACCESS_KEY_ID ?? "", s3SecretAccessKey: process.env.S3_SECRET_ACCESS_KEY ?? "", s3ForcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true", serveClientDist: process.env.SERVE_CLIENT_DIST === "true", clientDistDir: path.resolve(serverRoot, clientDistDir), signedUrlTtlSeconds: Number(process.env.SIGNED_URL_TTL_SECONDS ?? 600), maxUploadSizeBytes: Number(process.env.MAX_UPLOAD_SIZE_BYTES ?? 10 * 1024 * 1024), rateLimitEnabled: process.env.RATE_LIMIT_ENABLED != null ? process.env.RATE_LIMIT_ENABLED === "true" : nodeEnv === "production", rateLimitLoginMax: Number(process.env.RATE_LIMIT_LOGIN_MAX ?? 10), rateLimitLoginWindow: process.env.RATE_LIMIT_LOGIN_WINDOW ?? "1 minute", delegationRevokeCron: process.env.DELEGATION_REVOKE_CRON ?? "0 * * * *", jobDriver: process.env.JOB_DRIVER ?? "cron", jobsEnabled: process.env.JOBS_ENABLED !== "false", pgBossSchema: process.env.PG_BOSS_SCHEMA ?? "pgboss", ocrProvider: process.env.OCR_PROVIDER ?? "off", ocrBatchSize: Number(process.env.OCR_BATCH_SIZE ?? 10), ocrLockTtlSeconds: Number(process.env.OCR_LOCK_TTL_SECONDS ?? 300), ocrPollCron: process.env.OCR_POLL_CRON ?? "* * * * *", ocrMaxAttempts: Number(process.env.OCR_MAX_ATTEMPTS ?? 3), ocrTimeoutMs: Number(process.env.OCR_TIMEOUT_MS ?? 5000), invoiceArchiveCron: process.env.INVOICE_ARCHIVE_CRON ?? "30 2 * * *", invoiceArchiveUnusedDays: Number(process.env.INVOICE_ARCHIVE_UNUSED_DAYS ?? 90), invoiceArchiveBatchSize: Number(process.env.INVOICE_ARCHIVE_BATCH_SIZE ?? 200), tencentSecretId: process.env.TENCENT_SECRET_ID ?? "", tencentSecretKey: process.env.TENCENT_SECRET_KEY ?? "", tencentOcrRegion: process.env.TENCENT_OCR_REGION ?? "ap-beijing", emailEnabled: process.env.EMAIL_ENABLED === "true", emailFrom: process.env.EMAIL_FROM ?? "no-reply@hyinda.com", emailBaseUrl: process.env.EMAIL_BASE_URL ?? "", smtpHost: process.env.SMTP_HOST ?? "", smtpPort: Number(process.env.SMTP_PORT ?? 587), smtpSecure: process.env.SMTP_SECURE === "true", smtpUser: process.env.SMTP_USER ?? "", smtpPass: process.env.SMTP_PASS ?? "", }; } module.exports = { getConfig };