package config import ( "fmt" "os" "strconv" ) type Config struct { // Server Port string BaseURL string // R2 R2AccountID string R2AccessKeyID string R2SecretAccessKey string R2BucketName string R2Endpoint string // Limits MaxUploadSize int64 // bytes DefaultExpiryDays int // 0 = no default expiry // Rate limiting RateLimit float64 // requests per second per IP RateBurst int // Database DBPath string } func Load() (*Config, error) { c := &Config{ Port: getEnv("PORT", "8080"), BaseURL: getEnv("BASE_URL", "http://localhost:8080"), R2AccountID: os.Getenv("R2_ACCOUNT_ID"), R2AccessKeyID: os.Getenv("R2_ACCESS_KEY_ID"), R2SecretAccessKey: os.Getenv("R2_SECRET_ACCESS_KEY"), R2BucketName: getEnv("R2_BUCKET_NAME", "uploads"), DBPath: getEnv("DB_PATH", "/data/upload.db"), } c.R2Endpoint = fmt.Sprintf("https://%s.r2.cloudflarestorage.com", c.R2AccountID) maxSize, err := strconv.ParseInt(getEnv("MAX_UPLOAD_SIZE", "5368709120"), 10, 64) // 5GB default if err != nil { return nil, fmt.Errorf("invalid MAX_UPLOAD_SIZE: %w", err) } c.MaxUploadSize = maxSize c.DefaultExpiryDays, _ = strconv.Atoi(getEnv("DEFAULT_EXPIRY_DAYS", "0")) rateLimit, _ := strconv.ParseFloat(getEnv("RATE_LIMIT", "2"), 64) c.RateLimit = rateLimit c.RateBurst, _ = strconv.Atoi(getEnv("RATE_BURST", "5")) if c.R2AccountID == "" || c.R2AccessKeyID == "" || c.R2SecretAccessKey == "" { return nil, fmt.Errorf("R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY are required") } return c, nil } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }