ai-wpa/lib/env.ts

200 lines
5.8 KiB
TypeScript

/**
* Environment variable validation and management
* Ensures all required environment variables are present and valid
*/
import { z } from 'zod'
// Define the schema for environment variables
const envSchema = z.object({
// Node environment
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
// Server configuration
PORT: z.string().default('3006').transform(Number),
// Database
MONGODB_URI: z.string().min(1, 'MongoDB URI is required'),
// Redis
REDIS_URL: z.string().min(1, 'Redis URL is required'),
// Authentication secrets (required)
SESSION_SECRET: z.string().min(32, 'Session secret must be at least 32 characters'),
JWT_SECRET: z.string().min(1, 'JWT secret is required'),
JWT_REFRESH_SECRET: z.string().min(1, 'JWT refresh secret is required'),
// Optional configuration
NEXT_PUBLIC_SITE_URL: z.string().url().optional(),
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
// Analytics (optional)
NEXT_PUBLIC_GA_MEASUREMENT_ID: z.string().optional(),
NEXT_PUBLIC_PLAUSIBLE_DOMAIN: z.string().optional(),
// Email (optional)
SMTP_HOST: z.string().optional(),
SMTP_PORT: z
.string()
.optional()
.transform((val) => (val ? Number(val) : undefined)),
SMTP_USER: z.string().optional(),
SMTP_PASS: z.string().optional(),
// File storage (optional) - using dev-portfolio naming convention
MINIO_ENDPOINT: z.string().optional(),
MINIO_PORT: z
.string()
.optional()
.transform((val) => (val ? Number(val) : undefined)),
MINIO_KEY: z.string().optional(),
MINIO_SECRET: z.string().optional(),
MINIO_IMAGE_BUCKET: z.string().optional(),
// Legacy names for backward compatibility
MINIO_ACCESS_KEY: z.string().optional(),
MINIO_SECRET_KEY: z.string().optional(),
MINIO_BUCKET: z.string().optional(),
// External file upload API
FILE_UPLOAD_TOKEN: z.string().optional(),
// Monitoring (optional)
SENTRY_DSN: z.string().optional(),
SENTRY_ORG: z.string().optional(),
SENTRY_PROJECT: z.string().optional(),
})
// Parse and validate environment variables
function validateEnv() {
// Skip validation during build if SKIP_ENV_VALIDATION is set
if (process.env.SKIP_ENV_VALIDATION === 'true') {
console.log('⚠️ Skipping environment validation (SKIP_ENV_VALIDATION=true)')
// Return process.env with unknown first to bypass TypeScript's type checking
return process.env as unknown as z.infer<typeof envSchema>
}
try {
return envSchema.parse(process.env)
} catch (error) {
if (error instanceof z.ZodError) {
const missingVars = error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
console.error('❌ Invalid environment variables:')
missingVars.forEach((error) => console.error(` - ${error}`))
throw new Error('Environment validation failed')
}
throw error
}
}
// Export validated environment variables
export const env = validateEnv() as z.infer<typeof envSchema>
// Utility functions for environment checks
export const isDevelopment = env.NODE_ENV === 'development'
export const isProduction = env.NODE_ENV === 'production'
export const isTest = env.NODE_ENV === 'test'
// Database configuration
export const dbConfig = {
uri: env.MONGODB_URI,
}
// Redis configuration
export const redisConfig = {
url: env.REDIS_URL,
}
// Authentication configuration
export const authConfig = {
sessionSecret: env.SESSION_SECRET,
jwtSecret: env.JWT_SECRET,
jwtRefreshSecret: env.JWT_REFRESH_SECRET,
}
// App configuration
export const appConfig = {
port: env.PORT,
siteUrl: env.NEXT_PUBLIC_SITE_URL || env.NEXT_PUBLIC_APP_URL || `http://localhost:${env.PORT}`,
appUrl: env.NEXT_PUBLIC_APP_URL || env.NEXT_PUBLIC_SITE_URL || `http://localhost:${env.PORT}`,
}
// Analytics configuration
export const analyticsConfig = {
googleAnalytics: {
measurementId: env.NEXT_PUBLIC_GA_MEASUREMENT_ID,
enabled: !!env.NEXT_PUBLIC_GA_MEASUREMENT_ID && isProduction,
},
plausible: {
domain: env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN,
enabled: !!env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN && isProduction,
},
}
// Email configuration
export const emailConfig = {
smtp: {
host: env.SMTP_HOST,
port: env.SMTP_PORT || 587,
user: env.SMTP_USER,
pass: env.SMTP_PASS,
},
enabled: !!(env.SMTP_HOST && env.SMTP_USER && env.SMTP_PASS),
}
// File storage configuration - prioritize dev-portfolio naming
export const storageConfig = {
minio: {
endpoint: env.MINIO_ENDPOINT,
port: env.MINIO_PORT || 9000,
accessKey: env.MINIO_KEY || env.MINIO_ACCESS_KEY,
secretKey: env.MINIO_SECRET || env.MINIO_SECRET_KEY,
bucket: env.MINIO_IMAGE_BUCKET || env.MINIO_BUCKET,
},
enabled: !!(
env.MINIO_ENDPOINT &&
(env.MINIO_KEY || env.MINIO_ACCESS_KEY) &&
(env.MINIO_SECRET || env.MINIO_SECRET_KEY)
),
}
// Monitoring configuration
export const monitoringConfig = {
sentry: {
dsn: env.SENTRY_DSN,
org: env.SENTRY_ORG,
project: env.SENTRY_PROJECT,
},
enabled: !!env.SENTRY_DSN && isProduction,
}
// Environment info for debugging
export const envInfo = {
nodeEnv: env.NODE_ENV,
port: env.PORT,
features: {
analytics: analyticsConfig.googleAnalytics.enabled || analyticsConfig.plausible.enabled,
email: emailConfig.enabled,
storage: storageConfig.enabled,
monitoring: monitoringConfig.enabled,
},
}
// Log environment info in development
if (isDevelopment) {
console.log('🔧 Environment configuration:')
console.log(` - Node: ${env.NODE_ENV}`)
console.log(` - Port: ${env.PORT}`)
console.log(` - Database: ${env.MONGODB_URI.includes('localhost') ? 'Local' : 'Remote'}`)
console.log(` - Redis: ${env.REDIS_URL.includes('localhost') ? 'Local' : 'Remote'}`)
console.log(
` - Features: ${
Object.entries(envInfo.features)
.filter(([, enabled]) => enabled)
.map(([name]) => name)
.join(', ') || 'None'
}`
)
}