allow mysql alongside sqlite
This commit is contained in:
parent
31dbfa0fc3
commit
41f80008c9
3
go.mod
3
go.mod
@ -7,8 +7,11 @@ require (
|
||||
zombiezen.com/go/sqlite v1.4.2
|
||||
)
|
||||
|
||||
require filippo.io/edwards25519 v1.1.0 // indirect
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -1,5 +1,9 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
@ -5,21 +5,62 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "modernc.org/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
// DatabaseType represents the type of database backend
|
||||
type DatabaseType int
|
||||
|
||||
const (
|
||||
SQLite DatabaseType = iota
|
||||
MySQL
|
||||
)
|
||||
|
||||
// Config holds database configuration
|
||||
type Config struct {
|
||||
Type DatabaseType
|
||||
DSN string // Data Source Name (connection string)
|
||||
PoolSize int // Connection pool size
|
||||
}
|
||||
|
||||
// Database wraps the SQL database connection
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
pool *sqlitex.Pool // For achievements system compatibility
|
||||
dbPath string // Store path for pool creation
|
||||
pool *sqlitex.Pool // For achievements system compatibility (SQLite only)
|
||||
config Config
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new database connection
|
||||
func New(path string) (*Database, error) {
|
||||
db, err := sql.Open("sqlite", path)
|
||||
// New creates a new database connection with the provided configuration
|
||||
func New(config Config) (*Database, error) {
|
||||
var driverName string
|
||||
var pool *sqlitex.Pool
|
||||
|
||||
// Set default pool size
|
||||
if config.PoolSize == 0 {
|
||||
config.PoolSize = 25
|
||||
}
|
||||
|
||||
switch config.Type {
|
||||
case SQLite:
|
||||
driverName = "sqlite"
|
||||
// Create sqlitex pool for achievements system compatibility
|
||||
var err error
|
||||
pool, err = sqlitex.NewPool(config.DSN, sqlitex.PoolOptions{
|
||||
PoolSize: 5,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create sqlite pool: %w", err)
|
||||
}
|
||||
case MySQL:
|
||||
driverName = "mysql"
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported database type: %d", config.Type)
|
||||
}
|
||||
|
||||
db, err := sql.Open(driverName, config.DSN)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
@ -30,26 +71,13 @@ func New(path string) (*Database, error) {
|
||||
}
|
||||
|
||||
// Set connection pool settings
|
||||
db.SetMaxOpenConns(25)
|
||||
db.SetMaxIdleConns(5)
|
||||
|
||||
// Create sqlitex pool for achievements system
|
||||
pool, err := sqlitex.NewPool(path, sqlitex.PoolOptions{
|
||||
PoolSize: 5,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create sqlite pool: %w", err)
|
||||
}
|
||||
db.SetMaxOpenConns(config.PoolSize)
|
||||
db.SetMaxIdleConns(config.PoolSize / 5)
|
||||
|
||||
d := &Database{
|
||||
db: db,
|
||||
pool: pool,
|
||||
dbPath: path,
|
||||
}
|
||||
|
||||
// Initialize schema
|
||||
if err := d.initSchema(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize schema: %w", err)
|
||||
config: config,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
@ -63,257 +91,16 @@ func (d *Database) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
|
||||
// GetType returns the database type
|
||||
func (d *Database) GetType() DatabaseType {
|
||||
return d.config.Type
|
||||
}
|
||||
|
||||
// GetPool returns the sqlitex pool for achievements system compatibility
|
||||
func (d *Database) GetPool() *sqlitex.Pool {
|
||||
return d.pool
|
||||
}
|
||||
|
||||
// initSchema creates the database schema if it doesn't exist
|
||||
func (d *Database) initSchema() error {
|
||||
schemas := []string{
|
||||
// Rules table
|
||||
`CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
category TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
UNIQUE(category, name)
|
||||
)`,
|
||||
|
||||
// Accounts table
|
||||
`CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT,
|
||||
admin_level INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login DATETIME
|
||||
)`,
|
||||
|
||||
// Characters table
|
||||
`CREATE TABLE IF NOT EXISTS characters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER NOT NULL,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
race_id INTEGER NOT NULL,
|
||||
class_id INTEGER NOT NULL,
|
||||
level INTEGER DEFAULT 1,
|
||||
x REAL DEFAULT 0,
|
||||
y REAL DEFAULT 0,
|
||||
z REAL DEFAULT 0,
|
||||
heading REAL DEFAULT 0,
|
||||
zone_id INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_played DATETIME,
|
||||
FOREIGN KEY(account_id) REFERENCES accounts(id)
|
||||
)`,
|
||||
|
||||
// Zones table
|
||||
`CREATE TABLE IF NOT EXISTS zones (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
file TEXT NOT NULL,
|
||||
description TEXT,
|
||||
motd TEXT,
|
||||
min_level INTEGER DEFAULT 0,
|
||||
max_level INTEGER DEFAULT 100,
|
||||
min_version INTEGER DEFAULT 0,
|
||||
xp_modifier REAL DEFAULT 1.0,
|
||||
city_zone INTEGER DEFAULT 0,
|
||||
weather_allowed INTEGER DEFAULT 1,
|
||||
safe_x REAL DEFAULT 0,
|
||||
safe_y REAL DEFAULT 0,
|
||||
safe_z REAL DEFAULT 0,
|
||||
safe_heading REAL DEFAULT 0
|
||||
)`,
|
||||
|
||||
// Server statistics table
|
||||
`CREATE TABLE IF NOT EXISTS server_stats (
|
||||
stat_id INTEGER PRIMARY KEY,
|
||||
stat_value INTEGER,
|
||||
stat_date INTEGER,
|
||||
save_needed INTEGER DEFAULT 0
|
||||
)`,
|
||||
|
||||
// Merchant tables
|
||||
`CREATE TABLE IF NOT EXISTS merchants (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
merchant_type INTEGER DEFAULT 0
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS merchant_items (
|
||||
merchant_id INTEGER NOT NULL,
|
||||
item_id INTEGER NOT NULL,
|
||||
quantity INTEGER DEFAULT -1,
|
||||
price_coins INTEGER DEFAULT 0,
|
||||
price_status INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(merchant_id, item_id),
|
||||
FOREIGN KEY(merchant_id) REFERENCES merchants(id)
|
||||
)`,
|
||||
|
||||
// Achievement tables
|
||||
`CREATE TABLE IF NOT EXISTS achievements (
|
||||
achievement_id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
uncompleted_text TEXT,
|
||||
completed_text TEXT,
|
||||
category TEXT,
|
||||
expansion TEXT,
|
||||
icon INTEGER DEFAULT 0,
|
||||
point_value INTEGER DEFAULT 0,
|
||||
qty_req INTEGER DEFAULT 1,
|
||||
hide_achievement INTEGER DEFAULT 0,
|
||||
unknown3a INTEGER DEFAULT 0,
|
||||
unknown3b INTEGER DEFAULT 0
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS achievements_requirements (
|
||||
achievement_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
qty_req INTEGER DEFAULT 1,
|
||||
PRIMARY KEY(achievement_id, name),
|
||||
FOREIGN KEY(achievement_id) REFERENCES achievements(achievement_id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS achievements_rewards (
|
||||
achievement_id INTEGER NOT NULL,
|
||||
reward TEXT NOT NULL,
|
||||
PRIMARY KEY(achievement_id, reward),
|
||||
FOREIGN KEY(achievement_id) REFERENCES achievements(achievement_id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS character_achievements (
|
||||
char_id INTEGER NOT NULL,
|
||||
achievement_id INTEGER NOT NULL,
|
||||
completed_date INTEGER,
|
||||
PRIMARY KEY(char_id, achievement_id),
|
||||
FOREIGN KEY(char_id) REFERENCES characters(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(achievement_id) REFERENCES achievements(achievement_id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS character_achievements_items (
|
||||
char_id INTEGER NOT NULL,
|
||||
achievement_id INTEGER NOT NULL,
|
||||
items INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(char_id, achievement_id),
|
||||
FOREIGN KEY(char_id) REFERENCES characters(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(achievement_id) REFERENCES achievements(achievement_id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
// Title tables
|
||||
`CREATE TABLE IF NOT EXISTS titles (
|
||||
title_id INTEGER PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
category INTEGER DEFAULT 0,
|
||||
rarity INTEGER DEFAULT 0,
|
||||
position INTEGER DEFAULT 0,
|
||||
description TEXT,
|
||||
is_unique INTEGER DEFAULT 0,
|
||||
is_hidden INTEGER DEFAULT 0,
|
||||
color_code TEXT,
|
||||
requirements TEXT,
|
||||
source_type INTEGER DEFAULT 0,
|
||||
source_id INTEGER DEFAULT 0,
|
||||
created_date INTEGER,
|
||||
expire_date INTEGER
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS character_titles (
|
||||
char_id INTEGER NOT NULL,
|
||||
title_id INTEGER NOT NULL,
|
||||
source_achievement_id INTEGER DEFAULT 0,
|
||||
source_quest_id INTEGER DEFAULT 0,
|
||||
granted_date INTEGER,
|
||||
expire_date INTEGER,
|
||||
PRIMARY KEY(char_id, title_id),
|
||||
FOREIGN KEY(char_id) REFERENCES characters(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(title_id) REFERENCES titles(title_id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS character_active_titles (
|
||||
char_id INTEGER PRIMARY KEY,
|
||||
prefix_title_id INTEGER DEFAULT 0,
|
||||
suffix_title_id INTEGER DEFAULT 0,
|
||||
FOREIGN KEY(char_id) REFERENCES characters(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(prefix_title_id) REFERENCES titles(title_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY(suffix_title_id) REFERENCES titles(title_id) ON DELETE SET NULL
|
||||
)`,
|
||||
|
||||
// NPC tables
|
||||
`CREATE TABLE IF NOT EXISTS npcs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
level INTEGER DEFAULT 1,
|
||||
max_level INTEGER DEFAULT 1,
|
||||
race INTEGER DEFAULT 0,
|
||||
model_type INTEGER DEFAULT 0,
|
||||
size INTEGER DEFAULT 32,
|
||||
hp INTEGER DEFAULT 100,
|
||||
power INTEGER DEFAULT 100,
|
||||
x REAL DEFAULT 0,
|
||||
y REAL DEFAULT 0,
|
||||
z REAL DEFAULT 0,
|
||||
heading REAL DEFAULT 0,
|
||||
respawn_time INTEGER DEFAULT 300,
|
||||
zone_id INTEGER DEFAULT 0,
|
||||
aggro_radius REAL DEFAULT 10,
|
||||
ai_strategy INTEGER DEFAULT 0,
|
||||
loot_table_id INTEGER DEFAULT 0,
|
||||
merchant_type INTEGER DEFAULT 0,
|
||||
randomize_appearance INTEGER DEFAULT 0,
|
||||
show_name INTEGER DEFAULT 1,
|
||||
show_level INTEGER DEFAULT 1,
|
||||
targetable INTEGER DEFAULT 1,
|
||||
show_command_icon INTEGER DEFAULT 1,
|
||||
display_hand_icon INTEGER DEFAULT 0,
|
||||
faction_id INTEGER DEFAULT 0,
|
||||
created_date INTEGER,
|
||||
last_modified INTEGER
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS npc_spells (
|
||||
npc_id INTEGER NOT NULL,
|
||||
spell_id INTEGER NOT NULL,
|
||||
tier INTEGER DEFAULT 1,
|
||||
hp_percentage INTEGER DEFAULT 100,
|
||||
priority INTEGER DEFAULT 1,
|
||||
cast_type INTEGER DEFAULT 0,
|
||||
recast_delay INTEGER DEFAULT 5,
|
||||
PRIMARY KEY(npc_id, spell_id),
|
||||
FOREIGN KEY(npc_id) REFERENCES npcs(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS npc_skills (
|
||||
npc_id INTEGER NOT NULL,
|
||||
skill_name TEXT NOT NULL,
|
||||
skill_value INTEGER DEFAULT 0,
|
||||
max_value INTEGER DEFAULT 0,
|
||||
PRIMARY KEY(npc_id, skill_name),
|
||||
FOREIGN KEY(npc_id) REFERENCES npcs(id) ON DELETE CASCADE
|
||||
)`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS npc_loot (
|
||||
npc_id INTEGER NOT NULL,
|
||||
item_id INTEGER NOT NULL,
|
||||
probability REAL DEFAULT 100.0,
|
||||
min_level INTEGER DEFAULT 0,
|
||||
max_level INTEGER DEFAULT 100,
|
||||
PRIMARY KEY(npc_id, item_id),
|
||||
FOREIGN KEY(npc_id) REFERENCES npcs(id) ON DELETE CASCADE
|
||||
)`,
|
||||
}
|
||||
|
||||
for _, schema := range schemas {
|
||||
if _, err := d.db.Exec(schema); err != nil {
|
||||
return fmt.Errorf("failed to create schema: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Query executes a query that returns rows
|
||||
func (d *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
@ -369,6 +156,23 @@ func (d *Database) SaveRule(category, name, value, description string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// NewSQLite creates a new SQLite database connection
|
||||
func NewSQLite(path string) (*Database, error) {
|
||||
return New(Config{
|
||||
Type: SQLite,
|
||||
DSN: path,
|
||||
})
|
||||
}
|
||||
|
||||
// NewMySQL creates a new MySQL/MariaDB database connection
|
||||
// DSN format: user:password@tcp(host:port)/database
|
||||
func NewMySQL(dsn string) (*Database, error) {
|
||||
return New(Config{
|
||||
Type: MySQL,
|
||||
DSN: dsn,
|
||||
})
|
||||
}
|
||||
|
||||
// GetZones retrieves all zones from the database
|
||||
func (d *Database) GetZones() ([]map[string]interface{}, error) {
|
||||
rows, err := d.Query(`
|
||||
|
108
internal/database/database_test.go
Normal file
108
internal/database/database_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSQLite(t *testing.T) {
|
||||
// Test SQLite connection
|
||||
db, err := NewSQLite("file::memory:?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SQLite database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Test database type
|
||||
if db.GetType() != SQLite {
|
||||
t.Errorf("Expected SQLite database type, got %v", db.GetType())
|
||||
}
|
||||
|
||||
// Test basic query
|
||||
result, err := db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test table: %v", err)
|
||||
}
|
||||
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get rows affected: %v", err)
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
t.Errorf("Expected 0 rows affected for CREATE TABLE, got %d", affected)
|
||||
}
|
||||
|
||||
// Test insert
|
||||
_, err = db.Exec("INSERT INTO test (name) VALUES (?)", "test_value")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert test data: %v", err)
|
||||
}
|
||||
|
||||
// Test query
|
||||
var name string
|
||||
err = db.QueryRow("SELECT name FROM test WHERE id = 1").Scan(&name)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query test data: %v", err)
|
||||
}
|
||||
|
||||
if name != "test_value" {
|
||||
t.Errorf("Expected 'test_value', got '%s'", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config Config
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid_sqlite_config",
|
||||
config: Config{
|
||||
Type: SQLite,
|
||||
DSN: "file::memory:?mode=memory&cache=shared",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid_database_type",
|
||||
config: Config{
|
||||
Type: DatabaseType(99),
|
||||
DSN: "test",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db, err := New(tt.config)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if db != nil {
|
||||
db.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseTypeMethods(t *testing.T) {
|
||||
// Test SQLite
|
||||
db, err := NewSQLite("file::memory:?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SQLite database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if db.GetType() != SQLite {
|
||||
t.Errorf("Expected SQLite type, got %v", db.GetType())
|
||||
}
|
||||
|
||||
// Verify GetPool works for SQLite
|
||||
pool := db.GetPool()
|
||||
if pool == nil {
|
||||
t.Error("Expected non-nil pool for SQLite database")
|
||||
}
|
||||
}
|
@ -108,13 +108,39 @@ JSON-based configuration with CLI overrides:
|
||||
"listen_port": 9000,
|
||||
"max_clients": 1000,
|
||||
"web_port": 8080,
|
||||
"database_path": "world.db",
|
||||
"database_type": "sqlite",
|
||||
"database_path": "eq2.db",
|
||||
"database_host": "localhost",
|
||||
"database_port": 3306,
|
||||
"database_name": "eq2emu",
|
||||
"database_user": "eq2",
|
||||
"database_pass": "password",
|
||||
"server_name": "EQ2Go World Server",
|
||||
"xp_rate": 1.0,
|
||||
"ts_xp_rate": 1.0
|
||||
}
|
||||
```
|
||||
|
||||
For MySQL/MariaDB configuration:
|
||||
```json
|
||||
{
|
||||
"database_type": "mysql",
|
||||
"database_host": "localhost",
|
||||
"database_port": 3306,
|
||||
"database_name": "eq2emu",
|
||||
"database_user": "eq2",
|
||||
"database_pass": "password"
|
||||
}
|
||||
```
|
||||
|
||||
For SQLite configuration (default):
|
||||
```json
|
||||
{
|
||||
"database_type": "sqlite",
|
||||
"database_path": "eq2.db"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Startup
|
||||
|
@ -3,6 +3,7 @@ package world
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -78,7 +79,13 @@ type WorldConfig struct {
|
||||
WebKeyPassword string `json:"web_key_password"`
|
||||
|
||||
// Database settings
|
||||
DatabasePath string `json:"database_path"`
|
||||
DatabaseType string `json:"database_type"` // "sqlite" or "mysql"
|
||||
DatabasePath string `json:"database_path"` // For SQLite: file path
|
||||
DatabaseHost string `json:"database_host"` // For MySQL: hostname
|
||||
DatabasePort int `json:"database_port"` // For MySQL: port
|
||||
DatabaseName string `json:"database_name"` // For MySQL: database name
|
||||
DatabaseUser string `json:"database_user"` // For MySQL: username
|
||||
DatabasePass string `json:"database_pass"` // For MySQL: password
|
||||
|
||||
// Server settings
|
||||
ServerName string `json:"server_name"`
|
||||
@ -138,7 +145,25 @@ type ServerStatistics struct {
|
||||
// NewWorld creates a new world server instance
|
||||
func NewWorld(config *WorldConfig) (*World, error) {
|
||||
// Initialize database
|
||||
db, err := database.New(config.DatabasePath)
|
||||
var db *database.Database
|
||||
var err error
|
||||
|
||||
switch strings.ToLower(config.DatabaseType) {
|
||||
case "mysql", "mariadb":
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
||||
config.DatabaseUser, config.DatabasePass, config.DatabaseHost, config.DatabasePort, config.DatabaseName)
|
||||
db, err = database.NewMySQL(dsn)
|
||||
case "sqlite", "":
|
||||
// Default to SQLite if not specified
|
||||
dbPath := config.DatabasePath
|
||||
if dbPath == "" {
|
||||
dbPath = "eq2.db"
|
||||
}
|
||||
db, err = database.NewSQLite(dbPath)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported database type: %s", config.DatabaseType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize database: %w", err)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user