main
Bel LaPointe 2025-02-12 11:18:46 -07:00
parent 855ba998c9
commit 33d1c67385
8 changed files with 162 additions and 111 deletions

View File

@ -13,7 +13,9 @@ import (
func NewTestCtx(t *testing.T) context.Context {
d := t.TempDir()
b, err := sql.Open("sqlite", path.Join(d, "db.db"))
p := path.Join(d, "db.db")
t.Logf("test db at %s", p)
b, err := sql.Open("sqlite", p)
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,30 @@
package lobby
import (
"context"
lobby "gitea/price-is-wrong/src/state/fsm/lobby/internal"
"io"
)
func (l *Lobby) withEvent(ctx context.Context, e lobby.Event) error {
if err := upsertEvent(ctx, l.id, e); err != nil {
return err
}
l2, err := openID(ctx, l.id)
if err != nil {
return err
}
*l = *l2
return nil
}
func upsertEvent(ctx context.Context, lobbyID int, e lobby.Event) error {
b, err := lobby.MarshalEvent(e)
if err != nil {
return err
}
_ = b
return io.EOF
}

View File

@ -5,32 +5,23 @@ import (
"fmt"
)
type Event interface {
LobbyEvent()
}
type Event interface{}
type PlayerJoin struct {
ID int
}
func (PlayerJoin) LobbyEvent() {}
type PlayerLeave struct {
ID int
}
func (PlayerLeave) LobbyEvent() {}
func ParseEvent(b []byte) (Event, error) {
var wrapper struct {
Type string `json:"type"`
Package string `json:"package"`
Payload json.RawMessage `json:"payload"`
}
if err := json.Unmarshal(b, &wrapper); err != nil {
return nil, err
} else if wrapper.Package != "lobby" {
return nil, nil
}
var e Event
@ -54,10 +45,8 @@ func MarshalEvent(e Event) ([]byte, error) {
var marshalme struct {
Payload Event `json:"payload"`
Type string `json:"type"`
Package string `json:"package"`
}
marshalme.Payload = e
marshalme.Type = fmt.Sprintf("%T", e)
marshalme.Package = "lobby"
return json.Marshal(marshalme)
}

View File

@ -1,24 +0,0 @@
package lobby
import (
"context"
"database/sql"
"fmt"
"gitea/price-is-wrong/src/lib"
)
type Storage interface {
}
type DB struct{ db *sql.DB }
var _ Storage = DB{}
func NewDB(ctx context.Context) (DB, error) {
db := lib.ExtractDB(ctx)
if db == nil {
return DB{}, fmt.Errorf("db in context expected")
}
return DB{db: db}, nil
}

View File

@ -1,15 +0,0 @@
package lobby_test
import (
"gitea/price-is-wrong/src/lib"
lobby "gitea/price-is-wrong/src/state/fsm/lobby/internal"
"testing"
)
func TestDB(t *testing.T) {
db, err := lobby.NewDB(lib.NewTestCtx(t))
if err != nil {
t.Fatal(err)
}
_ = db
}

View File

@ -1,59 +0,0 @@
package lobby
import (
"context"
"fmt"
"gitea/price-is-wrong/src/lib/db"
"io"
)
type Lobby struct{}
func Open(ctx context.Context, id string) (Lobby, error) {
if err := initialize(ctx); err != nil {
return Lobby{}, fmt.Errorf("failed to initialize lobbies: %w", err)
}
if result, err := open(ctx, id); err != nil {
return Lobby{}, err
} else if result != nil {
} else if err := create(ctx, id); err != nil {
return Lobby{}, err
}
return mustOpen(ctx, id)
}
func mustOpen(ctx context.Context, id string) (Lobby, error) {
result, err := open(ctx, id)
if err != nil {
return Lobby{}, err
}
if result == nil {
return Lobby{}, fmt.Errorf("failed to open %s", id)
}
return *result, nil
}
func open(ctx context.Context, id string) (*Lobby, error) {
return nil, io.EOF
}
func create(ctx context.Context, id string) error {
return io.EOF
}
func initialize(ctx context.Context) error {
_, err := db.From(ctx).ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS lobbies (
id SERIAL PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS lobbies_events (
id SERIAL PRIMARY KEY,
lobby_id NUMBER,
payload TEXT,
FOREIGN KEY (lobby_id) REFERENCES lobbies (id)
);
`)
return err
}

114
src/state/fsm/lobby/open.go Normal file
View File

@ -0,0 +1,114 @@
package lobby
import (
"context"
"database/sql"
"fmt"
"gitea/price-is-wrong/src/lib/db"
lobby "gitea/price-is-wrong/src/state/fsm/lobby/internal"
"slices"
)
type Lobby struct {
id int
Players []int
}
func Open(ctx context.Context, name string) (*Lobby, error) {
if err := initialize(ctx); err != nil {
return nil, fmt.Errorf("failed to initialize lobbies: %w", err)
}
if result, err := open(ctx, name); err != nil {
return nil, err
} else if result == nil {
if err := create(ctx, name); err != nil {
return nil, err
}
}
return mustOpen(ctx, name)
}
func mustOpen(ctx context.Context, name string) (*Lobby, error) {
result, err := open(ctx, name)
if err != nil {
return nil, err
}
if result == nil {
return nil, fmt.Errorf("no lobby found with name %s", name)
}
return result, nil
}
func open(ctx context.Context, name string) (*Lobby, error) {
row := db.From(ctx).QueryRow(`SELECT id FROM lobbies WHERE name=?`, name)
if err := row.Err(); err != nil {
return nil, fmt.Errorf("no lobby found with name %s", name)
}
var id sql.NullInt32
if err := row.Scan(&id); err == sql.ErrNoRows || !id.Valid {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("failed to scan id from lobby: %w", err)
}
return openID(ctx, int(id.Int32))
}
func openID(ctx context.Context, id int) (*Lobby, error) {
rows, err := db.From(ctx).QueryContext(ctx, `
SELECT payload
FROM lobby_events
WHERE lobby_events.lobby_id=(SELECT id FROM lobbies WHERE name=?)
ORDER BY id
`, id)
if err != nil {
return nil, fmt.Errorf("failed to query event payloads for id %d: %w", id, err)
}
defer rows.Close()
result := Lobby{id: id}
for rows.Next() {
var b []byte
if err := rows.Scan(&b); err != nil {
return nil, fmt.Errorf("failed to scan event: %w", err)
}
event, err := lobby.ParseEvent(b)
if err != nil {
return nil, fmt.Errorf("failed to parse event: %w", err)
}
switch e := event.(type) {
case lobby.PlayerJoin:
result.Players = append(result.Players, e.ID)
case lobby.PlayerLeave:
result.Players = slices.DeleteFunc(result.Players, func(id int) bool { return id == e.ID })
}
}
return &result, rows.Err()
}
func create(ctx context.Context, name string) error {
_, err := db.From(ctx).ExecContext(ctx, `
INSERT INTO lobbies (name) VALUES (?)
`, name)
return err
}
func initialize(ctx context.Context) error {
_, err := db.From(ctx).ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS lobbies (
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE IF NOT EXISTS lobby_events (
id INTEGER PRIMARY KEY,
lobby_id NUMBER,
payload TEXT,
FOREIGN KEY (lobby_id) REFERENCES lobbies (id)
);
`)
return err
}

View File

@ -0,0 +1,14 @@
package lobby
import (
"context"
lobby "gitea/price-is-wrong/src/state/fsm/lobby/internal"
)
func (l *Lobby) Join(ctx context.Context, id int) error {
return l.withEvent(ctx, lobby.PlayerJoin{ID: id})
}
func (l *Lobby) Leave(ctx context.Context, id int) error {
return l.withEvent(ctx, lobby.PlayerLeave{ID: id})
}