package config import ( "context" "fmt" "os" "strconv" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/joho/godotenv" ) // Config содержит конфигурацию приложения type Config struct { // Database DatabaseURL string // Server ServerHost string ServerPort string AppEnv string // JWT JWTSecret string JWTAccessTokenTTL time.Duration JWTRefreshTokenTTL time.Duration // CORS CORSAllowedOrigins []string CORSAllowedMethods []string CORSAllowedHeaders []string // Rate Limiting RateLimitRequestsPerMinute int RateLimitBurst int // Matching Algorithm MatchingDefaultRadiusMeters int MatchingDefaultLimit int } // Load загружает конфигурацию из переменных окружения func Load() (*Config, error) { // Загружаем .env файл если он существует _ = godotenv.Load() cfg := &Config{ DatabaseURL: getDatabaseURL(), ServerHost: getEnv("APP_HOST", "0.0.0.0"), ServerPort: getEnv("APP_PORT", "8080"), AppEnv: getEnv("APP_ENV", "development"), JWTSecret: getEnv("JWT_SECRET", "change_me_to_secure_random_string_min_32_chars"), JWTAccessTokenTTL: parseDuration(getEnv("JWT_ACCESS_TOKEN_EXPIRY", "15m")), JWTRefreshTokenTTL: parseDuration(getEnv("JWT_REFRESH_TOKEN_EXPIRY", "168h")), // 7 days RateLimitRequestsPerMinute: getEnvInt("RATE_LIMIT_REQUESTS_PER_MINUTE", 60), RateLimitBurst: getEnvInt("RATE_LIMIT_BURST", 10), MatchingDefaultRadiusMeters: getEnvInt("MATCHING_DEFAULT_RADIUS_METERS", 10000), MatchingDefaultLimit: getEnvInt("MATCHING_DEFAULT_LIMIT", 20), } return cfg, nil } // NewDBPool создает новый пул соединений с БД func NewDBPool(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { config, err := pgxpool.ParseConfig(databaseURL) if err != nil { return nil, fmt.Errorf("failed to parse database URL: %w", err) } // Настройка пула соединений config.MaxConns = 25 config.MinConns = 5 config.MaxConnLifetime = time.Hour config.MaxConnIdleTime = time.Minute * 30 config.HealthCheckPeriod = time.Minute pool, err := pgxpool.NewWithConfig(ctx, config) if err != nil { return nil, fmt.Errorf("failed to create connection pool: %w", err) } // Проверка соединения if err := pool.Ping(ctx); err != nil { return nil, fmt.Errorf("failed to ping database: %w", err) } return pool, nil } // getDatabaseURL собирает DATABASE_URL из отдельных переменных или использует готовый func getDatabaseURL() string { // Если задан DATABASE_URL, используем его if url := os.Getenv("DATABASE_URL"); url != "" { return url } // Иначе собираем из отдельных переменных user := getEnv("DB_USER", "volontery") password := getEnv("DB_PASSWORD", "volontery") host := getEnv("DB_HOST", "localhost") port := getEnv("DB_PORT", "5432") name := getEnv("DB_NAME", "volontery_db") sslmode := getEnv("DB_SSLMODE", "disable") return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s", user, password, host, port, name, sslmode) } // getEnv получает переменную окружения или возвращает значение по умолчанию func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // getEnvInt получает целочисленную переменную окружения func getEnvInt(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { if intVal, err := strconv.Atoi(value); err == nil { return intVal } } return defaultValue } // parseDuration парсит duration строку func parseDuration(s string) time.Duration { d, err := time.ParseDuration(s) if err != nil { return 15 * time.Minute // default } return d }