You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
3.7 KiB
Go

package database
import (
"context"
"database/sql"
"fmt"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
// Config holds database configuration
type Config struct {
Driver string
Host string
Port int
User string
Password string
Database string
SSLMode string
}
// Client wraps database connection
type Client struct {
db *sql.DB
config Config
}
// NewClient creates a new database client
func NewClient(config Config) (*Client, error) {
var dsn string
switch config.Driver {
case "sqlite3":
dsn = config.Database
case "postgres":
dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
config.Host, config.Port, config.User, config.Password, config.Database, config.SSLMode)
default:
return nil, fmt.Errorf("unsupported driver: %s", config.Driver)
}
db, err := sql.Open(config.Driver, dsn)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Test connection
if err := db.PingContext(context.Background()); err != nil {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return &Client{
db: db,
config: config,
}, nil
}
// Close closes the database connection
func (c *Client) Close() error {
if c.db != nil {
return c.db.Close()
}
return nil
}
// DB returns the underlying sql.DB instance
func (c *Client) DB() *sql.DB {
return c.db
}
// Health checks database connection health
func (c *Client) Health(ctx context.Context) error {
return c.db.PingContext(ctx)
}
// CreateTables creates the basic table structure
func (c *Client) CreateTables(ctx context.Context) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS questions (
id TEXT PRIMARY KEY,
theme TEXT NOT NULL,
text TEXT NOT NULL,
answer TEXT NOT NULL,
hint TEXT,
difficulty TEXT DEFAULT 'medium' CHECK (difficulty IN ('easy', 'medium', 'hard')),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_questions_theme ON questions(theme)`,
`CREATE INDEX IF NOT EXISTS idx_questions_difficulty ON questions(difficulty)`,
`CREATE INDEX IF NOT EXISTS idx_questions_active ON questions(is_active)`,
`CREATE TABLE IF NOT EXISTS game_sessions (
id TEXT PRIMARY KEY,
player_name TEXT NOT NULL,
user_id TEXT,
total_score INTEGER DEFAULT 0,
questions_asked INTEGER DEFAULT 0,
questions_correct INTEGER DEFAULT 0,
hints_used INTEGER DEFAULT 0,
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
end_time TIMESTAMP,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'completed', 'timeout', 'abandoned')),
current_question_id TEXT,
current_attempts INTEGER DEFAULT 0,
session_data TEXT
)`,
`CREATE INDEX IF NOT EXISTS idx_sessions_user ON game_sessions(user_id)`,
`CREATE INDEX IF NOT EXISTS idx_sessions_status ON game_sessions(status)`,
`CREATE INDEX IF NOT EXISTS idx_sessions_score ON game_sessions(total_score)`,
`CREATE TABLE IF NOT EXISTS question_attempts (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
question_id TEXT NOT NULL,
attempt_number INTEGER NOT NULL,
submitted_answer TEXT NOT NULL,
is_correct BOOLEAN NOT NULL,
used_hint BOOLEAN DEFAULT FALSE,
points_awarded INTEGER DEFAULT 0,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
time_taken_ms INTEGER DEFAULT 0,
metadata TEXT
)`,
`CREATE INDEX IF NOT EXISTS idx_attempts_session ON question_attempts(session_id)`,
`CREATE INDEX IF NOT EXISTS idx_attempts_question ON question_attempts(question_id)`,
}
for _, query := range queries {
if _, err := c.db.ExecContext(ctx, query); err != nil {
return fmt.Errorf("failed to create table: %w", err)
}
}
return nil
}