Files
autoglue/internal/config/config.go
allanice001 3a1ce33bca feat: adding embedded db-studio
Signed-off-by: allanice001 <allanice001@gmail.com>
2025-11-11 03:19:09 +00:00

225 lines
5.9 KiB
Go

package config
import (
"errors"
"fmt"
"strings"
"sync"
"github.com/joho/godotenv"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
type Config struct {
DbURL string
DbURLRO string
Port string
Host string
JWTIssuer string
JWTAudience string
JWTPrivateEncKey string
OAuthRedirectBase string
GoogleClientID string
GoogleClientSecret string
GithubClientID string
GithubClientSecret string
UIDev bool
Env string
Debug bool
Swagger bool
SwaggerHost string
DBStudioEnabled bool
DBStudioBind string
DBStudioPort string
DBStudioUser string
DBStudioPass string
}
var (
once sync.Once
cached Config
loadErr error
)
func Load() (Config, error) {
once.Do(func() {
_ = godotenv.Load()
// Use a private viper to avoid global mutation/races
v := viper.New()
// Defaults
v.SetDefault("bind.address", "127.0.0.1")
v.SetDefault("bind.port", "8080")
v.SetDefault("database.url", "postgres://user:pass@localhost:5432/db?sslmode=disable")
v.SetDefault("database.url_ro", "")
v.SetDefault("db_studio.enabled", false)
v.SetDefault("db_studio.bind", "127.0.0.1")
v.SetDefault("db_studio.port", "0") // 0 = random
v.SetDefault("db_studio.user", "")
v.SetDefault("db_studio.pass", "")
v.SetDefault("ui.dev", false)
v.SetDefault("env", "development")
v.SetDefault("debug", false)
v.SetDefault("swagger", false)
v.SetDefault("swagger.host", "localhost:8080")
// Env setup and binding
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
keys := []string{
"bind.address",
"bind.port",
"database.url",
"database.url_ro",
"jwt.issuer",
"jwt.audience",
"jwt.private.enc.key",
"oauth.redirect.base",
"google.client.id",
"google.client.secret",
"github.client.id",
"github.client.secret",
"ui.dev",
"env",
"debug",
"swagger",
"swagger.host",
"db_studio.enabled",
"db_studio.bind",
"db_studio.port",
"db_studio.user",
"db_studio.pass",
}
for _, k := range keys {
_ = v.BindEnv(k)
}
// Build config
cfg := Config{
DbURL: v.GetString("database.url"),
DbURLRO: v.GetString("database.url_ro"),
Port: v.GetString("bind.port"),
Host: v.GetString("bind.address"),
JWTIssuer: v.GetString("jwt.issuer"),
JWTAudience: v.GetString("jwt.audience"),
JWTPrivateEncKey: v.GetString("jwt.private.enc.key"),
OAuthRedirectBase: v.GetString("oauth.redirect.base"),
GoogleClientID: v.GetString("google.client.id"),
GoogleClientSecret: v.GetString("google.client.secret"),
GithubClientID: v.GetString("github.client.id"),
GithubClientSecret: v.GetString("github.client.secret"),
UIDev: v.GetBool("ui.dev"),
Env: v.GetString("env"),
Debug: v.GetBool("debug"),
Swagger: v.GetBool("swagger"),
SwaggerHost: v.GetString("swagger.host"),
DBStudioEnabled: v.GetBool("db_studio.enabled"),
DBStudioBind: v.GetString("db_studio.bind"),
DBStudioPort: v.GetString("db_studio.port"),
DBStudioUser: v.GetString("db_studio.user"),
DBStudioPass: v.GetString("db_studio.pass"),
}
// Validate
if err := validateConfig(cfg); err != nil {
loadErr = err
return
}
cached = cfg
})
return cached, loadErr
}
func validateConfig(cfg Config) error {
var errs []string
// Required general settings
req := map[string]string{
"jwt.issuer": cfg.JWTIssuer,
"jwt.audience": cfg.JWTAudience,
"jwt.private.enc.key": cfg.JWTPrivateEncKey,
"oauth.redirect.base": cfg.OAuthRedirectBase,
}
for k, v := range req {
if strings.TrimSpace(v) == "" {
errs = append(errs, fmt.Sprintf("missing required config key %q (env %s)", k, envNameFromKey(k)))
}
}
// OAuth provider requirements:
googleOK := strings.TrimSpace(cfg.GoogleClientID) != "" && strings.TrimSpace(cfg.GoogleClientSecret) != ""
githubOK := strings.TrimSpace(cfg.GithubClientID) != "" && strings.TrimSpace(cfg.GithubClientSecret) != ""
// If partially configured, report what's missing for each
if !googleOK && (cfg.GoogleClientID != "" || cfg.GoogleClientSecret != "") {
if cfg.GoogleClientID == "" {
errs = append(errs, fmt.Sprintf("google.client.id is missing (env %s) while google.client.secret is set", envNameFromKey("google.client.id")))
}
if cfg.GoogleClientSecret == "" {
errs = append(errs, fmt.Sprintf("google.client.secret is missing (env %s) while google.client.id is set", envNameFromKey("google.client.secret")))
}
}
if !githubOK && (cfg.GithubClientID != "" || cfg.GithubClientSecret != "") {
if cfg.GithubClientID == "" {
errs = append(errs, fmt.Sprintf("github.client.id is missing (env %s) while github.client.secret is set", envNameFromKey("github.client.id")))
}
if cfg.GithubClientSecret == "" {
errs = append(errs, fmt.Sprintf("github.client.secret is missing (env %s) while github.client.id is set", envNameFromKey("github.client.secret")))
}
}
// Enforce minimum: at least one full provider
if !googleOK && !githubOK {
errs = append(errs, "at least one OAuth provider must be fully configured: either Google (google.client.id + google.client.secret) or GitHub (github.client.id + github.client.secret)")
}
if len(errs) > 0 {
return errors.New(strings.Join(errs, "; "))
}
return nil
}
func envNameFromKey(key string) string {
return strings.ToUpper(strings.ReplaceAll(key, ".", "_"))
}
func DebugPrintConfig() {
cfg, _ := Load()
b, err := yaml.Marshal(cfg)
if err != nil {
fmt.Println("error marshalling config:", err)
return
}
fmt.Println("Loaded configuration:")
fmt.Println(string(b))
}
func IsUIDev() bool {
cfg, _ := Load()
return cfg.UIDev
}
func IsDev() bool {
cfg, _ := Load()
return strings.EqualFold(cfg.Env, "development")
}
func IsDebug() bool {
cfg, _ := Load()
return cfg.Debug
}
func IsSwaggerEnabled() bool {
cfg, _ := Load()
return cfg.Swagger
}