initial
This commit is contained in:
92
db/db.go
Normal file
92
db/db.go
Normal file
@@ -0,0 +1,92 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user