238 lines
7.1 KiB
Go
238 lines
7.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config represents the application configuration
|
|
type Config struct {
|
|
App AppConfig `mapstructure:"app"`
|
|
Database DatabaseConfig `mapstructure:"database"`
|
|
Tables map[string]TableConfig `mapstructure:"tables"`
|
|
}
|
|
|
|
// AppConfig holds application-level configuration
|
|
type AppConfig struct {
|
|
Name string `mapstructure:"name"`
|
|
Theme string `mapstructure:"theme"`
|
|
Language string `mapstructure:"language"`
|
|
Port int `mapstructure:"port"`
|
|
}
|
|
|
|
// DatabaseConfig holds database connection settings
|
|
type DatabaseConfig struct {
|
|
Type string `mapstructure:"type"`
|
|
Host string `mapstructure:"host"`
|
|
Port int `mapstructure:"port"`
|
|
User string `mapstructure:"user"`
|
|
Password string `mapstructure:"password"`
|
|
DBName string `mapstructure:"dbname"`
|
|
Path string `mapstructure:"path"`
|
|
DSN string `mapstructure:"dsn"`
|
|
}
|
|
|
|
// TableConfig holds table-specific configuration
|
|
type TableConfig struct {
|
|
Alias string `mapstructure:"alias"`
|
|
Layout string `mapstructure:"layout"`
|
|
PageSize int `mapstructure:"page_size"`
|
|
Fields map[string]FieldConfig `mapstructure:"fields"`
|
|
Features map[string]interface{} `mapstructure:"features"`
|
|
Options map[string]interface{} `mapstructure:"options"`
|
|
}
|
|
|
|
// FieldConfig holds field-specific configuration
|
|
type FieldConfig struct {
|
|
Type string `mapstructure:"type"`
|
|
Alias string `mapstructure:"alias"`
|
|
Hidden bool `mapstructure:"hidden"`
|
|
Searchable bool `mapstructure:"searchable"`
|
|
MaxLength int `mapstructure:"max_length"`
|
|
Length int `mapstructure:"length"`
|
|
Markdown bool `mapstructure:"markdown"`
|
|
Excerpt int `mapstructure:"excerpt"`
|
|
Size string `mapstructure:"size"`
|
|
Fit string `mapstructure:"fit"`
|
|
Format string `mapstructure:"format"`
|
|
Relative bool `mapstructure:"relative"`
|
|
Colors map[string]string `mapstructure:"colors"`
|
|
Separator string `mapstructure:"separator"`
|
|
Primary bool `mapstructure:"primary"`
|
|
AvatarField string `mapstructure:"avatar_field"`
|
|
Suffix string `mapstructure:"suffix"`
|
|
Prefix string `mapstructure:"prefix"`
|
|
Options map[string]interface{} `mapstructure:"options"`
|
|
}
|
|
|
|
// LoadConfig loads configuration from file and environment variables
|
|
func LoadConfig(configPath string) (*Config, error) {
|
|
logger := slog.With("component", "config")
|
|
|
|
// Set default values
|
|
viper.SetDefault("app.name", "Database Render")
|
|
viper.SetDefault("app.theme", "modern")
|
|
viper.SetDefault("app.language", "zh-CN")
|
|
viper.SetDefault("app.port", 8080)
|
|
|
|
// Database defaults
|
|
viper.SetDefault("database.type", "sqlite")
|
|
viper.SetDefault("database.host", "localhost")
|
|
viper.SetDefault("database.port", 3306)
|
|
viper.SetDefault("database.dbname", "testdb")
|
|
|
|
// Set config file
|
|
if configPath != "" {
|
|
viper.SetConfigFile(configPath)
|
|
} else {
|
|
viper.SetConfigName("config")
|
|
viper.SetConfigType("yaml")
|
|
viper.AddConfigPath(".")
|
|
viper.AddConfigPath("./config")
|
|
viper.AddConfigPath("/etc/database-render")
|
|
}
|
|
|
|
// Environment variables
|
|
viper.SetEnvPrefix("DR")
|
|
viper.AutomaticEnv()
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
|
|
// Read config
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
logger.Error("failed to read config file", "error", err)
|
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
logger.Warn("config file not found, using defaults")
|
|
}
|
|
|
|
// Parse database DSN if provided
|
|
if dsn := viper.GetString("database"); dsn != "" {
|
|
if err := parseDSN(dsn); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Unmarshal config
|
|
var config Config
|
|
if err := viper.Unmarshal(&config); err != nil {
|
|
logger.Error("failed to unmarshal config", "error", err)
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
|
|
// Validate config
|
|
if err := validateConfig(&config); err != nil {
|
|
logger.Error("config validation failed", "error", err)
|
|
return nil, fmt.Errorf("config validation failed: %w", err)
|
|
}
|
|
|
|
logger.Info("configuration loaded successfully",
|
|
"config_file", viper.ConfigFileUsed(),
|
|
"tables_count", len(config.Tables))
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// parseDSN parses database connection string
|
|
func parseDSN(dsn string) error {
|
|
if strings.HasPrefix(dsn, "sqlite://") {
|
|
path := strings.TrimPrefix(dsn, "sqlite://")
|
|
viper.Set("database.type", "sqlite")
|
|
viper.Set("database.path", path)
|
|
return nil
|
|
}
|
|
|
|
if strings.HasPrefix(dsn, "mysql://") {
|
|
dsn = strings.TrimPrefix(dsn, "mysql://")
|
|
parts := strings.Split(dsn, "@")
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("invalid mysql dsn format")
|
|
}
|
|
|
|
userPass := strings.Split(parts[0], ":")
|
|
if len(userPass) != 2 {
|
|
return fmt.Errorf("invalid mysql user:pass format")
|
|
}
|
|
|
|
hostPort := strings.Split(parts[1], "/")
|
|
if len(hostPort) != 2 {
|
|
return fmt.Errorf("invalid mysql host/db format")
|
|
}
|
|
|
|
host := strings.Split(hostPort[0], ":")
|
|
if len(host) == 2 {
|
|
viper.Set("database.port", host[1])
|
|
}
|
|
|
|
viper.Set("database.type", "mysql")
|
|
viper.Set("database.user", userPass[0])
|
|
viper.Set("database.password", userPass[1])
|
|
viper.Set("database.host", host[0])
|
|
viper.Set("database.dbname", hostPort[1])
|
|
return nil
|
|
}
|
|
|
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
|
viper.Set("database.type", "postgres")
|
|
viper.Set("database.dsn", dsn)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("unsupported database dsn format")
|
|
}
|
|
|
|
// validateConfig validates the loaded configuration
|
|
func validateConfig(config *Config) error {
|
|
if len(config.Tables) == 0 {
|
|
return fmt.Errorf("no tables configured")
|
|
}
|
|
|
|
for name, table := range config.Tables {
|
|
if name == "" {
|
|
return fmt.Errorf("table name cannot be empty")
|
|
}
|
|
if table.Alias == "" {
|
|
table.Alias = name
|
|
}
|
|
if table.Layout == "" {
|
|
table.Layout = "card"
|
|
}
|
|
if table.PageSize == 0 {
|
|
table.PageSize = 12
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTableConfig returns configuration for a specific table
|
|
func (c *Config) GetTableConfig(tableName string) (*TableConfig, bool) {
|
|
config, exists := c.Tables[tableName]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
return &config, true
|
|
}
|
|
|
|
// GetDatabaseDSN returns the database connection string
|
|
func (c *Config) GetDatabaseDSN() string {
|
|
switch c.Database.Type {
|
|
case "sqlite":
|
|
return c.Database.Path
|
|
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)
|
|
case "postgres":
|
|
if c.Database.DSN != "" {
|
|
return c.Database.DSN
|
|
}
|
|
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
|
c.Database.Host, c.Database.Port, c.Database.User, c.Database.Password, c.Database.DBName)
|
|
default:
|
|
return c.Database.DSN
|
|
}
|
|
}
|