2024-01-31 23:29:31 -05:00
|
|
|
package player
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"gitlab.com/zephyrtronium/sq"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Session is a session ID.
|
2024-01-31 23:40:12 -05:00
|
|
|
type Session struct {
|
|
|
|
uuid.UUID
|
|
|
|
}
|
2024-01-31 23:29:31 -05:00
|
|
|
|
2024-02-01 09:26:27 -05:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-01-31 23:29:31 -05:00
|
|
|
// 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)
|
2024-01-31 23:40:12 -05:00
|
|
|
return Session{id}, nil
|
2024-01-31 23:29:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;`
|