shotgun/player/session.go

84 lines
2.6 KiB
Go

package player
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/google/uuid"
"gitlab.com/zephyrtronium/sq"
)
// Session is a session ID.
type Session struct {
uuid.UUID
}
// ParseSession parses a session ID.
func ParseSession(s string) (Session, error) {
id, err := uuid.Parse(s)
if err != nil {
return Session{}, fmt.Errorf("couldn't parse session ID: %w", err)
}
return Session{id}, nil
}
// 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;`