Files
go_oxspeak_server/config/config.go
2025-11-11 20:47:43 +01:00

153 lines
3.6 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"sync"
"github.com/BurntSushi/toml"
)
type Config struct {
Server ServerConfig `toml:"server"`
Database DatabaseConfig `toml:"database"`
JWT JWTConfig `toml:"jwt"`
}
type ServerConfig struct {
BindAddr string `toml:"bind_addr"`
Mode string `toml:"mode"` // "debug" or "release"
}
type DatabaseConfig struct {
Type string `toml:"type"` // "sqlite", "postgres", "mysql"
Path string `toml:"path"` // For SQLite
Host string `toml:"host"` // For PostgreSQL/MySQL
Port int `toml:"port"` // For PostgreSQL/MySQL
User string `toml:"user"` // For PostgreSQL/MySQL
Password string `toml:"password"` // For PostgreSQL/MySQL
DBName string `toml:"dbname"` // For PostgreSQL/MySQL
SSLMode string `toml:"sslmode"` // For PostgreSQL
}
type JWTConfig struct {
Secret string `toml:"secret"`
Expiration int `toml:"expiration"` // in hours
}
var (
instance *Config
once sync.Once
)
// Load loads the configuration from a TOML file
func Load(configPath string) (*Config, error) {
var err error
once.Do(func() {
instance = &Config{}
if _, loadErr := toml.DecodeFile(configPath, instance); loadErr != nil {
err = fmt.Errorf("failed to load configuration: %w", loadErr)
return
}
// Default values if not specified
if instance.Server.BindAddr == "" {
instance.Server.BindAddr = "0.0.0.0:7000"
}
if instance.Server.Mode == "" {
instance.Server.Mode = "release"
}
if instance.Database.Type == "" {
instance.Database.Type = "sqlite"
}
if instance.Database.Path == "" {
instance.Database.Path = "./oxspeak.db"
}
if instance.JWT.Expiration == 0 {
instance.JWT.Expiration = 24
}
// Validation
if instance.JWT.Secret == "" {
err = fmt.Errorf("JWT secret is required")
return
}
if instance.Database.Type != "sqlite" && instance.Database.Type != "postgres" && instance.Database.Type != "mysql" {
err = fmt.Errorf("invalid database type: %s (accepted values: sqlite, postgres, mysql)", instance.Database.Type)
return
}
})
return instance, err
}
// Get returns the configuration instance (must be loaded first)
func Get() *Config {
if instance == nil {
panic("configuration not loaded. Call Load() first")
}
return instance
}
// GetDSN returns the connection string based on the database type
func (c *Config) GetDSN() string {
switch c.Database.Type {
case "sqlite":
return c.Database.Path
case "postgres":
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.Database.Host,
c.Database.Port,
c.Database.User,
c.Database.Password,
c.Database.DBName,
c.Database.SSLMode,
)
case "mysql":
return fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
c.Database.User,
c.Database.Password,
c.Database.Host,
c.Database.Port,
c.Database.DBName,
)
default:
return ""
}
}
// GetDialector returns the GORM dialect name
func (c *Config) GetDialector() string {
return c.Database.Type
}
// FindConfigFile searches for the config file in multiple locations
func FindConfigFile() (string, error) {
locations := []string{
"./config.toml",
"./config/config.toml",
"/etc/oxspeak/config.toml",
}
// Add executable directory
if exePath, err := os.Executable(); err == nil {
exeDir := filepath.Dir(exePath)
locations = append([]string{
filepath.Join(exeDir, "config.toml"),
}, locations...)
}
for _, path := range locations {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("configuration file not found in locations: %v", locations)
}