| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- // Package config предоставляет конфигурацию приложения.
- // Загружает переменные окружения через godotenv, поддерживает Docker-секреты
- // через суффикс _FILE (getSecret), сборку DatabaseURL/RedisURL из компонентов.
- // Экспортирует структуру Config со всеми настройками (БД, S3, JWT, Redis, CORS).
- package config
- import (
- "os"
- "strings"
- "github.com/joho/godotenv"
- )
- type Config struct {
- AppEnv string
- ServerPort string
- DatabaseURL string
- RedisURL string
- S3Endpoint string
- S3PublicEndpoint string
- S3AccessKey string
- S3SecretKey string
- S3Bucket string
- JWTSecret string
- JWTRefreshSecret string
- // CloudPaymentsPublicID и CloudPaymentsAPISecret зарезервированы для будущей интеграции платежей
- CloudPaymentsPublicID string
- CloudPaymentsAPISecret string
- AllowedOrigins []string
- }
- func Load() *Config {
- godotenv.Load()
- appEnv := getEnv("APP_ENV", "development")
- isProd := appEnv == "production"
- cfg := &Config{
- AppEnv: appEnv,
- ServerPort: getEnv("SERVER_PORT", "8080"),
- DatabaseURL: buildDatabaseURL(),
- RedisURL: buildRedisURL(),
- S3Endpoint: getEnv("S3_ENDPOINT", "http://localhost:9000"),
- S3PublicEndpoint: getEnv("S3_PUBLIC_ENDPOINT", "http://localhost:9000"),
- S3AccessKey: getEnv("S3_ACCESS_KEY", "photoplaces"),
- S3SecretKey: getSecret("S3_SECRET_KEY", "photoplaces_dev"),
- S3Bucket: getEnv("S3_BUCKET", "photoplaces"),
- JWTSecret: getSecretOrRequire("JWT_SECRET", "dev-secret", isProd),
- JWTRefreshSecret: getSecretOrRequire("JWT_REFRESH_SECRET", "dev-refresh-secret", isProd),
- CloudPaymentsPublicID: getEnv("CLOUDPAYMENTS_PUBLIC_ID", ""),
- CloudPaymentsAPISecret: getEnv("CLOUDPAYMENTS_API_SECRET", ""),
- AllowedOrigins: parseAllowedOrigins(getEnv("ALLOWED_ORIGINS", ""), isProd),
- }
- cfg.validate()
- return cfg
- }
- func getSecretOrRequire(key, fallback string, required bool) string {
- val := getSecret(key, "")
- if val == "" {
- if required {
- panic("config: " + key + " must be set in production (env var or _FILE)")
- }
- return fallback
- }
- return val
- }
- func parseAllowedOrigins(val string, required bool) []string {
- if val == "" {
- if required {
- panic("config: ALLOWED_ORIGINS must be set in production")
- }
- return []string{"http://localhost:3000"}
- }
- return strings.Split(val, ",")
- }
- func (c *Config) validate() {
- if c.AppEnv != "production" {
- return
- }
- // Проверяем, что не используются дефолтные dev-значения
- if c.JWTSecret == "dev-secret" {
- panic("config validation failed: JWT_SECRET must be set to a secure value in production")
- }
- if c.JWTRefreshSecret == "dev-refresh-secret" {
- panic("config validation failed: JWT_REFRESH_SECRET must be set to a secure value in production")
- }
- if len(c.AllowedOrigins) == 1 && c.AllowedOrigins[0] == "http://localhost:3000" {
- panic("config validation failed: ALLOWED_ORIGINS must be set to production domains")
- }
- }
- func getEnv(key, fallback string) string {
- if v := os.Getenv(key); v != "" {
- return v
- }
- return fallback
- }
- func getSecret(key, fallback string) string {
- // Try _FILE variant first (Docker secrets)
- if v := os.Getenv(key + "_FILE"); v != "" {
- content, err := os.ReadFile(v)
- if err == nil {
- return strings.TrimSpace(string(content))
- }
- }
- // Fallback to env var
- if v := os.Getenv(key); v != "" {
- return v
- }
- return fallback
- }
- func buildDatabaseURL() string {
- // If DATABASE_URL is explicitly set, use it
- if v := os.Getenv("DATABASE_URL"); v != "" {
- return v
- }
- // Build from components with secret from file
- password := getSecret("DB_PASSWORD", "photoplaces_dev")
- return "postgres://photoplaces:" + password + "@postgres:5432/photoplaces?sslmode=disable"
- }
- func buildRedisURL() string {
- if v := os.Getenv("REDIS_URL"); v != "" {
- return v
- }
- password := getSecret("REDIS_PASSWORD", "")
- if password != "" {
- return "redis://:" + password + "@redis:6379/0"
- }
- return "redis://redis:6379/0"
- }
|