config.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package config
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "time"
  7. )
  8. // Config holds the application configuration
  9. type Config struct {
  10. DBHost string
  11. DBPort string
  12. DBUser string
  13. DBPassword string
  14. DBName string
  15. JWTSecret string
  16. ServerPort string
  17. GinMode string
  18. StoragePath string
  19. AccessTokenTTL time.Duration
  20. RefreshTokenTTL time.Duration
  21. }
  22. // Load reads configuration from environment variables with fallbacks
  23. func Load() *Config {
  24. return &Config{
  25. DBHost: getEnv("DB_HOST", "localhost"),
  26. DBPort: getEnv("DB_PORT", "5432"),
  27. DBUser: getEnv("DB_USER", "celestia"),
  28. DBPassword: getEnv("DB_PASSWORD", "celestia_secret"),
  29. DBName: getEnv("DB_NAME", "celestia_trace"),
  30. JWTSecret: getEnv("JWT_SECRET", "change-me-in-production"),
  31. ServerPort: getEnv("SERVER_PORT", "8080"),
  32. GinMode: getEnv("GIN_MODE", "release"),
  33. StoragePath: getEnv("STORAGE_PATH", "/data/uploads"),
  34. AccessTokenTTL: getDurationEnv("ACCESS_TOKEN_TTL", 30*time.Minute),
  35. RefreshTokenTTL: getDurationEnv("REFRESH_TOKEN_TTL", 30*24*time.Hour),
  36. }
  37. }
  38. // Validate prevents production from silently starting with repository defaults.
  39. func (c *Config) Validate() error {
  40. requiredDatabaseSettings := map[string]string{
  41. "DB_HOST": c.DBHost,
  42. "DB_PORT": c.DBPort,
  43. "DB_USER": c.DBUser,
  44. "DB_PASSWORD": c.DBPassword,
  45. "DB_NAME": c.DBName,
  46. }
  47. for name, value := range requiredDatabaseSettings {
  48. if strings.TrimSpace(value) == "" {
  49. return fmt.Errorf("%s must not be empty", name)
  50. }
  51. }
  52. if c.GinMode == "release" {
  53. weakSecrets := []string{"", "change-me-in-production", "your_jwt_secret_here_change_me"}
  54. for _, weak := range weakSecrets {
  55. if c.JWTSecret == weak {
  56. return fmt.Errorf("JWT_SECRET must be configured with a production secret")
  57. }
  58. }
  59. if len(c.JWTSecret) < 32 {
  60. return fmt.Errorf("JWT_SECRET must contain at least 32 characters")
  61. }
  62. }
  63. if strings.TrimSpace(c.StoragePath) == "" {
  64. return fmt.Errorf("STORAGE_PATH must not be empty")
  65. }
  66. return nil
  67. }
  68. func getEnv(key, fallback string) string {
  69. if value, exists := os.LookupEnv(key); exists {
  70. return value
  71. }
  72. return fallback
  73. }
  74. func getDurationEnv(key string, fallback time.Duration) time.Duration {
  75. value, exists := os.LookupEnv(key)
  76. if !exists {
  77. return fallback
  78. }
  79. parsed, err := time.ParseDuration(value)
  80. if err != nil || parsed <= 0 {
  81. return fallback
  82. }
  83. return parsed
  84. }