initial commit

This commit is contained in:
2025-11-29 00:28:21 +05:00
parent 46229acc82
commit ec3b03a935
76 changed files with 13492 additions and 0 deletions

139
internal/config/config.go Normal file
View File

@@ -0,0 +1,139 @@
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
}