Add DATABASE_URL parsing for Dokku compatibility

- Parse DATABASE_URL environment variable (postgres://...)
- Dokku sets this via postgres:link command
- Falls back to individual env vars if DATABASE_URL not set

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-26 20:14:39 -06:00
parent dd3a624ce2
commit 6955aca956

View File

@@ -2,7 +2,9 @@ package config
import (
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -96,6 +98,34 @@ func Load() (*Config, error) {
// Set defaults
setDefaults()
// Parse DATABASE_URL if set (Dokku-style)
dbConfig := DatabaseConfig{
Host: viper.GetString("DB_HOST"),
Port: viper.GetInt("DB_PORT"),
User: viper.GetString("POSTGRES_USER"),
Password: viper.GetString("POSTGRES_PASSWORD"),
Database: viper.GetString("POSTGRES_DB"),
SSLMode: viper.GetString("DB_SSLMODE"),
MaxOpenConns: viper.GetInt("DB_MAX_OPEN_CONNS"),
MaxIdleConns: viper.GetInt("DB_MAX_IDLE_CONNS"),
MaxLifetime: viper.GetDuration("DB_MAX_LIFETIME"),
}
// Override with DATABASE_URL if present
if databaseURL := viper.GetString("DATABASE_URL"); databaseURL != "" {
parsed, err := parseDatabaseURL(databaseURL)
if err == nil {
dbConfig.Host = parsed.Host
dbConfig.Port = parsed.Port
dbConfig.User = parsed.User
dbConfig.Password = parsed.Password
dbConfig.Database = parsed.Database
if parsed.SSLMode != "" {
dbConfig.SSLMode = parsed.SSLMode
}
}
}
cfg = &Config{
Server: ServerConfig{
Port: viper.GetInt("PORT"),
@@ -103,17 +133,7 @@ func Load() (*Config, error) {
AllowedHosts: strings.Split(viper.GetString("ALLOWED_HOSTS"), ","),
Timezone: viper.GetString("TIMEZONE"),
},
Database: DatabaseConfig{
Host: viper.GetString("DB_HOST"),
Port: viper.GetInt("DB_PORT"),
User: viper.GetString("POSTGRES_USER"),
Password: viper.GetString("POSTGRES_PASSWORD"),
Database: viper.GetString("POSTGRES_DB"),
SSLMode: viper.GetString("DB_SSLMODE"),
MaxOpenConns: viper.GetInt("DB_MAX_OPEN_CONNS"),
MaxIdleConns: viper.GetInt("DB_MAX_IDLE_CONNS"),
MaxLifetime: viper.GetDuration("DB_MAX_LIFETIME"),
},
Database: dbConfig,
Redis: RedisConfig{
URL: viper.GetString("REDIS_URL"),
Password: viper.GetString("REDIS_PASSWORD"),
@@ -239,3 +259,39 @@ func (p *PushConfig) ReadAPNSKey() (string, error) {
return string(content), nil
}
// parseDatabaseURL parses a PostgreSQL URL into DatabaseConfig
// Format: postgres://user:password@host:port/database?sslmode=disable
func parseDatabaseURL(databaseURL string) (*DatabaseConfig, error) {
u, err := url.Parse(databaseURL)
if err != nil {
return nil, fmt.Errorf("failed to parse DATABASE_URL: %w", err)
}
// Default port
port := 5432
if u.Port() != "" {
port, err = strconv.Atoi(u.Port())
if err != nil {
return nil, fmt.Errorf("invalid port in DATABASE_URL: %w", err)
}
}
// Get password
password, _ := u.User.Password()
// Get database name (remove leading slash)
database := strings.TrimPrefix(u.Path, "/")
// Get sslmode from query params
sslMode := u.Query().Get("sslmode")
return &DatabaseConfig{
Host: u.Hostname(),
Port: port,
User: u.User.Username(),
Password: password,
Database: database,
SSLMode: sslMode,
}, nil
}