package game import ( "context" "database/sql" "github.com/ryanhamamura/c4/db/repository" ) // Persistence methods on GameStore (used during Get to hydrate from DB). func (gs *GameStore) saveGame(g *Game) error { ctx := context.Background() _, err := gs.queries.GetGame(ctx, g.ID) if err == sql.ErrNoRows { _, err = gs.queries.CreateGame(ctx, repository.CreateGameParams{ ID: g.ID, Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), }) return err } if err != nil { return err } return gs.queries.UpdateGame(ctx, updateGameParams(g)) } func (gs *GameStore) loadGame(id string) (*Game, error) { row, err := gs.queries.GetGame(context.Background(), id) if err != nil { return nil, err } return gameFromRow(row) } func (gs *GameStore) loadGamePlayers(id string) ([]*Player, error) { rows, err := gs.queries.GetGamePlayers(context.Background(), id) if err != nil { return nil, err } return playersFromRows(rows), nil } // Persistence methods on GameInstance (used during gameplay mutations). func (gi *GameInstance) saveGame(g *Game) error { ctx := context.Background() _, err := gi.queries.GetGame(ctx, g.ID) if err == sql.ErrNoRows { _, err = gi.queries.CreateGame(ctx, repository.CreateGameParams{ ID: g.ID, Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), }) return err } if err != nil { return err } return gi.queries.UpdateGame(ctx, updateGameParams(g)) } func (gi *GameInstance) saveGamePlayer(gameID string, player *Player, slot int) error { var userID, guestPlayerID sql.NullString if player.UserID != nil { userID = sql.NullString{String: *player.UserID, Valid: true} } else { guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} } return gi.queries.CreateGamePlayer(context.Background(), repository.CreateGamePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, Nickname: player.Nickname, Color: int64(player.Color), Slot: int64(slot), }) } // Shared helpers for domain ↔ DB mapping. func updateGameParams(g *Game) repository.UpdateGameParams { var winnerUserID sql.NullString if g.Winner != nil && g.Winner.UserID != nil { winnerUserID = sql.NullString{String: *g.Winner.UserID, Valid: true} } var winningCells sql.NullString if wc := g.WinningCellsToJSON(); wc != "" { winningCells = sql.NullString{String: wc, Valid: true} } var rematchGameID sql.NullString if g.RematchGameID != nil { rematchGameID = sql.NullString{String: *g.RematchGameID, Valid: true} } return repository.UpdateGameParams{ Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), WinnerUserID: winnerUserID, WinningCells: winningCells, RematchGameID: rematchGameID, ID: g.ID, } } func gameFromRow(row repository.Game) (*Game, error) { g := &Game{ ID: row.ID, CurrentTurn: int(row.CurrentTurn), Status: GameStatus(row.Status), } if err := g.BoardFromJSON(row.Board); err != nil { return nil, err } if row.WinningCells.Valid { _ = g.WinningCellsFromJSON(row.WinningCells.String) } if row.RematchGameID.Valid { g.RematchGameID = &row.RematchGameID.String } return g, nil } func playersFromRows(rows []repository.GamePlayer) []*Player { players := make([]*Player, 0, len(rows)) for _, row := range rows { player := &Player{ Nickname: row.Nickname, Color: int(row.Color), } if row.UserID.Valid { player.UserID = &row.UserID.String player.ID = PlayerID(row.UserID.String) } else if row.GuestPlayerID.Valid { player.ID = PlayerID(row.GuestPlayerID.String) } players = append(players, player) } return players }