import dotenv from 'dotenv';
import { z } from 'zod';
// Load environment variables
dotenv.config();
// Configuration schema for validation
const ConfigSchema = z.object({
// Server Configuration
server: z.object({
port: z.number().default(3000),
host: z.string().default('localhost'),
environment: z.enum(['development', 'production', 'test']).default('development'),
cors: z.object({
origin: z.union([z.string(), z.array(z.string())]).default('*'),
credentials: z.boolean().default(true)
})
}),
// MCP Configuration
mcp: z.object({
serverName: z.string().default('calendar-assistant'),
version: z.string().default('1.0.0'),
maxToolCalls: z.number().default(20),
timeout: z.number().default(30000) // 30 seconds
}),
// Database Configuration
database: z.object({
url: z.string(),
ssl: z.boolean().default(false),
pool: z.object({
min: z.number().default(2),
max: z.number().default(10),
acquireTimeoutMillis: z.number().default(60000),
idleTimeoutMillis: z.number().default(30000)
})
}),
// Redis Configuration
redis: z.object({
url: z.string(),
ttl: z.object({
events: z.number().default(900), // 15 minutes
freeTime: z.number().default(300), // 5 minutes
userPreferences: z.number().default(3600), // 1 hour
contacts: z.number().default(1800) // 30 minutes
})
}),
// Google Calendar Configuration
google: z.object({
clientId: z.string(),
clientSecret: z.string(),
redirectUri: z.string(),
scopes: z.array(z.string()).default([
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/calendar.events'
])
}),
// Authentication Configuration
auth: z.object({
jwtSecret: z.string(),
jwtExpiry: z.string().default('24h'),
refreshTokenExpiry: z.string().default('30d'),
bcryptRounds: z.number().default(12)
}),
// Rate Limiting Configuration
rateLimit: z.object({
windowMs: z.number().default(900000), // 15 minutes
maxRequests: z.number().default(100),
skipSuccessfulRequests: z.boolean().default(false)
}),
// Logging Configuration
logging: z.object({
level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
format: z.enum(['json', 'simple']).default('json'),
file: z.object({
enabled: z.boolean().default(true),
filename: z.string().default('logs/calendar-server.log'),
maxSize: z.string().default('10m'),
maxFiles: z.number().default(5)
}),
console: z.object({
enabled: z.boolean().default(true),
colorize: z.boolean().default(true)
})
}),
// Feature Flags
features: z.object({
enableCaching: z.boolean().default(true),
enableBatching: z.boolean().default(true),
enablePrefetching: z.boolean().default(true),
enableAnalytics: z.boolean().default(false),
maxBatchSize: z.number().default(10),
enableMultiProvider: z.boolean().default(false) // Future: Outlook, Apple Calendar
})
});
type Config = z.infer<typeof ConfigSchema>;
// Build configuration from environment variables
function buildConfig(): Config {
const rawConfig = {
server: {
port: parseInt(process.env.PORT || '3000', 10),
host: process.env.HOST || 'localhost',
environment: process.env.NODE_ENV || 'development',
cors: {
origin: process.env.CORS_ORIGIN || '*',
credentials: process.env.CORS_CREDENTIALS === 'true'
}
},
mcp: {
serverName: process.env.MCP_SERVER_NAME || 'calendar-assistant',
version: process.env.MCP_VERSION || '1.0.0',
maxToolCalls: parseInt(process.env.MCP_MAX_TOOL_CALLS || '20', 10),
timeout: parseInt(process.env.MCP_TIMEOUT || '30000', 10)
},
database: {
url: process.env.DATABASE_URL || 'postgresql://localhost:5432/calendar_assistant',
ssl: process.env.DATABASE_SSL === 'true',
pool: {
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
acquireTimeoutMillis: parseInt(process.env.DB_POOL_ACQUIRE_TIMEOUT || '60000', 10),
idleTimeoutMillis: parseInt(process.env.DB_POOL_IDLE_TIMEOUT || '30000', 10)
}
},
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379',
ttl: {
events: parseInt(process.env.REDIS_TTL_EVENTS || '900', 10),
freeTime: parseInt(process.env.REDIS_TTL_FREE_TIME || '300', 10),
userPreferences: parseInt(process.env.REDIS_TTL_USER_PREFS || '3600', 10),
contacts: parseInt(process.env.REDIS_TTL_CONTACTS || '1800', 10)
}
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback',
scopes: (process.env.GOOGLE_SCOPES || 'calendar,calendar.events').split(',').map(scope =>
`https://www.googleapis.com/auth/calendar${scope === 'calendar' ? '' : '.' + scope}`
)
},
auth: {
jwtSecret: process.env.JWT_SECRET || 'your-super-secret-jwt-key',
jwtExpiry: process.env.JWT_EXPIRY || '24h',
refreshTokenExpiry: process.env.REFRESH_TOKEN_EXPIRY || '30d',
bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || '12', 10)
},
rateLimit: {
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10),
maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10),
skipSuccessfulRequests: process.env.RATE_LIMIT_SKIP_SUCCESSFUL === 'true'
},
logging: {
level: process.env.LOG_LEVEL || 'info',
format: process.env.LOG_FORMAT || 'json',
file: {
enabled: process.env.LOG_FILE_ENABLED !== 'false',
filename: process.env.LOG_FILE_NAME || 'logs/calendar-server.log',
maxSize: process.env.LOG_FILE_MAX_SIZE || '10m',
maxFiles: parseInt(process.env.LOG_FILE_MAX_FILES || '5', 10)
},
console: {
enabled: process.env.LOG_CONSOLE_ENABLED !== 'false',
colorize: process.env.LOG_CONSOLE_COLORIZE !== 'false'
}
},
features: {
enableCaching: process.env.FEATURE_CACHING !== 'false',
enableBatching: process.env.FEATURE_BATCHING !== 'false',
enablePrefetching: process.env.FEATURE_PREFETCHING !== 'false',
enableAnalytics: process.env.FEATURE_ANALYTICS === 'true',
maxBatchSize: parseInt(process.env.FEATURE_MAX_BATCH_SIZE || '10', 10),
enableMultiProvider: process.env.FEATURE_MULTI_PROVIDER === 'true'
}
};
return ConfigSchema.parse(rawConfig);
}
// Validate and export configuration
export const config = buildConfig();
// Configuration validation function
export function validateConfig(): { valid: boolean; errors?: string[] } {
try {
ConfigSchema.parse(config);
return { valid: true };
} catch (error) {
if (error instanceof z.ZodError) {
return {
valid: false,
errors: error.errors.map(err => `${err.path.join('.')}: ${err.message}`)
};
}
return {
valid: false,
errors: ['Unknown configuration validation error']
};
}
}
// Helper functions
export function isDevelopment(): boolean {
return config.server.environment === 'development';
}
export function isProduction(): boolean {
return config.server.environment === 'production';
}
export function isTest(): boolean {
return config.server.environment === 'test';
}
// Export configuration sections for easier imports
export const {
server: serverConfig,
mcp: mcpConfig,
database: databaseConfig,
redis: redisConfig,
google: googleConfig,
auth: authConfig,
rateLimit: rateLimitConfig,
logging: loggingConfig,
features: featureConfig
} = config;