import { z } from 'zod'; const configSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), QIPAI_API_HOST: z.string().min(1).default('0.0.0.0'), QIPAI_API_PORT: z.coerce.number().int().min(1).max(65535).default(3001), QIPAI_API_VERSION: z.string().min(1).default('0.1.0'), QIPAI_API_CORS_ORIGINS: z.string().default('https://api.txyundm.cn'), QIPAI_MYSQL_HOST: z.string().min(1).default('127.0.0.1'), QIPAI_MYSQL_PORT: z.coerce.number().int().min(1).max(65535).default(3306), QIPAI_MYSQL_DATABASE: z.string().min(1).default('qipai'), QIPAI_MYSQL_USER: z.string().min(1).default('qipai_app'), QIPAI_MYSQL_PASSWORD: z.string().default(''), QIPAI_MYSQL_CONNECTION_LIMIT: z.coerce.number().int().min(1).max(50).default(10), QIPAI_JWT_SECRET: z.string().min(32).default('development-only-change-this-jwt-secret'), QIPAI_ACCESS_TOKEN_TTL_SECONDS: z.coerce.number().int().min(60).max(86400).default(900), QIPAI_SESSION_TTL_SECONDS: z.coerce.number().int().min(300).max(2592000).default(604800), QIPAI_WECHAT_APP_SECRETS: z.string().default('{}'), QIPAI_TEST_PAYMENT_ENABLED: z.enum(['true', 'false']).default('false'), QIPAI_WECHAT_PAY_CREDENTIALS: z.string().default('{}'), QIPAI_PROFIT_SHARE_MOCK_ENABLED: z.enum(['true', 'false']).default('false'), QIPAI_CLEANING_PAYOUT_MOCK_ENABLED: z.enum(['true', 'false']).default('false'), QIPAI_THIRD_PARTY_CREDENTIALS: z.string().default('{}'), QIPAI_MQTT_URL: z.string().url().default('mqtt://101.42.38.246:1883'), QIPAI_MQTT_CLIENT_ID: z.string().min(1).max(128).default('qipai-backend'), QIPAI_MQTT_USERNAME: z.string().default(''), QIPAI_MQTT_PASSWORD: z.string().default(''), QIPAI_MQTT_RECONNECT_MS: z.coerce.number().int().min(1000).max(60000).default(3000), QIPAI_MQTT_CONNECT_TIMEOUT_MS: z.coerce.number().int().min(1000).max(60000).default(10000), QIPAI_MQTT_MAX_MESSAGE_BYTES: z.coerce.number().int().min(1024).max(1048576).default(65536) }); export type AppConfig = ReturnType; export function loadConfig(env: NodeJS.ProcessEnv = process.env) { const parsed = configSchema.parse(env); if ( parsed.NODE_ENV === 'production' && parsed.QIPAI_JWT_SECRET === 'development-only-change-this-jwt-secret' ) { throw new Error('QIPAI_JWT_SECRET must be explicitly configured in production.'); } const mqttUsernameConfigured = parsed.QIPAI_MQTT_USERNAME.length > 0; const mqttPasswordConfigured = parsed.QIPAI_MQTT_PASSWORD.length > 0; if (mqttUsernameConfigured !== mqttPasswordConfigured) { throw new Error('QIPAI_MQTT_USERNAME and QIPAI_MQTT_PASSWORD must be configured together.'); } if (parsed.NODE_ENV === 'production' && !mqttUsernameConfigured) { throw new Error('MQTT credentials must be explicitly configured in production.'); } return { nodeEnv: parsed.NODE_ENV, host: parsed.QIPAI_API_HOST, port: parsed.QIPAI_API_PORT, version: parsed.QIPAI_API_VERSION, corsOrigins: parsed.QIPAI_API_CORS_ORIGINS.split(',').map((item) => item.trim()).filter(Boolean), mysql: { host: parsed.QIPAI_MYSQL_HOST, port: parsed.QIPAI_MYSQL_PORT, database: parsed.QIPAI_MYSQL_DATABASE, user: parsed.QIPAI_MYSQL_USER, credential: parsed.QIPAI_MYSQL_PASSWORD, passwordConfigured: parsed.QIPAI_MYSQL_PASSWORD.length > 0, connectionLimit: parsed.QIPAI_MYSQL_CONNECTION_LIMIT }, auth: { jwtSecret: parsed.QIPAI_JWT_SECRET, accessTokenTtlSeconds: parsed.QIPAI_ACCESS_TOKEN_TTL_SECONDS, sessionTtlSeconds: parsed.QIPAI_SESSION_TTL_SECONDS, wechatAppSecretsJson: parsed.QIPAI_WECHAT_APP_SECRETS }, payment: { testAdapterEnabled: parsed.NODE_ENV !== 'production' && parsed.QIPAI_TEST_PAYMENT_ENABLED === 'true', wechatCredentialsJson: parsed.QIPAI_WECHAT_PAY_CREDENTIALS, profitShareMockEnabled: parsed.NODE_ENV !== 'production' && parsed.QIPAI_PROFIT_SHARE_MOCK_ENABLED === 'true', cleaningPayoutMockEnabled: parsed.NODE_ENV !== 'production' && parsed.QIPAI_CLEANING_PAYOUT_MOCK_ENABLED === 'true' }, thirdParty: { credentialsJson: parsed.QIPAI_THIRD_PARTY_CREDENTIALS }, mqtt: { url: parsed.QIPAI_MQTT_URL, clientId: parsed.QIPAI_MQTT_CLIENT_ID, username: parsed.QIPAI_MQTT_USERNAME, credential: parsed.QIPAI_MQTT_PASSWORD, usernameConfigured: mqttUsernameConfigured, passwordConfigured: mqttPasswordConfigured, reconnectPeriodMs: parsed.QIPAI_MQTT_RECONNECT_MS, connectTimeoutMs: parsed.QIPAI_MQTT_CONNECT_TIMEOUT_MS, maxMessageBytes: parsed.QIPAI_MQTT_MAX_MESSAGE_BYTES } }; }