package config import ( "log" "os" "path/filepath" "strconv" "strings" "time" "github.com/joho/godotenv" ) // Config holds all configuration for the application type Config struct { // App settings AppEnv string Port string Debug bool // Database settings DatabaseURL string MaxIdleConnections int MaxOpenConnections int ConnMaxLifetime time.Duration // JWT settings JWTSecret string JWTExpiration time.Duration // Server settings ReadTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration // Security ContentSecurityPolicy string // File upload settings UploadDir string MaxUploadSize int64 AllowedMimeTypes []string // Email settings SMTPHost string SMTPPort int SMTPUser string SMTPPassword string SMTPFrom string SMTPFromName string SMTPEncryption string SMTPAuth bool SMTPSkipVerify bool // Email templates EmailTemplateDir string // Contact settings ContactEmail string AdminEmail string // Admin access token (optional) to allow token-based admin access AdminAccessToken string // Newsletter settings NewsletterEnabled bool // CORS settings AllowedOrigins []string // External services ScraperBaseURL string FrontendBaseURL string PublicAPIBaseURL string // Umami Analytics UmamiURL string UmamiUsername string UmamiPassword string UmamiWebsiteID string // If empty, will auto-create on production } var AppConfig *Config // LoadConfig loads the configuration from environment variables func LoadConfig() { // Load .env file if it exists _ = godotenv.Load() AppConfig = &Config{ // App settings AppEnv: getEnv("APP_ENV", "development"), Port: getEnv("PORT", "8080"), Debug: getEnvAsBool("DEBUG", true), // Database settings DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/fotbal_club?sslmode=disable"), MaxIdleConnections: getEnvAsInt("DB_MAX_IDLE_CONNS", 10), MaxOpenConnections: getEnvAsInt("DB_MAX_OPEN_CONNS", 100), ConnMaxLifetime: time.Duration(getEnvAsInt("DB_CONN_MAX_LIFETIME", 60)) * time.Minute, // JWT settings JWTSecret: getEnv("JWT_SECRET", "default-secret-key-change-in-production"), JWTExpiration: time.Duration(getEnvAsInt("JWT_EXPIRATION_HOURS", 24)) * time.Hour, // Server settings ReadTimeout: time.Duration(getEnvAsInt("READ_TIMEOUT", 5)) * time.Second, WriteTimeout: time.Duration(getEnvAsInt("WRITE_TIMEOUT", 10)) * time.Second, IdleTimeout: time.Duration(getEnvAsInt("IDLE_TIMEOUT", 120)) * time.Second, // Security ContentSecurityPolicy: getEnv("CONTENT_SECURITY_POLICY", "default-src 'self' data: blob: https: http:; img-src * data: blob:; style-src 'self' 'unsafe-inline' https: http:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http:; connect-src *;"), // File upload settings UploadDir: getEnv("UPLOAD_DIR", "./uploads"), MaxUploadSize: int64(getEnvAsInt("MAX_UPLOAD_SIZE", 10)) * 1024 * 1024, // 10MB default AllowedMimeTypes: []string{ // Images "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", // Documents "application/pdf", "application/msword", // .doc "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx "application/vnd.ms-excel", // .xls "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx "application/vnd.ms-powerpoint", // .ppt "application/vnd.openxmlformats-officedocument.presentationml.presentation", // .pptx // Text "text/plain", // Archives "application/zip", "application/x-zip-compressed", "application/x-rar-compressed", "application/vnd.rar", }, // Email settings SMTPHost: getEnv("SMTP_HOST", "smtp.example.com"), SMTPPort: getEnvAsInt("SMTP_PORT", 587), SMTPUser: getEnv("SMTP_USER", ""), SMTPPassword: getEnv("SMTP_PASSWORD", ""), SMTPFrom: getEnv("SMTP_FROM", "noreply@fotbal-club.cz"), SMTPFromName: getEnv("SMTP_FROM_NAME", "Fotbal Club"), SMTPEncryption: getEnv("SMTP_ENCRYPTION", "tls"), SMTPAuth: getEnvAsBool("SMTP_AUTH", true), SMTPSkipVerify: getEnvAsBool("SMTP_SKIP_VERIFY", false), // Email templates - using absolute path to templates EmailTemplateDir: getEnv("EMAIL_TEMPLATE_DIR", filepath.Join("templates", "emails")), // Contact settings ContactEmail: getEnv("CONTACT_EMAIL", "help@tdvorak.dev"), AdminEmail: getEnv("ADMIN_EMAIL", "help@tdvorak.dev"), AdminAccessToken: getEnv("ADMIN_ACCESS_TOKEN", ""), // Newsletter settings NewsletterEnabled: getEnvAsBool("NEWSLETTER_ENABLED", true), // CORS settings AllowedOrigins: []string{ "http://localhost:3000", "http://localhost:8080", }, // External services ScraperBaseURL: getEnv("FACR_SCRAPER_BASE_URL", "http://localhost:8081"), FrontendBaseURL: getEnv("FRONTEND_BASE_URL", "http://localhost:3000"), PublicAPIBaseURL: getEnv("PUBLIC_API_BASE_URL", "http://localhost:8080/api/v1"), // Umami Analytics UmamiURL: getEnv("UMAMI_URL", ""), UmamiUsername: getEnv("UMAMI_USERNAME", ""), UmamiPassword: getEnv("UMAMI_PASSWORD", ""), UmamiWebsiteID: getEnv("UMAMI_WEBSITE_ID", ""), } // Override allowed origins if specified in environment (comma-separated) if origins := os.Getenv("ALLOWED_ORIGINS"); origins != "" { parts := strings.Split(origins, ",") var list []string for _, p := range parts { v := strings.TrimSpace(p) if v != "" { list = append(list, v) } } if len(list) > 0 { AppConfig.AllowedOrigins = list } } } func getEnv(key, defaultValue string) string { value := os.Getenv(key) if value == "" { return defaultValue } return value } func getEnvAsInt(key string, defaultValue int) int { valueStr := os.Getenv(key) if valueStr == "" { return defaultValue } value, err := strconv.Atoi(valueStr) if err != nil { log.Printf("Invalid value for %s: %v. Using default: %d", key, err, defaultValue) return defaultValue } return value } func getEnvAsBool(key string, defaultValue bool) bool { valueStr := os.Getenv(key) if valueStr == "" { return defaultValue } value, err := strconv.ParseBool(valueStr) if err != nil { log.Printf("Invalid boolean value for %s: %v. Using default: %t", key, err, defaultValue) return defaultValue } return value }