@@ -0,0 +1,189 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MySQLInput struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Database string `json:"database"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Charset string `json:"charset"`
|
||||
ParseTime bool `json:"parseTime"`
|
||||
TLS string `json:"tls"`
|
||||
}
|
||||
|
||||
type SafeDatabaseConfig struct {
|
||||
Provider string `json:"provider"`
|
||||
SQLitePath string `json:"sqlitePath"`
|
||||
MySQLDSN string `json:"mysqlDsn"`
|
||||
MySQLHost string `json:"mysqlHost"`
|
||||
MySQLPort int `json:"mysqlPort"`
|
||||
MySQLDatabase string `json:"mysqlDatabase"`
|
||||
MySQLUser string `json:"mysqlUser"`
|
||||
HasPassword bool `json:"hasPassword"`
|
||||
}
|
||||
|
||||
func BuildMySQLDSN(input MySQLInput) (string, error) {
|
||||
host := strings.TrimSpace(input.Host)
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := input.Port
|
||||
if port <= 0 {
|
||||
port = 3306
|
||||
}
|
||||
database := strings.TrimSpace(input.Database)
|
||||
username := strings.TrimSpace(input.Username)
|
||||
if database == "" {
|
||||
return "", errors.New("mysql database is required")
|
||||
}
|
||||
if username == "" {
|
||||
return "", errors.New("mysql username is required")
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("charset", firstNonEmpty(strings.TrimSpace(input.Charset), "utf8mb4"))
|
||||
params.Set("parseTime", strconv.FormatBool(input.ParseTime))
|
||||
if tls := strings.TrimSpace(input.TLS); tls != "" {
|
||||
params.Set("tls", tls)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", username, input.Password, host, port, database, params.Encode()), nil
|
||||
}
|
||||
|
||||
func NormalizeDatabase(baseDir string, current DatabaseConfig, incoming DatabaseConfig, keepPassword bool) (DatabaseConfig, error) {
|
||||
next := current
|
||||
structuredChanged := false
|
||||
if incoming.Provider != "" {
|
||||
next.Provider = strings.ToLower(strings.TrimSpace(incoming.Provider))
|
||||
}
|
||||
if next.Provider == "" {
|
||||
next.Provider = "sqlite"
|
||||
}
|
||||
if incoming.SQLitePath != "" {
|
||||
next.SQLitePath = incoming.SQLitePath
|
||||
}
|
||||
if next.SQLitePath != "" && !filepath.IsAbs(next.SQLitePath) && !strings.HasPrefix(strings.ToLower(next.SQLitePath), "file:") {
|
||||
next.SQLitePath = filepath.Join(baseDir, next.SQLitePath)
|
||||
}
|
||||
if incoming.MySQLHost != "" {
|
||||
next.MySQLHost = strings.TrimSpace(incoming.MySQLHost)
|
||||
structuredChanged = true
|
||||
}
|
||||
if incoming.MySQLPort > 0 {
|
||||
next.MySQLPort = incoming.MySQLPort
|
||||
structuredChanged = true
|
||||
}
|
||||
if incoming.MySQLDatabase != "" {
|
||||
next.MySQLDatabase = strings.TrimSpace(incoming.MySQLDatabase)
|
||||
structuredChanged = true
|
||||
}
|
||||
if incoming.MySQLUser != "" {
|
||||
next.MySQLUser = strings.TrimSpace(incoming.MySQLUser)
|
||||
structuredChanged = true
|
||||
}
|
||||
if incoming.MySQLPassword != "" || !keepPassword {
|
||||
next.MySQLPassword = incoming.MySQLPassword
|
||||
structuredChanged = true
|
||||
}
|
||||
if incoming.MySQLDSN != "" {
|
||||
next.MySQLDSN = strings.TrimSpace(incoming.MySQLDSN)
|
||||
}
|
||||
if next.MySQLHost == "" {
|
||||
next.MySQLHost = "127.0.0.1"
|
||||
}
|
||||
if next.MySQLPort <= 0 {
|
||||
next.MySQLPort = 3306
|
||||
}
|
||||
if next.Provider == "sqlite" {
|
||||
next.MySQLDSN = ""
|
||||
} else if next.Provider == "mysql" {
|
||||
if structuredChanged || next.MySQLDSN == "" {
|
||||
dsn, err := BuildMySQLDSN(MySQLInput{
|
||||
Host: next.MySQLHost,
|
||||
Port: next.MySQLPort,
|
||||
Database: next.MySQLDatabase,
|
||||
Username: next.MySQLUser,
|
||||
Password: next.MySQLPassword,
|
||||
Charset: "utf8mb4",
|
||||
ParseTime: true,
|
||||
})
|
||||
if err != nil {
|
||||
return DatabaseConfig{}, err
|
||||
}
|
||||
next.MySQLDSN = dsn
|
||||
}
|
||||
if strings.TrimSpace(next.MySQLDSN) == "" {
|
||||
return DatabaseConfig{}, errors.New("mysql connection is required")
|
||||
}
|
||||
} else {
|
||||
return DatabaseConfig{}, errors.New("provider must be sqlite or mysql")
|
||||
}
|
||||
if strings.TrimSpace(next.SQLitePath) == "" {
|
||||
return DatabaseConfig{}, errors.New("sqlite path is required")
|
||||
}
|
||||
if next.MaxOpenConns <= 0 {
|
||||
next.MaxOpenConns = 10
|
||||
}
|
||||
if next.MaxIdleConns <= 0 {
|
||||
next.MaxIdleConns = 4
|
||||
}
|
||||
if next.ConnMaxLifetimeSeconds <= 0 {
|
||||
next.ConnMaxLifetimeSeconds = 300
|
||||
}
|
||||
if next.HealthIntervalSec <= 0 {
|
||||
next.HealthIntervalSec = 30
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func SafeDatabase(baseDir string, cfg DatabaseConfig) SafeDatabaseConfig {
|
||||
return SafeDatabaseConfig{
|
||||
Provider: firstNonEmpty(cfg.Provider, "sqlite"),
|
||||
SQLitePath: relativeToBase(baseDir, cfg.SQLitePath),
|
||||
MySQLDSN: MaskDSN(cfg.MySQLDSN),
|
||||
MySQLHost: cfg.MySQLHost,
|
||||
MySQLPort: cfg.MySQLPort,
|
||||
MySQLDatabase: cfg.MySQLDatabase,
|
||||
MySQLUser: cfg.MySQLUser,
|
||||
HasPassword: strings.TrimSpace(cfg.MySQLPassword) != "" || dsnHasPassword(cfg.MySQLDSN),
|
||||
}
|
||||
}
|
||||
|
||||
func MaskDSN(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
at := strings.Index(value, "@")
|
||||
colon := strings.Index(value, ":")
|
||||
if at > -1 && colon > -1 && colon < at {
|
||||
return value[:colon+1] + "******" + value[at:]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func relativeToBase(base, value string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return ""
|
||||
}
|
||||
if base != "" {
|
||||
if rel, err := filepath.Rel(base, value); err == nil && !strings.HasPrefix(rel, "..") && rel != "." {
|
||||
return filepath.ToSlash(rel)
|
||||
}
|
||||
}
|
||||
return filepath.ToSlash(value)
|
||||
}
|
||||
|
||||
func dsnHasPassword(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
at := strings.Index(value, "@")
|
||||
colon := strings.Index(value, ":")
|
||||
return at > -1 && colon > -1 && colon < at && colon+1 < at
|
||||
}
|
||||
Reference in New Issue
Block a user