wheeee
parent
855ba998c9
commit
33d1c67385
|
|
@ -13,7 +13,9 @@ import (
|
||||||
|
|
||||||
func NewTestCtx(t *testing.T) context.Context {
|
func NewTestCtx(t *testing.T) context.Context {
|
||||||
d := t.TempDir()
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event interface {
|
type Event interface{}
|
||||||
LobbyEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PlayerJoin struct {
|
type PlayerJoin struct {
|
||||||
ID int
|
ID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (PlayerJoin) LobbyEvent() {}
|
|
||||||
|
|
||||||
type PlayerLeave struct {
|
type PlayerLeave struct {
|
||||||
ID int
|
ID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (PlayerLeave) LobbyEvent() {}
|
|
||||||
|
|
||||||
func ParseEvent(b []byte) (Event, error) {
|
func ParseEvent(b []byte) (Event, error) {
|
||||||
var wrapper struct {
|
var wrapper struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Package string `json:"package"`
|
|
||||||
Payload json.RawMessage `json:"payload"`
|
Payload json.RawMessage `json:"payload"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(b, &wrapper); err != nil {
|
if err := json.Unmarshal(b, &wrapper); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if wrapper.Package != "lobby" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var e Event
|
var e Event
|
||||||
|
|
@ -54,10 +45,8 @@ func MarshalEvent(e Event) ([]byte, error) {
|
||||||
var marshalme struct {
|
var marshalme struct {
|
||||||
Payload Event `json:"payload"`
|
Payload Event `json:"payload"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Package string `json:"package"`
|
|
||||||
}
|
}
|
||||||
marshalme.Payload = e
|
marshalme.Payload = e
|
||||||
marshalme.Type = fmt.Sprintf("%T", e)
|
marshalme.Type = fmt.Sprintf("%T", e)
|
||||||
marshalme.Package = "lobby"
|
|
||||||
return json.Marshal(marshalme)
|
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