mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 23:54:33 +01:00
239 lines
5.6 KiB
Go
239 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
// DBConfig holds database connection configuration.
|
|
type DBConfig struct {
|
|
Host string
|
|
Port int
|
|
User string
|
|
Password string
|
|
DBName string
|
|
ConfigPath string
|
|
}
|
|
|
|
// ErupeConfig represents the relevant parts of config.json.
|
|
type ErupeConfig struct {
|
|
Database struct {
|
|
Host string `json:"Host"`
|
|
Port int `json:"Port"`
|
|
User string `json:"User"`
|
|
Password string `json:"Password"`
|
|
Database string `json:"Database"`
|
|
} `json:"Database"`
|
|
}
|
|
|
|
// loadConfigFile loads database settings from config.json.
|
|
func loadConfigFile(path string) (*ErupeConfig, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var cfg ErupeConfig
|
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// findConfigFile searches for config.json in common locations.
|
|
func findConfigFile() string {
|
|
// Check paths relative to current directory and up to project root
|
|
paths := []string{
|
|
"config.json",
|
|
"../../config.json", // From tools/usercheck/
|
|
"../../../config.json",
|
|
}
|
|
|
|
// Also check if we can find it via executable path
|
|
if exe, err := os.Executable(); err == nil {
|
|
dir := filepath.Dir(exe)
|
|
paths = append(paths,
|
|
filepath.Join(dir, "config.json"),
|
|
filepath.Join(dir, "../../config.json"),
|
|
)
|
|
}
|
|
|
|
for _, p := range paths {
|
|
if _, err := os.Stat(p); err == nil {
|
|
return p
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// addDBFlags adds common database flags to a FlagSet.
|
|
func addDBFlags(fs *flag.FlagSet, cfg *DBConfig) {
|
|
fs.StringVar(&cfg.ConfigPath, "config", "", "Path to config.json (auto-detected if not specified)")
|
|
fs.StringVar(&cfg.Host, "host", "", "Database host (overrides config.json)")
|
|
fs.IntVar(&cfg.Port, "port", 0, "Database port (overrides config.json)")
|
|
fs.StringVar(&cfg.User, "user", "", "Database user (overrides config.json)")
|
|
fs.StringVar(&cfg.Password, "password", "", "Database password (overrides config.json)")
|
|
fs.StringVar(&cfg.DBName, "dbname", "", "Database name (overrides config.json)")
|
|
}
|
|
|
|
// resolveDBConfig resolves the final database configuration.
|
|
// Priority: CLI flags > environment variables > config.json > defaults
|
|
func resolveDBConfig(cfg *DBConfig) error {
|
|
// Try to load from config.json
|
|
configPath := cfg.ConfigPath
|
|
if configPath == "" {
|
|
configPath = findConfigFile()
|
|
}
|
|
|
|
var fileCfg *ErupeConfig
|
|
if configPath != "" {
|
|
var err error
|
|
fileCfg, err = loadConfigFile(configPath)
|
|
if err != nil {
|
|
// Only error if user explicitly specified a config path
|
|
if cfg.ConfigPath != "" {
|
|
return fmt.Errorf("failed to load config file: %w", err)
|
|
}
|
|
// Otherwise just ignore and use defaults/flags
|
|
}
|
|
}
|
|
|
|
// Apply config.json values as base
|
|
if fileCfg != nil {
|
|
if cfg.Host == "" {
|
|
cfg.Host = fileCfg.Database.Host
|
|
}
|
|
if cfg.Port == 0 {
|
|
cfg.Port = fileCfg.Database.Port
|
|
}
|
|
if cfg.User == "" {
|
|
cfg.User = fileCfg.Database.User
|
|
}
|
|
if cfg.Password == "" {
|
|
cfg.Password = fileCfg.Database.Password
|
|
}
|
|
if cfg.DBName == "" {
|
|
cfg.DBName = fileCfg.Database.Database
|
|
}
|
|
}
|
|
|
|
// Apply environment variables
|
|
if cfg.Host == "" {
|
|
cfg.Host = os.Getenv("ERUPE_DB_HOST")
|
|
}
|
|
if cfg.User == "" {
|
|
cfg.User = os.Getenv("ERUPE_DB_USER")
|
|
}
|
|
if cfg.Password == "" {
|
|
cfg.Password = os.Getenv("ERUPE_DB_PASSWORD")
|
|
}
|
|
if cfg.DBName == "" {
|
|
cfg.DBName = os.Getenv("ERUPE_DB_NAME")
|
|
}
|
|
|
|
// Apply defaults
|
|
if cfg.Host == "" {
|
|
cfg.Host = "localhost"
|
|
}
|
|
if cfg.Port == 0 {
|
|
cfg.Port = 5432
|
|
}
|
|
if cfg.User == "" {
|
|
cfg.User = "postgres"
|
|
}
|
|
if cfg.DBName == "" {
|
|
cfg.DBName = "erupe"
|
|
}
|
|
|
|
// Password is required
|
|
if cfg.Password == "" {
|
|
return fmt.Errorf("database password is required (set in config.json, use -password flag, or ERUPE_DB_PASSWORD env var)")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// connectDB establishes a connection to the PostgreSQL database.
|
|
func connectDB(cfg *DBConfig) (*sql.DB, error) {
|
|
if err := resolveDBConfig(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Use single quotes around values to handle special characters in passwords
|
|
connStr := fmt.Sprintf(
|
|
"host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable",
|
|
cfg.Host, cfg.Port, cfg.User, escapeConnStringValue(cfg.Password), cfg.DBName,
|
|
)
|
|
|
|
db, err := sql.Open("postgres", connStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
|
|
if err := db.Ping(); err != nil {
|
|
_ = db.Close()
|
|
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
// escapeConnStringValue escapes single quotes in connection string values.
|
|
func escapeConnStringValue(s string) string {
|
|
// In PostgreSQL connection strings, single quotes inside quoted values
|
|
// must be escaped by doubling them
|
|
result := ""
|
|
for _, c := range s {
|
|
switch c {
|
|
case '\'':
|
|
result += "''"
|
|
case '\\':
|
|
result += "\\\\"
|
|
default:
|
|
result += string(c)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ConnectedUser represents a user currently connected to the server.
|
|
type ConnectedUser struct {
|
|
CharID uint32
|
|
CharName string
|
|
ServerID uint16
|
|
ServerName string
|
|
UserID int
|
|
Username string
|
|
LastLogin sql.NullTime
|
|
HR int
|
|
GR int
|
|
}
|
|
|
|
// ServerStatus represents the status of a channel server.
|
|
type ServerStatus struct {
|
|
ServerID uint16
|
|
WorldName string
|
|
WorldDesc string
|
|
Land int
|
|
CurrentPlayers int
|
|
Season int
|
|
}
|
|
|
|
// LoginHistory represents a player's login history entry.
|
|
type LoginHistory struct {
|
|
CharID uint32
|
|
CharName string
|
|
LastLogin sql.NullTime
|
|
HR int
|
|
GR int
|
|
Username string
|
|
}
|