184 lines
4.9 KiB
Go
184 lines
4.9 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
type Database struct {
|
|
conn *sql.DB
|
|
}
|
|
|
|
func New(dsn string) (*Database, error) {
|
|
conn, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
|
|
conn.SetMaxOpenConns(25)
|
|
conn.SetMaxIdleConns(5)
|
|
conn.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
if err := conn.Ping(); err != nil {
|
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
}
|
|
|
|
db := &Database{conn: conn}
|
|
if err := db.createTables(); err != nil {
|
|
return nil, fmt.Errorf("failed to create tables: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (db *Database) createTables() error {
|
|
queries := []string{
|
|
`CREATE TABLE IF NOT EXISTS players (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
username VARCHAR(255) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_login TIMESTAMP NULL,
|
|
INDEX idx_username (username)
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS player_positions (
|
|
player_id INT PRIMARY KEY,
|
|
x FLOAT NOT NULL DEFAULT 0,
|
|
y FLOAT NOT NULL DEFAULT 0,
|
|
z FLOAT NOT NULL DEFAULT 0,
|
|
yaw FLOAT NOT NULL DEFAULT 0,
|
|
pitch FLOAT NOT NULL DEFAULT 0,
|
|
world VARCHAR(255) DEFAULT 'main',
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS player_sessions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
player_id INT NOT NULL,
|
|
token VARCHAR(255) UNIQUE NOT NULL,
|
|
server_type VARCHAR(50) NOT NULL,
|
|
server_id VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
INDEX idx_token (token),
|
|
INDEX idx_player (player_id),
|
|
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
|
|
)`,
|
|
}
|
|
|
|
for _, query := range queries {
|
|
if _, err := db.conn.Exec(query); err != nil {
|
|
return fmt.Errorf("failed to execute query: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db *Database) Close() error {
|
|
return db.conn.Close()
|
|
}
|
|
|
|
func (db *Database) CreatePlayer(username, passwordHash string) (int64, error) {
|
|
result, err := db.conn.Exec(
|
|
"INSERT INTO players (username, password_hash) VALUES (?, ?)",
|
|
username, passwordHash,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.LastInsertId()
|
|
}
|
|
|
|
func (db *Database) GetPlayerByUsername(username string) (*Player, error) {
|
|
p := &Player{}
|
|
err := db.conn.QueryRow(
|
|
"SELECT id, username, password_hash FROM players WHERE username = ?",
|
|
username,
|
|
).Scan(&p.ID, &p.Username, &p.PasswordHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (db *Database) GetPlayerByID(playerID int64) (*Player, error) {
|
|
p := &Player{}
|
|
err := db.conn.QueryRow(
|
|
"SELECT id, username, password_hash FROM players WHERE id = ?",
|
|
playerID,
|
|
).Scan(&p.ID, &p.Username, &p.PasswordHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (db *Database) UpdateLastLogin(playerID int64) error {
|
|
_, err := db.conn.Exec(
|
|
"UPDATE players SET last_login = CURRENT_TIMESTAMP WHERE id = ?",
|
|
playerID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (db *Database) SavePlayerPosition(playerID int64, pos *Position) error {
|
|
_, err := db.conn.Exec(`
|
|
INSERT INTO player_positions (player_id, x, y, z, yaw, pitch, world)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
x = VALUES(x), y = VALUES(y), z = VALUES(z),
|
|
yaw = VALUES(yaw), pitch = VALUES(pitch), world = VALUES(world)
|
|
`, playerID, pos.X, pos.Y, pos.Z, pos.Yaw, pos.Pitch, pos.World)
|
|
return err
|
|
}
|
|
|
|
func (db *Database) GetPlayerPosition(playerID int64) (*Position, error) {
|
|
pos := &Position{}
|
|
err := db.conn.QueryRow(
|
|
"SELECT x, y, z, yaw, pitch, world FROM player_positions WHERE player_id = ?",
|
|
playerID,
|
|
).Scan(&pos.X, &pos.Y, &pos.Z, &pos.Yaw, &pos.Pitch, &pos.World)
|
|
if err == sql.ErrNoRows {
|
|
return &Position{X: 0, Y: 0, Z: 0, Yaw: 0, Pitch: 0, World: "main"}, nil
|
|
}
|
|
return pos, err
|
|
}
|
|
|
|
func (db *Database) CreateSession(playerID int64, token, serverType, serverID string, duration time.Duration) error {
|
|
expiresAt := time.Now().Add(duration)
|
|
_, err := db.conn.Exec(
|
|
`INSERT INTO player_sessions (player_id, token, server_type, server_id, expires_at)
|
|
VALUES (?, ?, ?, ?, ?)`,
|
|
playerID, token, serverType, serverID, expiresAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (db *Database) ValidateSession(token string) (*Session, error) {
|
|
s := &Session{}
|
|
err := db.conn.QueryRow(
|
|
`SELECT id, player_id, token, server_type, server_id, expires_at
|
|
FROM player_sessions
|
|
WHERE token = ? AND expires_at > NOW()`,
|
|
token,
|
|
).Scan(&s.ID, &s.PlayerID, &s.Token, &s.ServerType, &s.ServerID, &s.ExpiresAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (db *Database) DeleteSession(token string) error {
|
|
_, err := db.conn.Exec("DELETE FROM player_sessions WHERE token = ?", token)
|
|
return err
|
|
}
|
|
|
|
func (db *Database) CleanExpiredSessions() error {
|
|
_, err := db.conn.Exec("DELETE FROM player_sessions WHERE expires_at < NOW()")
|
|
return err
|
|
}
|