initial commit
This commit is contained in:
199
lib/env.ts
Normal file
199
lib/env.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* 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'
|
||||
}`
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user