small fix, don't worry about it

This commit is contained in:
Tomas Dvorak
2026-04-10 12:06:24 +02:00
commit 5c500a72b0
243 changed files with 44176 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
package config
import (
"fmt"
"os"
"slices"
"strconv"
"strings"
"time"
"github.com/joho/godotenv"
)
type Config struct {
Env string
AppName string
HTTP HTTPConfig
CORS CORSConfig
Postgres PostgresConfig
Cache CacheConfig
Auth AuthConfig
TMDB TMDBConfig
IGDB IGDBConfig
}
type HTTPConfig struct {
Host string
Port int
ReadTimeout time.Duration
WriteTimeout time.Duration
}
type CORSConfig struct {
AllowedOrigins []string
AllowedMethods []string
AllowedHeaders []string
ExposedHeaders []string
AllowCredentials bool
MaxAge time.Duration
}
type PostgresConfig struct {
URL string
MaxConns int32
MinConns int32
}
type CacheConfig struct {
Addr string
Password string
DB int
}
type AuthConfig struct {
AccessTokenTTLMinutes int
RefreshTokenTTLHours int
JWTSecret string
}
type TMDBConfig struct {
APIKey string
BaseURL string
}
type IGDBConfig struct {
ClientID string
ClientSecret string
BaseURL string
TokenURL string
}
func Load() Config {
_ = godotenv.Load()
return Config{
Env: getString("SEEN_ENV", "development"),
AppName: getString("SEEN_APP_NAME", "seen"),
HTTP: HTTPConfig{
Host: getString("SEEN_HTTP_HOST", "0.0.0.0"),
Port: getInt("SEEN_HTTP_PORT", 8081),
ReadTimeout: getDuration("SEEN_HTTP_READ_TIMEOUT", 15*time.Second),
WriteTimeout: getDuration("SEEN_HTTP_WRITE_TIMEOUT", 15*time.Second),
},
CORS: CORSConfig{
AllowedOrigins: getCSV("SEEN_CORS_ALLOWED_ORIGINS", []string{}),
AllowedMethods: getCSV("SEEN_CORS_ALLOWED_METHODS", []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
AllowedHeaders: getCSV("SEEN_CORS_ALLOWED_HEADERS", []string{"Accept", "Authorization", "Content-Type", "X-Request-ID"}),
ExposedHeaders: getCSV("SEEN_CORS_EXPOSED_HEADERS", []string{"X-Request-ID"}),
AllowCredentials: getBool("SEEN_CORS_ALLOW_CREDENTIALS", false),
MaxAge: getDuration("SEEN_CORS_MAX_AGE", 24*time.Hour),
},
Postgres: PostgresConfig{
URL: getString("SEEN_POSTGRES_URL", "postgres://seen:seen@localhost:5432/seen?sslmode=disable"),
MaxConns: int32(getInt("SEEN_POSTGRES_MAX_CONNS", 10)),
MinConns: int32(getInt("SEEN_POSTGRES_MIN_CONNS", 2)),
},
Cache: CacheConfig{
Addr: getString("SEEN_CACHE_ADDR", "localhost:6379"),
Password: getString("SEEN_CACHE_PASSWORD", ""),
DB: getInt("SEEN_CACHE_DB", 0),
},
Auth: AuthConfig{
AccessTokenTTLMinutes: getInt("SEEN_AUTH_ACCESS_TOKEN_TTL_MINUTES", 30),
RefreshTokenTTLHours: getInt("SEEN_AUTH_REFRESH_TOKEN_TTL_HOURS", 24*30),
JWTSecret: getString("SEEN_AUTH_JWT_SECRET", "replace-in-production"),
},
TMDB: TMDBConfig{
APIKey: getString("SEEN_TMDB_API_KEY", ""),
BaseURL: getString("SEEN_TMDB_BASE_URL", "https://api.themoviedb.org/3"),
},
IGDB: IGDBConfig{
ClientID: getString("SEEN_IGDB_CLIENT_ID", ""),
ClientSecret: getString("SEEN_IGDB_CLIENT_SECRET", ""),
BaseURL: getString("SEEN_IGDB_BASE_URL", "https://api.igdb.com/v4"),
TokenURL: getString("SEEN_IGDB_TOKEN_URL", "https://id.twitch.tv/oauth2/token"),
},
}
}
func (c HTTPConfig) Addr() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
func getString(key string, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func getInt(key string, fallback int) int {
value, ok := os.LookupEnv(key)
if !ok {
return fallback
}
parsed, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return parsed
}
func getDuration(key string, fallback time.Duration) time.Duration {
value, ok := os.LookupEnv(key)
if !ok {
return fallback
}
parsed, err := time.ParseDuration(value)
if err != nil {
return fallback
}
return parsed
}
func getBool(key string, fallback bool) bool {
value, ok := os.LookupEnv(key)
if !ok {
return fallback
}
switch strings.ToLower(strings.TrimSpace(value)) {
case "1", "true", "yes", "on":
return true
case "0", "false", "no", "off":
return false
default:
return fallback
}
}
func getCSV(key string, fallback []string) []string {
value, ok := os.LookupEnv(key)
if !ok {
return slices.Clone(fallback)
}
parts := strings.Split(value, ",")
items := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}
items = append(items, trimmed)
}
if len(items) == 0 {
return slices.Clone(fallback)
}
return items
}
+71
View File
@@ -0,0 +1,71 @@
package config
import (
"testing"
"time"
)
func TestLoadDefaults(t *testing.T) {
t.Setenv("SEEN_HTTP_PORT", "invalid")
t.Setenv("SEEN_HTTP_READ_TIMEOUT", "invalid")
cfg := Load()
if cfg.HTTP.Port != 8081 {
t.Fatalf("expected default port 8081, got %d", cfg.HTTP.Port)
}
if cfg.HTTP.ReadTimeout != 15*time.Second {
t.Fatalf("expected default read timeout, got %s", cfg.HTTP.ReadTimeout)
}
}
func TestLoadCustomValues(t *testing.T) {
t.Setenv("SEEN_ENV", "test")
t.Setenv("SEEN_HTTP_PORT", "9090")
t.Setenv("SEEN_HTTP_READ_TIMEOUT", "5s")
t.Setenv("SEEN_POSTGRES_MAX_CONNS", "22")
t.Setenv("SEEN_CACHE_DB", "3")
t.Setenv("SEEN_IGDB_CLIENT_ID", "client-id")
t.Setenv("SEEN_IGDB_TOKEN_URL", "https://id.twitch.tv/oauth2/token")
t.Setenv("SEEN_CORS_ALLOWED_ORIGINS", "https://seen.example.com, https://www.seen.example.com")
t.Setenv("SEEN_CORS_ALLOW_CREDENTIALS", "true")
cfg := Load()
if cfg.Env != "test" {
t.Fatalf("expected env test, got %q", cfg.Env)
}
if cfg.HTTP.Port != 9090 {
t.Fatalf("expected http port 9090, got %d", cfg.HTTP.Port)
}
if cfg.HTTP.ReadTimeout != 5*time.Second {
t.Fatalf("expected read timeout 5s, got %s", cfg.HTTP.ReadTimeout)
}
if cfg.Postgres.MaxConns != 22 {
t.Fatalf("expected max conns 22, got %d", cfg.Postgres.MaxConns)
}
if cfg.Cache.DB != 3 {
t.Fatalf("expected cache db 3, got %d", cfg.Cache.DB)
}
if cfg.IGDB.ClientID != "client-id" {
t.Fatalf("expected igdb client id to load, got %q", cfg.IGDB.ClientID)
}
if cfg.IGDB.TokenURL != "https://id.twitch.tv/oauth2/token" {
t.Fatalf("expected igdb token url to load, got %q", cfg.IGDB.TokenURL)
}
if len(cfg.CORS.AllowedOrigins) != 2 {
t.Fatalf("expected 2 cors origins, got %d", len(cfg.CORS.AllowedOrigins))
}
if !cfg.CORS.AllowCredentials {
t.Fatal("expected allow credentials to be true")
}
}