From 38368ba151da9aa5cf79ec8865e302aa43b33673 Mon Sep 17 00:00:00 2001 From: Bhavnoor Singh Saroya Date: Mon, 18 Aug 2025 21:29:05 -0700 Subject: [PATCH] add db package db package allows for writing games to postgres db --- cli/commands/play.go | 9 ++++ db/db.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 db/db.go diff --git a/cli/commands/play.go b/cli/commands/play.go index 5f8dfdc..3224bd5 100644 --- a/cli/commands/play.go +++ b/cli/commands/play.go @@ -16,6 +16,8 @@ import ( "sync" "time" + // adding custom stuff here + "github.com/BattlesnakeOfficial/rules" "github.com/BattlesnakeOfficial/rules/board" "github.com/BattlesnakeOfficial/rules/client" @@ -25,8 +27,15 @@ import ( "github.com/pkg/browser" "github.com/spf13/cobra" log "github.com/spf13/jwalterweatherman" + + // adding custom stuff below here + "github.com/jackc/pgx/v5/pgxpool" ) +var db *pgxpool.Pool // global database connection pool + +// store frames in memory for now +// will write to db at end of game var frames []board.GameEvent // Used to store state for each SnakeState while running a local game diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..c36cb58 --- /dev/null +++ b/db/db.go @@ -0,0 +1,97 @@ +package db + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/BattlesnakeOfficial/rules/board" + "github.com/jackc/pgx/v5/pgxpool" +) + +// the db package provides a simple interface to interact with a PostgreSQL database, it is extremely simple and only writing, reading and deleting game info + +// @ author Bhavnoor Singh Saroya + +// Database wraps pgxpool +type Database struct { + Pool *pgxpool.Pool +} + +// Connect initializes the pool and creates the table if needed +func Connect(dsn string) (*Database, error) { + ctx := context.Background() + + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + return nil, fmt.Errorf("db connect: %w", err) + } + + // Ensure table exists + _, err = pool.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS items ( + id UUID PRIMARY KEY, + info JSONB NOT NULL, + frames JSONB[] + ) + `) + if err != nil { + return nil, fmt.Errorf("create table: %w", err) + } + + return &Database{Pool: pool}, nil +} + +func (db *Database) GetInfo(ctx context.Context, id string) (*board.Game, error) { + var gameData []byte + var it board.Game + err := db.Pool.QueryRow(ctx, + "SELECT info FROM items WHERE id=$1", id). + Scan(&gameData) + if err != nil { + return nil, err + } + // 2. Unmarshal the byte slice into the struct. + err = json.Unmarshal(gameData, &it) + if err != nil { + return nil, err + } + return &it, nil +} + +func (db *Database) WriteInfo(ctx context.Context, info board.Game, frames []board.GameEvent) error { + var err error + var uuid string = info.ID + // 1. Marshal the struct into a byte slice. + gameData, err := json.Marshal(info) + if err != nil { + return fmt.Errorf("marshal game info: %w", err) + } + + // convert []board.GameEvent to []json.RawMessage + var framesData []json.RawMessage + for _, f := range frames { + b, err := json.Marshal(f) + if err != nil { + return fmt.Errorf("marshal frame: %w", err) + } + framesData = append(framesData, b) + } + + // use pgtype to wrap into postgres []JSONB + _, errr := db.Pool.Exec(ctx, + "INSERT INTO items (id, info, frames) VALUES ($1, $2, $3)", + uuid, gameData, framesData) + if errr != nil { + print("error writing to db, bad stuff happened: ") + // panic(fmt.Sprintf("Error inserting item: %v", err)) + return fmt.Errorf("insert item: %w", errr) + } + return nil +} + +func (db *Database) Delete(ctx context.Context, id int) error { + _, err := db.Pool.Exec(ctx, + "DELETE FROM items WHERE id=$1", id) + return err +}