feat: init project
This commit is contained in:
236
internal/config/config.go
Normal file
236
internal/config/config.go
Normal file
@@ -0,0 +1,236 @@
|
||||
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"`
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user