- User registration/login with bcrypt password hashing - SQLite database with goose migrations and sqlc-generated queries - Games and players persisted to database, resumable after restart - Guest play still supported alongside authenticated users - Auth UI components (login/register forms, auth header, guest banner)
127 lines
2.9 KiB
Go
127 lines
2.9 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
|
|
"github.com/ryanhamamura/c4/db/gen"
|
|
"github.com/ryanhamamura/c4/game"
|
|
)
|
|
|
|
type GamePersister struct {
|
|
queries *gen.Queries
|
|
}
|
|
|
|
func NewGamePersister(q *gen.Queries) *GamePersister {
|
|
return &GamePersister{queries: q}
|
|
}
|
|
|
|
func (p *GamePersister) SaveGame(g *game.Game) error {
|
|
ctx := context.Background()
|
|
|
|
existing, err := p.queries.GetGame(ctx, g.ID)
|
|
if err == sql.ErrNoRows {
|
|
_, err = p.queries.CreateGame(ctx, gen.CreateGameParams{
|
|
ID: g.ID,
|
|
Board: g.BoardToJSON(),
|
|
CurrentTurn: int64(g.CurrentTurn),
|
|
Status: int64(g.Status),
|
|
})
|
|
return err
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var winnerUserID sql.NullString
|
|
if g.Winner != nil && g.Winner.UserID != nil {
|
|
winnerUserID = sql.NullString{String: *g.Winner.UserID, Valid: true}
|
|
}
|
|
|
|
winningCells := sql.NullString{}
|
|
if wc := g.WinningCellsToJSON(); wc != "" {
|
|
winningCells = sql.NullString{String: wc, Valid: true}
|
|
}
|
|
|
|
_ = existing
|
|
return p.queries.UpdateGame(ctx, gen.UpdateGameParams{
|
|
Board: g.BoardToJSON(),
|
|
CurrentTurn: int64(g.CurrentTurn),
|
|
Status: int64(g.Status),
|
|
WinnerUserID: winnerUserID,
|
|
WinningCells: winningCells,
|
|
ID: g.ID,
|
|
})
|
|
}
|
|
|
|
func (p *GamePersister) LoadGame(id string) (*game.Game, error) {
|
|
ctx := context.Background()
|
|
row, err := p.queries.GetGame(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g := &game.Game{
|
|
ID: row.ID,
|
|
CurrentTurn: int(row.CurrentTurn),
|
|
Status: game.GameStatus(row.Status),
|
|
}
|
|
|
|
if err := g.BoardFromJSON(row.Board); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if row.WinningCells.Valid {
|
|
g.WinningCellsFromJSON(row.WinningCells.String)
|
|
}
|
|
|
|
return g, nil
|
|
}
|
|
|
|
func (p *GamePersister) SaveGamePlayer(gameID string, player *game.Player, slot int) error {
|
|
ctx := context.Background()
|
|
|
|
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 p.queries.CreateGamePlayer(ctx, gen.CreateGamePlayerParams{
|
|
GameID: gameID,
|
|
UserID: userID,
|
|
GuestPlayerID: guestPlayerID,
|
|
Nickname: player.Nickname,
|
|
Color: int64(player.Color),
|
|
Slot: int64(slot),
|
|
})
|
|
}
|
|
|
|
func (p *GamePersister) LoadGamePlayers(gameID string) ([]*game.Player, error) {
|
|
ctx := context.Background()
|
|
rows, err := p.queries.GetGamePlayers(ctx, gameID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
players := make([]*game.Player, 0, len(rows))
|
|
for _, row := range rows {
|
|
player := &game.Player{
|
|
Nickname: row.Nickname,
|
|
Color: int(row.Color),
|
|
}
|
|
|
|
if row.UserID.Valid {
|
|
player.UserID = &row.UserID.String
|
|
player.ID = game.PlayerID(row.UserID.String)
|
|
} else if row.GuestPlayerID.Valid {
|
|
player.ID = game.PlayerID(row.GuestPlayerID.String)
|
|
}
|
|
|
|
players = append(players, player)
|
|
}
|
|
|
|
return players, nil
|
|
}
|