From 83215f5c147266e0e70a9f6eb133cfb769beb675 Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Fri, 4 Jul 2025 10:02:39 +0300 Subject: [PATCH] Fix: test; limit changes on player update --- handlers/actions.go | 51 ---------------------------- handlers/game.go | 10 ++---- handlers/handlers.go | 4 +-- migrations/001_initial_schema.up.sql | 7 ++-- models/main.go | 24 ++++++------- repos/actions.go | 5 --- repos/actions_test.go | 19 ++++++----- repos/players.go | 6 ++-- repos/rooms.go | 6 ++++ 9 files changed, 40 insertions(+), 92 deletions(-) diff --git a/handlers/actions.go b/handlers/actions.go index 80a02cb..983f201 100644 --- a/handlers/actions.go +++ b/handlers/actions.go @@ -24,57 +24,6 @@ func createRoom(ctx context.Context, req *models.RoomReq) (*models.Room, error) return room, nil } -// // DEPRECATED -// func saveRoom(room *models.Room) error { -// key := models.CacheRoomPrefix + room.ID -// data, err := json.Marshal(room) -// if err != nil { -// return err -// } -// memcache.Set(key, data) -// // do I need last action here? since room save is kind of an action on itself -// // time.Now().Add(time.Hour).Sub(room.LastActionTS) -// anHour := int64(216000) // 60 * 60 * 60 -// memcache.Expire(key, anHour) -// return nil -// } - -// func getRoomByID(roomID string) (*models.Room, error) { -// roomBytes, err := memcache.Get(models.CacheRoomPrefix + roomID) -// if err != nil { -// return nil, err -// } -// resp := &models.Room{} -// if err := json.Unmarshal(roomBytes, &resp); err != nil { -// return nil, err -// } -// return resp, nil -// } - -// func removeRoom(roomID string) { -// key := models.CacheRoomPrefix + roomID -// memcache.RemoveKey(key) -// } - -// context - -// func getStateByCtx(ctx context.Context) (*models.UserState, error) { -// username, ok := ctx.Value(models.CtxUsernameKey).(string) -// if !ok { -// log.Debug("no username in ctx") -// return &models.UserState{}, errors.New("no username in ctx") -// } -// us, err := loadState(username) -// if err != nil { -// return &models.UserState{}, err -// } -// return us, nil -// } - -// func dbCreate(fi *models.FullInfo) error{ -// repo.CreateRoom() -// } - func saveFullInfo(ctx context.Context, fi *models.FullInfo) error { // INFO: no transactions; so case is possible where first object is updated but the second is not if err := repo.PlayerUpdate(ctx, fi.State); err != nil { diff --git a/handlers/game.go b/handlers/game.go index a3b5de9..b5715fe 100644 --- a/handlers/game.go +++ b/handlers/game.go @@ -159,7 +159,6 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { abortWithError(w, err.Error()) return } - // Initialize transaction ctx, tx, err := repo.InitTx(r.Context()) if err != nil { @@ -173,7 +172,6 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { panic(r) } }() - fi.Room.IsRunning = true fi.Room.IsOver = false fi.Room.TeamTurn = "blue" @@ -189,24 +187,21 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { Action: models.ActionTypeGameStarted, } fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) - // Use the new context with transaction if err := saveFullInfo(ctx, fi); err != nil { tx.Rollback() abortWithError(w, err.Error()) return } - // Save action history action.RoomID = fi.Room.ID - action.CreatedAtUnix = time.Now().Unix() + action.CreatedAt = time.Now() if err := repo.CreateAction(ctx, fi.Room.ID, &action); err != nil { tx.Rollback() log.Error("failed to save action", "error", err) abortWithError(w, err.Error()) return } - // Save word cards for _, card := range fi.Room.Cards { card.RoomID = fi.Room.ID // Ensure RoomID is set for each card @@ -273,7 +268,8 @@ func HandleJoinRoom(w http.ResponseWriter, r *http.Request) { fi.State.RoomID = &room.ID fi.Room = room fi.List = nil - if err := saveFullInfo(r.Context(), fi); err != nil { + if err := repo.PlayerSetRoomID(r.Context(), room.ID, fi.State.Username); err != nil { + log.Error("failed to set room_id for player", "error", err, "username", fi.State.Username, "room_id", room.ID) abortWithError(w, err.Error()) return } diff --git a/handlers/handlers.go b/handlers/handlers.go index ba5e93b..21b06d9 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -110,11 +110,11 @@ func HandleExit(w http.ResponseWriter, r *http.Request) { fiToSave := &models.FullInfo{ Room: exitedRoom, } - if err := saveFullInfo(r.Context(), fiToSave); err != nil { + if err := repo.PlayerExitRoom(r.Context(), fi.State.Username); err != nil { abortWithError(w, err.Error()) return } - if err := repo.PlayerExitRoom(r.Context(), fi.State.Username); err != nil { + if err := saveFullInfo(r.Context(), fiToSave); err != nil { abortWithError(w, err.Error()) return } diff --git a/migrations/001_initial_schema.up.sql b/migrations/001_initial_schema.up.sql index dc866c5..fc9a960 100644 --- a/migrations/001_initial_schema.up.sql +++ b/migrations/001_initial_schema.up.sql @@ -38,11 +38,12 @@ CREATE TABLE word_cards ( ); CREATE TABLE card_marks ( - id INTEGER PRIMARY KEY AUTOINCREMENT, card_id INTEGER NOT NULL, username TEXT NOT NULL, active BOOLEAN NOT NULL DEFAULT TRUE, - FOREIGN KEY (card_id) REFERENCES word_cards(id) ON DELETE CASCADE + FOREIGN KEY (card_id) REFERENCES word_cards(id) ON DELETE CASCADE, + FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE, + PRIMARY KEY (card_id, username) ); CREATE TABLE actions ( @@ -75,4 +76,4 @@ CREATE TABLE sessions( token_key TEXT NOT NULL DEFAULT '' UNIQUE, -- encoded value username TEXT NOT NULL, FOREIGN KEY (username) REFERENCES players(username) ON DELETE CASCADE -); \ No newline at end of file +); diff --git a/models/main.go b/models/main.go index 14e639e..d31be32 100644 --- a/models/main.go +++ b/models/main.go @@ -90,16 +90,15 @@ type Team struct { } type Action struct { - ID uint32 `json:"id" db:"id"` - RoomID string `json:"room_id" db:"room_id"` - Actor string `json:"actor" db:"actor"` - ActorColor string `json:"actor_color" db:"actor_color"` - Action string `json:"action_type" db:"action_type"` - Word string `json:"word" db:"word"` - WordColor string `json:"word_color" db:"word_color"` - Number string `json:"number_associated" db:"number_associated"` - CreatedAt time.Time `json:"created_at" db:"-"` - CreatedAtUnix int64 `db:"created_at"` + ID uint32 `json:"id" db:"id"` + RoomID string `json:"room_id" db:"room_id"` + Actor string `json:"actor" db:"actor"` + ActorColor string `json:"actor_color" db:"actor_color"` + Action string `json:"action_type" db:"action_type"` + Word string `json:"word" db:"word"` + WordColor string `json:"word_color" db:"word_color"` + Number string `json:"number_associated" db:"number_associated"` + CreatedAt time.Time `json:"created_at" db:"created_at"` } type Player struct { @@ -126,8 +125,9 @@ type BotPlayer struct { } type CardMark struct { - Username string - Active bool + CardID uint32 `db:"card_id"` + Username string `db:"username"` + Active bool `db:"active"` } type Room struct { diff --git a/repos/actions.go b/repos/actions.go index 54f1933..49388e6 100644 --- a/repos/actions.go +++ b/repos/actions.go @@ -3,7 +3,6 @@ package repos import ( "context" "gralias/models" - "time" "github.com/jmoiron/sqlx" ) @@ -21,9 +20,6 @@ func (p *RepoProvider) ListActions(ctx context.Context, roomID string) ([]models if err != nil { return nil, err } - for i := range actions { - actions[i].CreatedAt = time.Unix(0, actions[i].CreatedAtUnix) - } return actions, nil } @@ -39,7 +35,6 @@ func (p *RepoProvider) GetLastClue(ctx context.Context, roomID string) (*models. if err != nil { return nil, err } - action.CreatedAt = time.Unix(0, action.CreatedAtUnix) return action, nil } diff --git a/repos/actions_test.go b/repos/actions_test.go index 93fc2c3..13fe4d0 100644 --- a/repos/actions_test.go +++ b/repos/actions_test.go @@ -7,8 +7,8 @@ import ( "time" "github.com/jmoiron/sqlx" - "github.com/stretchr/testify/assert" _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" ) func setupActionsTestDB(t *testing.T) (*sqlx.DB, func()) { @@ -24,7 +24,7 @@ func setupActionsTestDB(t *testing.T) (*sqlx.DB, func()) { word TEXT, word_color TEXT, number_associated TEXT, - created_at INTEGER + created_at TIMESTAMP ); ` _, err = db.Exec(schema) @@ -87,9 +87,9 @@ func TestActionsRepo_ListActions(t *testing.T) { CreatedAt: time.Now().Add(-1 * time.Second), } - _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) + _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt) assert.NoError(t, err) - _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt.UnixNano()) + _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt) assert.NoError(t, err) actions, err := repo.ListActions(context.Background(), roomID) @@ -135,11 +135,11 @@ func TestActionsRepo_GetLastClue(t *testing.T) { CreatedAt: time.Now().Add(-1 * time.Second), } - _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) + _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt) assert.NoError(t, err) - _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt.UnixNano()) + _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action2.Actor, action2.ActorColor, action2.Action, action2.Word, action2.WordColor, action2.Number, action2.CreatedAt) assert.NoError(t, err) - _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action3.Actor, action3.ActorColor, action3.Action, action3.Word, action3.WordColor, action3.Number, action3.CreatedAt.UnixNano()) + _, err = db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action3.Actor, action3.ActorColor, action3.Action, action3.Word, action3.WordColor, action3.Number, action3.CreatedAt) assert.NoError(t, err) lastClue, err := repo.GetLastClue(context.Background(), roomID) @@ -164,7 +164,7 @@ func TestActionsRepo_DeleteActionsByRoomID(t *testing.T) { Number: "3", CreatedAt: time.Now(), } - _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt.UnixNano()) + _, err := db.Exec(`INSERT INTO actions (room_id, actor, actor_color, action_type, word, word_color, number_associated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, roomID, action1.Actor, action1.ActorColor, action1.Action, action1.Word, action1.WordColor, action1.Number, action1.CreatedAt) assert.NoError(t, err) err = repo.DeleteActionsByRoomID(context.Background(), roomID) @@ -174,4 +174,5 @@ func TestActionsRepo_DeleteActionsByRoomID(t *testing.T) { err = db.Get(&count, "SELECT COUNT(*) FROM actions WHERE room_id = ?", roomID) assert.NoError(t, err) assert.Equal(t, 0, count) -} \ No newline at end of file +} + diff --git a/repos/players.go b/repos/players.go index 295fbfa..2d4fb8b 100644 --- a/repos/players.go +++ b/repos/players.go @@ -48,8 +48,8 @@ func (p *RepoProvider) PlayerAdd(ctx context.Context, player *models.Player) err func (p *RepoProvider) PlayerUpdate(ctx context.Context, player *models.Player) error { db := getDB(ctx, p.DB) - _, err := db.ExecContext(ctx, "UPDATE players SET room_id = ?, username = ?, team = ?, role = ?, is_bot = ? WHERE id = ?", - player.RoomID, player.Username, player.Team, player.Role, player.IsBot, player.ID) + _, err := db.ExecContext(ctx, "UPDATE players SET team = ?, role = ? WHERE username = ?", + player.Team, player.Role, player.Username) return err } @@ -67,7 +67,7 @@ func (p *RepoProvider) PlayerSetRoomID(ctx context.Context, username, roomID str func (p *RepoProvider) PlayerExitRoom(ctx context.Context, username string) error { db := getDB(ctx, p.DB) - _, err := db.ExecContext(ctx, "UPDATE players SET room_id='', team='', role='' WHERE username = ?", username) + _, err := db.ExecContext(ctx, "UPDATE players SET room_id=null, team='', role='' WHERE username = ?", username) return err } diff --git a/repos/rooms.go b/repos/rooms.go index 89cb5d7..838da24 100644 --- a/repos/rooms.go +++ b/repos/rooms.go @@ -2,6 +2,7 @@ package repos import ( "context" + "fmt" "gralias/models" "github.com/jmoiron/sqlx" @@ -70,12 +71,14 @@ func (p *RepoProvider) RoomGetExtended(ctx context.Context, id string) (*models. room := &models.Room{} err := sqlx.GetContext(ctx, p.DB, room, `SELECT * FROM rooms WHERE id = ?`, id) if err != nil { + err = fmt.Errorf("failed to get room; %w", err) return nil, err } // Get players players := []*models.Player{} err = sqlx.SelectContext(ctx, p.DB, &players, `SELECT * FROM players WHERE room_id = ?`, id) if err != nil { + err = fmt.Errorf("failed to get players; %w", err) return nil, err } room.RedTeam.Color = string(models.UserTeamRed) @@ -108,6 +111,7 @@ func (p *RepoProvider) RoomGetExtended(ctx context.Context, id string) (*models. wordCards := []models.WordCard{} err = sqlx.SelectContext(ctx, p.DB, &wordCards, `SELECT * FROM word_cards WHERE room_id = ?`, id) if err != nil { + err = fmt.Errorf("failed to get cards; %w", err) return nil, err } room.Cards = wordCards @@ -115,6 +119,7 @@ func (p *RepoProvider) RoomGetExtended(ctx context.Context, id string) (*models. actions := []models.Action{} err = sqlx.SelectContext(ctx, p.DB, &actions, `SELECT * FROM actions WHERE room_id = ? ORDER BY created_at ASC`, id) if err != nil { + err = fmt.Errorf("failed to get actions; %w", err) return nil, err } room.ActionHistory = actions @@ -122,6 +127,7 @@ func (p *RepoProvider) RoomGetExtended(ctx context.Context, id string) (*models. settings := &models.GameSettings{} err = sqlx.GetContext(ctx, p.DB, settings, `SELECT * FROM settings WHERE room_id = ?`, id) if err != nil { + err = fmt.Errorf("failed to get settings; %w", err) return nil, err } room.Settings = *settings