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 Premium 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 ZoneramaAPIBase string // Umami Analytics UmamiURL string UmamiUsername string UmamiPassword string UmamiWebsiteID string ErrorIngestURL string ErrorIngestToken string // Antivirus (optional) ClamAVEnabled bool ClamAVHost string ClamAVPort int // Feature flags RembgEnabled bool // Club data mode: "auto" (FACR integration) or "manual" (no FACR/http scraping) ClubDataMode string // E-shop settings EshopEnabled bool EshopFrontendURL string EshopAPIURL string // Stripe (E-shop payments) StripeEnabled bool StripeSecretKey string StripePublishableKey string StripeWebhookSecret string StripeCurrency string StripeReturnURL string // Revolut (E-shop payments) RevolutEnabled bool RevolutEnvironment string RevolutAPIKey string RevolutPublicKey string RevolutWebhookSecret string RevolutReturnURL string RevolutWebhookURL string // Packeta / Zasilkovna PacketaAPIPassword string PacketaWidgetAPIKey string PacketaEshopName string PacketaEnv string // DeepSeek AI Support DeepSeekAPIKey string DeepSeekBaseURL string } 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), Premium: getEnvAsBool("PREMIUM", false), // 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 *; frame-ancestors 'self';"), // File upload settings UploadDir: getEnv("UPLOAD_DIR", "./uploads"), MaxUploadSize: int64(getEnvAsInt("MAX_UPLOAD_SIZE", 20)) * 1024 * 1024, // 20MB 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"), ZoneramaAPIBase: getEnv("ZONERAMA_API_BASE", "https://zonerama.tdvorak.dev"), // Umami Analytics UmamiURL: getEnv("UMAMI_URL", ""), UmamiUsername: getEnv("UMAMI_USERNAME", ""), UmamiPassword: getEnv("UMAMI_PASSWORD", ""), UmamiWebsiteID: getEnv("UMAMI_WEBSITE_ID", ""), ErrorIngestURL: getEnv("ERROR_INGEST_URL", ""), ErrorIngestToken: getEnv("ERROR_INGEST_TOKEN", ""), // Antivirus (optional) ClamAVEnabled: getEnvAsBool("CLAMAV_ENABLED", false), ClamAVHost: getEnv("CLAMAV_HOST", "127.0.0.1"), ClamAVPort: getEnvAsInt("CLAMAV_PORT", 3310), // Feature flags RembgEnabled: getEnvAsBool("REMBG_ENABLED", true), // Club data mode: auto (FACR integration) or manual (no FACR/http scraping) ClubDataMode: strings.ToLower(strings.TrimSpace(getEnv("CLUB_DATA_MODE", "auto"))), // E-shop settings EshopEnabled: getEnvAsBool("ESHOP_ENABLED", false), EshopFrontendURL: getEnv("ESHOP_FRONTEND_URL", ""), EshopAPIURL: getEnv("ESHOP_API_URL", ""), // Stripe (E-shop payments) StripeEnabled: getEnvAsBool("STRIPE_ENABLED", false), StripeSecretKey: getEnv("STRIPE_SECRET_KEY", ""), StripePublishableKey: getEnv("STRIPE_PUBLISHABLE_KEY", ""), StripeWebhookSecret: getEnv("STRIPE_WEBHOOK_SECRET", ""), StripeCurrency: getEnv("STRIPE_CURRENCY", "CZK"), StripeReturnURL: getEnv("STRIPE_RETURN_URL", ""), // Revolut (E-shop payments) RevolutEnabled: getEnvAsBool("REVOLUT_ENABLED", false), RevolutEnvironment: getEnv("REVOLUT_ENVIRONMENT", "sandbox"), RevolutAPIKey: getEnv("REVOLUT_API_KEY", ""), RevolutPublicKey: getEnv("REVOLUT_PUBLIC_KEY", ""), RevolutWebhookSecret: getEnv("REVOLUT_WEBHOOK_SECRET", ""), RevolutReturnURL: getEnv("REVOLUT_RETURN_URL", ""), RevolutWebhookURL: getEnv("REVOLUT_WEBHOOK_URL", ""), // Packeta / Zasilkovna PacketaAPIPassword: getEnv("PACKETA_API_PASSWORD", ""), PacketaWidgetAPIKey: getEnv("PACKETA_WIDGET_API_KEY", ""), PacketaEshopName: getEnv("PACKETA_ESHP_NAME", "MyClubEshop"), PacketaEnv: getEnv("PACKETA_ENV", "test"), // DeepSeek AI Support (used by e-shop support chat) DeepSeekAPIKey: getEnv("DEEPSEEK_API_KEY", ""), DeepSeekBaseURL: getEnv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"), } // 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 }