Files
Horchposten/db/db.go
2026-03-08 14:44:50 +00:00

93 lines
3.2 KiB
Go

package db
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
// New opens a SQLite database at the given path and runs schema migrations.
func New(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite3", path+"?_journal_mode=WAL&_foreign_keys=on")
if err != nil {
return nil, fmt.Errorf("open db: %w", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("ping db: %w", err)
}
if err := migrate(db); err != nil {
return nil, fmt.Errorf("migrate db: %w", err)
}
log.Println("database ready:", path)
return db, nil
}
func migrate(db *sql.DB) error {
schema := `
CREATE TABLE IF NOT EXISTS players (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id TEXT NOT NULL UNIQUE,
first_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
last_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
total_sessions INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_players_client_id ON players(client_id);
CREATE TABLE IF NOT EXISTS player_ips (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER NOT NULL REFERENCES players(id) ON DELETE CASCADE,
ip_address TEXT NOT NULL,
first_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
last_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
UNIQUE(player_id, ip_address)
);
CREATE INDEX IF NOT EXISTS idx_player_ips_ip ON player_ips(ip_address);
CREATE TABLE IF NOT EXISTS game_sessions (
id TEXT PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES players(id) ON DELETE CASCADE,
score INTEGER NOT NULL DEFAULT 0,
level_reached INTEGER NOT NULL DEFAULT 1,
lives_used INTEGER NOT NULL DEFAULT 0,
duration_seconds INTEGER NOT NULL DEFAULT 0,
end_reason TEXT NOT NULL DEFAULT 'death',
started_at TEXT NOT NULL,
ended_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_game_sessions_score ON game_sessions(score DESC);
CREATE INDEX IF NOT EXISTS idx_game_sessions_player_ended ON game_sessions(player_id, ended_at DESC);
CREATE TABLE IF NOT EXISTS device_infos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL UNIQUE REFERENCES game_sessions(id) ON DELETE CASCADE,
ip_address TEXT NOT NULL DEFAULT '',
user_agent TEXT NOT NULL DEFAULT '',
platform TEXT NOT NULL DEFAULT '',
language TEXT NOT NULL DEFAULT '',
screen_width INTEGER,
screen_height INTEGER,
device_pixel_ratio REAL,
timezone TEXT NOT NULL DEFAULT '',
webgl_renderer TEXT NOT NULL DEFAULT '',
touch_support INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS high_scores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER NOT NULL UNIQUE REFERENCES players(id) ON DELETE CASCADE,
score INTEGER NOT NULL,
session_id TEXT REFERENCES game_sessions(id) ON DELETE SET NULL,
achieved_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_high_scores_score ON high_scores(score DESC);
`
_, err := db.Exec(schema)
return err
}