package player import ( "context" "fmt" "log/slog" "time" "github.com/google/uuid" "gitlab.com/zephyrtronium/sq" ) // Session is a session ID. type Session = uuid.UUID // InitSessions initializes an SQLite table relating player IDs to sessions. func InitSessions(ctx context.Context, db Execer) error { _, err := db.Exec(ctx, initSessions) if err != nil { return fmt.Errorf("couldn't init sessions table: %w", err) } return nil } // StartSession creates a new 24-hour session for a player as of now. func StartSession(ctx context.Context, db Execer, p ID, now time.Time) (Session, error) { id := uuid.New() ttl := now.Add(24 * time.Hour).Unix() _, err := db.Exec(ctx, `INSERT OR REPLACE INTO shotgun_session(player, id, ttl) VALUES (?, ?, ?)`, p, id, ttl) if err != nil { slog.ErrorContext(ctx, "couldn't start session", "player", p, "error", err.Error()) return Session{}, fmt.Errorf("couldn't start session: %w", err) } slog.InfoContext(ctx, "session started", "player", p, "session", id, "now", now) return Session(id), nil } // FromSession gets the player ID associated with a session and updates the // session to last 24 hours past now. func FromSession(ctx context.Context, db RowQuerier, id Session, now time.Time) (ID, error) { ttl := now.Add(24 * time.Hour).Unix() var p ID err := db.QueryRow(ctx, `UPDATE shotgun_session SET ttl = ? WHERE id = ? AND ttl >= ? RETURNING player;`, ttl, id, now.Unix()).Scan(&p) switch err { case nil: // do nothing case sq.ErrNoRows: slog.WarnContext(ctx, "looked for missing session", "session", id, "now", now) return ID{}, fmt.Errorf("no such session") default: slog.ErrorContext(ctx, "couldn't get player from session", "session", id, "now", now, "error", err.Error()) return ID{}, fmt.Errorf("couldn't get player from session: %w", err) } slog.InfoContext(ctx, "got player from session", "session", id, "player", p) return p, nil } // Logout deletes a session. func Logout(ctx context.Context, db Execer, id Session) error { _, err := db.Exec(ctx, `DELETE FROM shotgun_session WHERE id = ?`, id) if err != nil { slog.ErrorContext(ctx, "couldn't logout", "session", id, "error", err.Error()) return fmt.Errorf("couldn't logout: %w", err) } slog.InfoContext(ctx, "logged out", "session", id) return nil } const initSessions = `CREATE TABLE shotgun_session ( id TEXT PRIMARY KEY, player TEXT NOT NULL UNIQUE, ttl INTEGER ) STRICT;`