mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-06 20:04:25 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47cfc4b3da | |||
| 0e8ae75daf | |||
| ce092d1c62 | |||
| 871dd2e374 | |||
|
|
ebd03d10ad |
@@ -2,6 +2,9 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,6 +94,160 @@ func (c *DBManagerConfig) ToManagerConfig() interface{} {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PopulateFromDSN parses a DSN and populates the connection fields
|
||||||
|
func (cc *DBConnectionConfig) PopulateFromDSN() error {
|
||||||
|
if cc.DSN == "" {
|
||||||
|
return nil // Nothing to populate
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cc.Type {
|
||||||
|
case "postgres":
|
||||||
|
return cc.populatePostgresDSN()
|
||||||
|
case "mongodb":
|
||||||
|
return cc.populateMongoDSN()
|
||||||
|
case "mssql":
|
||||||
|
return cc.populateMSSQLDSN()
|
||||||
|
case "sqlite":
|
||||||
|
return cc.populateSQLiteDSN()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot parse DSN for unsupported database type: %s", cc.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populatePostgresDSN parses PostgreSQL DSN format
|
||||||
|
// Example: host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable
|
||||||
|
func (cc *DBConnectionConfig) populatePostgresDSN() error {
|
||||||
|
parts := strings.Fields(cc.DSN)
|
||||||
|
for _, part := range parts {
|
||||||
|
kv := strings.SplitN(part, "=", 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key, value := kv[0], kv[1]
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "host":
|
||||||
|
cc.Host = value
|
||||||
|
case "port":
|
||||||
|
port, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid port in DSN: %w", err)
|
||||||
|
}
|
||||||
|
cc.Port = port
|
||||||
|
case "user":
|
||||||
|
cc.User = value
|
||||||
|
case "password":
|
||||||
|
cc.Password = value
|
||||||
|
case "dbname":
|
||||||
|
cc.Database = value
|
||||||
|
case "sslmode":
|
||||||
|
cc.SSLMode = value
|
||||||
|
case "search_path":
|
||||||
|
cc.Schema = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateMongoDSN parses MongoDB DSN format
|
||||||
|
// Example: mongodb://user:password@host:port/database?authSource=admin&replicaSet=rs0
|
||||||
|
func (cc *DBConnectionConfig) populateMongoDSN() error {
|
||||||
|
u, err := url.Parse(cc.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid MongoDB DSN: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract user and password
|
||||||
|
if u.User != nil {
|
||||||
|
cc.User = u.User.Username()
|
||||||
|
if password, ok := u.User.Password(); ok {
|
||||||
|
cc.Password = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract host and port
|
||||||
|
if u.Host != "" {
|
||||||
|
host := u.Host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
hostPort := strings.SplitN(host, ":", 2)
|
||||||
|
cc.Host = hostPort[0]
|
||||||
|
if port, err := strconv.Atoi(hostPort[1]); err == nil {
|
||||||
|
cc.Port = port
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cc.Host = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract database
|
||||||
|
if u.Path != "" {
|
||||||
|
cc.Database = strings.TrimPrefix(u.Path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract query parameters
|
||||||
|
params := u.Query()
|
||||||
|
if authSource := params.Get("authSource"); authSource != "" {
|
||||||
|
cc.AuthSource = authSource
|
||||||
|
}
|
||||||
|
if replicaSet := params.Get("replicaSet"); replicaSet != "" {
|
||||||
|
cc.ReplicaSet = replicaSet
|
||||||
|
}
|
||||||
|
if readPref := params.Get("readPreference"); readPref != "" {
|
||||||
|
cc.ReadPreference = readPref
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateMSSQLDSN parses MSSQL DSN format
|
||||||
|
// Example: sqlserver://username:password@host:port?database=dbname&schema=dbo
|
||||||
|
func (cc *DBConnectionConfig) populateMSSQLDSN() error {
|
||||||
|
u, err := url.Parse(cc.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid MSSQL DSN: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract user and password
|
||||||
|
if u.User != nil {
|
||||||
|
cc.User = u.User.Username()
|
||||||
|
if password, ok := u.User.Password(); ok {
|
||||||
|
cc.Password = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract host and port
|
||||||
|
if u.Host != "" {
|
||||||
|
host := u.Host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
hostPort := strings.SplitN(host, ":", 2)
|
||||||
|
cc.Host = hostPort[0]
|
||||||
|
if port, err := strconv.Atoi(hostPort[1]); err == nil {
|
||||||
|
cc.Port = port
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cc.Host = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract query parameters
|
||||||
|
params := u.Query()
|
||||||
|
if database := params.Get("database"); database != "" {
|
||||||
|
cc.Database = database
|
||||||
|
}
|
||||||
|
if schema := params.Get("schema"); schema != "" {
|
||||||
|
cc.Schema = schema
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populateSQLiteDSN parses SQLite DSN format
|
||||||
|
// Example: /path/to/database.db or :memory:
|
||||||
|
func (cc *DBConnectionConfig) populateSQLiteDSN() error {
|
||||||
|
cc.FilePath = cc.DSN
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates the DBManager configuration
|
// Validate validates the DBManager configuration
|
||||||
func (c *DBManagerConfig) Validate() error {
|
func (c *DBManagerConfig) Validate() error {
|
||||||
if len(c.Connections) == 0 {
|
if len(c.Connections) == 0 {
|
||||||
|
|||||||
@@ -97,6 +97,29 @@ func (m *Manager) GetConfig() (*Config, error) {
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetConfig sets the complete configuration
|
||||||
|
func (m *Manager) SetConfig(cfg *Config) error {
|
||||||
|
configMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Marshal the config to a map structure that viper can use
|
||||||
|
if err := m.v.Unmarshal(&configMap); err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare config map: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use viper's merge to apply the config
|
||||||
|
m.v.Set("server", cfg.Server)
|
||||||
|
m.v.Set("tracing", cfg.Tracing)
|
||||||
|
m.v.Set("cache", cfg.Cache)
|
||||||
|
m.v.Set("logger", cfg.Logger)
|
||||||
|
m.v.Set("error_tracking", cfg.ErrorTracking)
|
||||||
|
m.v.Set("middleware", cfg.Middleware)
|
||||||
|
m.v.Set("cors", cfg.CORS)
|
||||||
|
m.v.Set("event_broker", cfg.EventBroker)
|
||||||
|
m.v.Set("dbmanager", cfg.DBManager)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns a configuration value by key
|
// Get returns a configuration value by key
|
||||||
func (m *Manager) Get(key string) interface{} {
|
func (m *Manager) Get(key string) interface{} {
|
||||||
return m.v.Get(key)
|
return m.v.Get(key)
|
||||||
@@ -122,6 +145,14 @@ func (m *Manager) Set(key string, value interface{}) {
|
|||||||
m.v.Set(key, value)
|
m.v.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveConfig writes the current configuration to the specified path
|
||||||
|
func (m *Manager) SaveConfig(path string) error {
|
||||||
|
if err := m.v.WriteConfigAs(path); err != nil {
|
||||||
|
return fmt.Errorf("failed to save config to %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// setDefaults sets default configuration values
|
// setDefaults sets default configuration values
|
||||||
func setDefaults(v *viper.Viper) {
|
func setDefaults(v *viper.Viper) {
|
||||||
// Server defaults
|
// Server defaults
|
||||||
@@ -166,6 +197,34 @@ func setDefaults(v *viper.Viper) {
|
|||||||
// Database defaults
|
// Database defaults
|
||||||
v.SetDefault("database.url", "")
|
v.SetDefault("database.url", "")
|
||||||
|
|
||||||
|
// Database Manager defaults
|
||||||
|
v.SetDefault("dbmanager.default_connection", "default")
|
||||||
|
v.SetDefault("dbmanager.max_open_conns", 25)
|
||||||
|
v.SetDefault("dbmanager.max_idle_conns", 5)
|
||||||
|
v.SetDefault("dbmanager.conn_max_lifetime", "30m")
|
||||||
|
v.SetDefault("dbmanager.conn_max_idle_time", "5m")
|
||||||
|
v.SetDefault("dbmanager.retry_attempts", 3)
|
||||||
|
v.SetDefault("dbmanager.retry_delay", "1s")
|
||||||
|
v.SetDefault("dbmanager.retry_max_delay", "10s")
|
||||||
|
v.SetDefault("dbmanager.health_check_interval", "30s")
|
||||||
|
v.SetDefault("dbmanager.enable_auto_reconnect", true)
|
||||||
|
|
||||||
|
// Default PostgreSQL connection
|
||||||
|
v.SetDefault("dbmanager.connections.default.name", "default")
|
||||||
|
v.SetDefault("dbmanager.connections.default.type", "postgres")
|
||||||
|
v.SetDefault("dbmanager.connections.default.host", "localhost")
|
||||||
|
v.SetDefault("dbmanager.connections.default.port", 5432)
|
||||||
|
v.SetDefault("dbmanager.connections.default.user", "postgres")
|
||||||
|
v.SetDefault("dbmanager.connections.default.password", "")
|
||||||
|
v.SetDefault("dbmanager.connections.default.database", "resolvespec")
|
||||||
|
v.SetDefault("dbmanager.connections.default.sslmode", "disable")
|
||||||
|
v.SetDefault("dbmanager.connections.default.connect_timeout", "10s")
|
||||||
|
v.SetDefault("dbmanager.connections.default.query_timeout", "30s")
|
||||||
|
v.SetDefault("dbmanager.connections.default.enable_tracing", false)
|
||||||
|
v.SetDefault("dbmanager.connections.default.enable_metrics", false)
|
||||||
|
v.SetDefault("dbmanager.connections.default.enable_logging", false)
|
||||||
|
v.SetDefault("dbmanager.connections.default.default_orm", "bun")
|
||||||
|
|
||||||
// Event Broker defaults
|
// Event Broker defaults
|
||||||
v.SetDefault("event_broker.enabled", false)
|
v.SetDefault("event_broker.enabled", false)
|
||||||
v.SetDefault("event_broker.provider", "memory")
|
v.SetDefault("event_broker.provider", "memory")
|
||||||
|
|||||||
@@ -50,6 +50,59 @@ type connectionManager struct {
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// singleton instance of the manager
|
||||||
|
instance Manager
|
||||||
|
// instanceMu protects the singleton instance
|
||||||
|
instanceMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetupManager initializes the singleton database manager with the provided configuration.
|
||||||
|
// This function must be called before GetInstance().
|
||||||
|
// Returns an error if the manager is already initialized or if configuration is invalid.
|
||||||
|
func SetupManager(cfg ManagerConfig) error {
|
||||||
|
instanceMu.Lock()
|
||||||
|
defer instanceMu.Unlock()
|
||||||
|
|
||||||
|
if instance != nil {
|
||||||
|
return fmt.Errorf("manager already initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr, err := NewManager(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create manager: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = mgr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInstance returns the singleton instance of the database manager.
|
||||||
|
// Returns an error if SetupManager has not been called yet.
|
||||||
|
func GetInstance() (Manager, error) {
|
||||||
|
instanceMu.RLock()
|
||||||
|
defer instanceMu.RUnlock()
|
||||||
|
|
||||||
|
if instance == nil {
|
||||||
|
return nil, fmt.Errorf("manager not initialized: call SetupManager first")
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetInstance resets the singleton instance (primarily for testing purposes).
|
||||||
|
// WARNING: This should only be used in tests. Calling this in production code
|
||||||
|
// while the manager is in use can lead to undefined behavior.
|
||||||
|
func ResetInstance() {
|
||||||
|
instanceMu.Lock()
|
||||||
|
defer instanceMu.Unlock()
|
||||||
|
|
||||||
|
if instance != nil {
|
||||||
|
_ = instance.Close()
|
||||||
|
}
|
||||||
|
instance = nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewManager creates a new database connection manager
|
// NewManager creates a new database connection manager
|
||||||
func NewManager(cfg ManagerConfig) (Manager, error) {
|
func NewManager(cfg ManagerConfig) (Manager, error) {
|
||||||
// Apply defaults and validate configuration
|
// Apply defaults and validate configuration
|
||||||
|
|||||||
Reference in New Issue
Block a user