wheeee
parent
855ba998c9
commit
33d1c67385
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
Loading…
Reference in New Issue