refactor: remove persister abstraction layer
Some checks failed
CI / Deploy / test (pull_request) Successful in 8s
CI / Deploy / lint (pull_request) Failing after 46s
CI / Deploy / deploy (pull_request) Has been skipped

Inline persistence logic directly into game stores and handlers:
- game/persist.go: DB mapping methods on GameStore and GameInstance
- snake/persist.go: DB mapping methods on SnakeStore and SnakeGameInstance
- Chat persistence inlined into c4game handlers
- Delete db/persister.go (GamePersister, SnakePersister, ChatPersister)
- Stores now take *repository.Queries directly instead of Persister interface
This commit is contained in:
Ryan Hamamura
2026-03-02 12:30:33 -10:00
parent 8c3b3fc6ea
commit 2aa026b1d5
10 changed files with 475 additions and 448 deletions

View File

@@ -1,8 +1,10 @@
package c4game
import (
"context"
"encoding/json"
"net/http"
"slices"
"strconv"
"sync"
"time"
@@ -10,14 +12,14 @@ import (
"github.com/alexedwards/scs/v2"
"github.com/go-chi/chi/v5"
"github.com/nats-io/nats.go"
"github.com/ryanhamamura/c4/db"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/features/c4game/components"
"github.com/ryanhamamura/c4/features/c4game/pages"
"github.com/ryanhamamura/c4/game"
"github.com/starfederation/datastar-go/datastar"
)
func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc {
func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "game_id")
@@ -73,8 +75,8 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPer
// Player is in the game — render full game page
g := gi.GetGame()
uiMsgs, _ := chatPersister.LoadChatMessages(gameID)
msgs := uiChatToComponents(uiMsgs)
chatMsgs := loadChatMessages(queries, gameID)
msgs := chatToComponents(chatMsgs)
if err := pages.GamePage(g, myColor, msgs).Render(r.Context(), w); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -82,7 +84,7 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPer
}
}
func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc {
func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "game_id")
@@ -103,9 +105,9 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio
sse := datastar.NewSSE(w, r)
// Load initial chat messages
uiMsgs, _ := chatPersister.LoadChatMessages(gameID)
chatMsgs := loadChatMessages(queries, gameID)
var chatMu sync.Mutex
chatMessages := uiChatToComponents(uiMsgs)
chatMessages := chatToComponents(chatMsgs)
// Send initial render of all components
sendGameComponents(sse, gi, myColor, chatMessages, &chatMu, gameID)
@@ -203,7 +205,7 @@ func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.H
}
}
func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc {
func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
gameID := chi.URLParam(r, "game_id")
@@ -254,7 +256,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM
Message: signals.ChatMsg,
Time: time.Now().UnixMilli(),
}
chatPersister.SaveChatMessage(gameID, cm)
saveChatMessage(queries, gameID, cm)
data, err := json.Marshal(cm)
if err != nil {
@@ -353,10 +355,40 @@ func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *game.GameIns
sse.PatchElementTempl(components.Chat(msgs, gameID), datastar.WithSelectorID("c4-chat")) //nolint:errcheck
}
// uiChatToComponents converts ui.C4ChatMessage slice to components.ChatMessage slice.
func uiChatToComponents(uiMsgs []game.ChatMessage) []components.ChatMessage {
msgs := make([]components.ChatMessage, len(uiMsgs))
for i, m := range uiMsgs {
// Chat persistence helpers — inlined from the former ChatPersister.
func saveChatMessage(queries *repository.Queries, gameID string, msg game.ChatMessage) {
queries.CreateChatMessage(context.Background(), repository.CreateChatMessageParams{ //nolint:errcheck
GameID: gameID,
Nickname: msg.Nickname,
Color: int64(msg.Color),
Message: msg.Message,
CreatedAt: msg.Time,
})
}
func loadChatMessages(queries *repository.Queries, gameID string) []game.ChatMessage {
rows, err := queries.GetChatMessages(context.Background(), gameID)
if err != nil {
return nil
}
msgs := make([]game.ChatMessage, len(rows))
for i, r := range rows {
msgs[i] = game.ChatMessage{
Nickname: r.Nickname,
Color: int(r.Color),
Message: r.Message,
Time: r.CreatedAt,
}
}
// DB returns newest-first; reverse for display
slices.Reverse(msgs)
return msgs
}
func chatToComponents(chatMsgs []game.ChatMessage) []components.ChatMessage {
msgs := make([]components.ChatMessage, len(chatMsgs))
for i, m := range chatMsgs {
msgs[i] = components.ChatMessage{
Nickname: m.Nickname,
Color: m.Color,

View File

@@ -4,7 +4,7 @@ import (
"github.com/alexedwards/scs/v2"
"github.com/go-chi/chi/v5"
"github.com/nats-io/nats.go"
"github.com/ryanhamamura/c4/db"
"github.com/ryanhamamura/c4/db/repository"
"github.com/ryanhamamura/c4/game"
)
@@ -13,14 +13,14 @@ func SetupRoutes(
store *game.GameStore,
nc *nats.Conn,
sessions *scs.SessionManager,
chatPersister *db.ChatPersister,
queries *repository.Queries,
) error {
router.Get("/game/{game_id}", HandleGamePage(store, sessions, chatPersister))
router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, chatPersister))
router.Get("/game/{game_id}", HandleGamePage(store, sessions, queries))
router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, queries))
router.Route("/api/game/{game_id}", func(r chi.Router) {
r.Post("/drop", HandleDropPiece(store, sessions))
r.Post("/chat", HandleSendChat(store, nc, sessions, chatPersister))
r.Post("/chat", HandleSendChat(store, nc, sessions, queries))
r.Post("/join", HandleSetNickname(store, sessions))
r.Post("/rematch", HandleRematch(store, sessions))
})