Compare commits
5 Commits
1e42085ce6
...
0bade0bd4f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bade0bd4f | ||
|
|
85fdfac888 | ||
|
|
d3689e36a7 | ||
|
|
91357afb1f | ||
|
|
73e79212a9 |
BIN
cmd/server/.games.go.swp
Normal file
BIN
cmd/server/.games.go.swp
Normal file
Binary file not shown.
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/glebarez/sqlite"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
type DB struct {
|
||||
scheme string
|
||||
conn string
|
||||
rw *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
|
||||
@@ -21,6 +23,7 @@ func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
|
||||
db := DB{
|
||||
scheme: scheme,
|
||||
conn: conn,
|
||||
rw: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
sql, err := db.dial(ctx)
|
||||
@@ -29,26 +32,58 @@ func NewDB(ctx context.Context, scheme, conn string) (DB, error) {
|
||||
}
|
||||
defer sql.Close()
|
||||
|
||||
if _, err := sql.ExecContext(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
uuid TEXT,
|
||||
name TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS games (
|
||||
uuid TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS players (
|
||||
user_uuid TEXT,
|
||||
game_uuid TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
game_uuid TEXT
|
||||
);
|
||||
`); err != nil {
|
||||
return DB{}, err
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (db DB) WithLock(cb func() error) error {
|
||||
db.rw.Lock()
|
||||
defer db.rw.Unlock()
|
||||
return cb()
|
||||
}
|
||||
|
||||
func (db DB) Exec(ctx context.Context, q string, args ...any) error {
|
||||
db.rw.RLock()
|
||||
defer db.rw.RUnlock()
|
||||
return db.exec(ctx, q, args...)
|
||||
}
|
||||
|
||||
func (db DB) exec(ctx context.Context, q string, args ...any) error {
|
||||
c, err := db.dial(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
_, err = c.ExecContext(ctx, q, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db DB) Query(ctx context.Context, cb func(*sql.Rows) error, q string, args ...any) error {
|
||||
db.rw.RLock()
|
||||
defer db.rw.RUnlock()
|
||||
return db.query(ctx, cb, q, args...)
|
||||
}
|
||||
|
||||
func (db DB) query(ctx context.Context, cb func(*sql.Rows) error, q string, args ...any) error {
|
||||
c, err := db.dial(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
rows, err := c.QueryContext(ctx, q, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if err := cb(rows); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return db, err
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func (db DB) dial(ctx context.Context) (*sql.DB, error) {
|
||||
|
||||
175
cmd/server/games.go
Normal file
175
cmd/server/games.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Games struct {
|
||||
db DB
|
||||
}
|
||||
|
||||
func NewGames(ctx context.Context, db DB) (Games, error) {
|
||||
err := db.Exec(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
uuid TEXT,
|
||||
updated DATETIME,
|
||||
name TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS games (
|
||||
uuid TEXT,
|
||||
updated DATETIME,
|
||||
name TEXT,
|
||||
completed DATETIME
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS players (
|
||||
user_uuid TEXT,
|
||||
game_uuid TEXT,
|
||||
updated DATETIME
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
game_uuid TEXT,
|
||||
updated DATETIME,
|
||||
payload TEXT
|
||||
);
|
||||
`)
|
||||
return Games{db: db}, err
|
||||
}
|
||||
|
||||
func (games Games) GamesForUser(ctx context.Context, id string) ([]string, error) {
|
||||
result := []string{}
|
||||
err := games.db.Query(ctx, func(rows *sql.Rows) error {
|
||||
var game string
|
||||
err := rows.Scan(&game)
|
||||
result = append(result, game)
|
||||
return err
|
||||
}, `
|
||||
SELECT players.game_uuid
|
||||
FROM players
|
||||
WHERE players.user_uuid=?
|
||||
`, id)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (games Games) GameByName(ctx context.Context, name string) (string, error) {
|
||||
return "", io.EOF
|
||||
}
|
||||
|
||||
type (
|
||||
GameState struct {
|
||||
Completed time.Time
|
||||
Players map[string]PlayerState
|
||||
}
|
||||
|
||||
PlayerState struct {
|
||||
Kills []Kill
|
||||
KillWords KillWords
|
||||
}
|
||||
|
||||
Kill struct {
|
||||
Timestamp time.Time
|
||||
Victim string
|
||||
Public bool
|
||||
}
|
||||
|
||||
KillWords struct {
|
||||
Global string
|
||||
Assigned time.Time
|
||||
Assignee string
|
||||
Assignment Assignment
|
||||
}
|
||||
|
||||
Assignment struct {
|
||||
Public []string
|
||||
Private []string
|
||||
}
|
||||
|
||||
EventType int
|
||||
|
||||
EventPlayerJoin struct {
|
||||
ID string
|
||||
}
|
||||
EventPlayerLeave struct {
|
||||
ID string
|
||||
}
|
||||
EventGameComplete struct{}
|
||||
EventAssignmentRotation struct {
|
||||
Killer string
|
||||
Killed string
|
||||
Assignments map[string]Assignment
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
PlayerJoin EventType = iota + 1
|
||||
PlayerLeave
|
||||
GameComplete
|
||||
AssignmentRotation
|
||||
)
|
||||
|
||||
func (games Games) GameState(ctx context.Context, id string) (GameState, error) {
|
||||
result := GameState{Players: map[string]PlayerState{}}
|
||||
|
||||
err := games.db.Query(ctx, func(rows *sql.Rows) error {
|
||||
var timestamp time.Time
|
||||
var payload []byte
|
||||
if err := rows.Scan(×tamp, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var peek struct {
|
||||
Type EventType
|
||||
}
|
||||
if err := json.Unmarshal(payload, &peek); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch peek.Type {
|
||||
case PlayerJoin:
|
||||
var playerJoin EventPlayerJoin
|
||||
if err := json.Unmarshal(payload, &playerJoin); err != nil {
|
||||
return err
|
||||
}
|
||||
result.Players[playerJoin.ID] = PlayerState{}
|
||||
return nil
|
||||
case PlayerLeave:
|
||||
var playerLeave EventPlayerLeave
|
||||
if err := json.Unmarshal(payload, &playerLeave); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(result.Players, playerLeave.ID)
|
||||
return nil
|
||||
case GameComplete:
|
||||
var gameComplete EventGameComplete
|
||||
if err := json.Unmarshal(payload, &gameComplete); err != nil {
|
||||
return err
|
||||
}
|
||||
result.Completed = timestamp
|
||||
return nil
|
||||
case AssignmentRotation:
|
||||
var assignmentRotation EventAssignmentRotation
|
||||
if err := json.Unmarshal(payload, &assignmentRotation); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO gather current assignees
|
||||
// TODO get victim's target
|
||||
// TODO assign victim's target to killer
|
||||
// TODO randomize everyone else so not the same as before
|
||||
return fmt.Errorf("not impl: assignment rotation: %+v", assignmentRotation)
|
||||
default:
|
||||
return fmt.Errorf("unknown event type %d: %s", peek.Type, payload)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, `
|
||||
SELECT timestamp, payload
|
||||
FROM events
|
||||
WHERE game_uuid=?
|
||||
`, id)
|
||||
|
||||
return result, err
|
||||
}
|
||||
@@ -27,10 +27,20 @@ func run(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := NewDB(ctx, config.DB.Scheme, config.DB.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
games, err := NewGames(ctx, db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
S := &S{
|
||||
ctx: ctx,
|
||||
limiter: rate.NewLimiter(10, 10),
|
||||
config: config,
|
||||
games: games,
|
||||
}
|
||||
|
||||
s := &http.Server{
|
||||
|
||||
@@ -13,6 +13,7 @@ type S struct {
|
||||
ctx context.Context
|
||||
limiter *rate.Limiter
|
||||
config Config
|
||||
games Games
|
||||
}
|
||||
|
||||
func (s *S) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
BIN
cmd/testapi/exec-testapi
Executable file
BIN
cmd/testapi/exec-testapi
Executable file
Binary file not shown.
Reference in New Issue
Block a user