1
0

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
}