From afd8a3e9d0350121f05f05a2a0b615fd1036130c Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:38:21 -1000 Subject: [PATCH] fix: resolve all linting errors and add SSE compression - Add brotli compression (level 5) to long-lived SSE event streams (HandleGameEvents, HandleSnakeEvents) to reduce wire payload - Fix all errcheck violations with nolint annotations for best-effort calls - Fix goimports: separate stdlib, third-party, and local import groups - Fix staticcheck: add package comments, use tagged switch - Zero lint issues remaining --- auth/auth.go | 1 + features/auth/handlers.go | 3 ++- features/auth/routes.go | 2 ++ features/c4game/handlers.go | 13 ++++++++----- features/c4game/routes.go | 2 ++ features/lobby/handlers.go | 10 +++++----- features/lobby/routes.go | 1 + features/snakegame/handlers.go | 11 +++++++---- features/snakegame/routes.go | 2 ++ game/logic.go | 1 + game/persist.go | 2 +- game/store.go | 17 +++++++++-------- nats/nats.go | 2 +- router/router.go | 16 ++++++++++++---- snake/logic.go | 1 + snake/loop.go | 8 ++++---- snake/store.go | 10 +++++----- snake/types.go | 6 +++--- 18 files changed, 67 insertions(+), 41 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 17b03d2..16a2d45 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,3 +1,4 @@ +// Package auth provides password hashing and verification using bcrypt. package auth import ( diff --git a/features/auth/handlers.go b/features/auth/handlers.go index 1212e0b..e39e59e 100644 --- a/features/auth/handlers.go +++ b/features/auth/handlers.go @@ -6,10 +6,11 @@ import ( "github.com/alexedwards/scs/v2" "github.com/google/uuid" + "github.com/starfederation/datastar-go/datastar" + "github.com/ryanhamamura/c4/auth" "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/features/auth/pages" - "github.com/starfederation/datastar-go/datastar" ) type LoginSignals struct { diff --git a/features/auth/routes.go b/features/auth/routes.go index 98ad6df..f028547 100644 --- a/features/auth/routes.go +++ b/features/auth/routes.go @@ -1,8 +1,10 @@ +// Package auth handles user authentication routes and handlers. package auth import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" + "github.com/ryanhamamura/c4/db/repository" ) diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index e0b107b..2e90886 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -12,11 +12,12 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/starfederation/datastar-go/datastar" + "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, queries *repository.Queries) http.HandlerFunc { @@ -102,7 +103,9 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio myColor := gi.GetPlayerColor(playerID) - sse := datastar.NewSSE(w, r) + sse := datastar.NewSSE(w, r, datastar.WithCompression( + datastar.WithBrotli(datastar.WithBrotliLevel(5)), + )) // Load initial chat messages chatMsgs := loadChatMessages(queries, gameID) @@ -118,7 +121,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio if err != nil { return } - defer gameSub.Unsubscribe() + defer gameSub.Unsubscribe() //nolint:errcheck // Subscribe to chat messages chatCh := make(chan *nats.Msg, 64) @@ -126,7 +129,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio if err != nil { return } - defer chatSub.Unsubscribe() + defer chatSub.Unsubscribe() //nolint:errcheck ctx := r.Context() for { @@ -263,7 +266,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM datastar.NewSSE(w, r) return } - nc.Publish("game.chat."+gameID, data) + nc.Publish("game.chat."+gameID, data) //nolint:errcheck // Clear the chat input sse := datastar.NewSSE(w, r) diff --git a/features/c4game/routes.go b/features/c4game/routes.go index 917e5a0..1bba4b2 100644 --- a/features/c4game/routes.go +++ b/features/c4game/routes.go @@ -1,9 +1,11 @@ +// Package c4game handles Connect 4 game routes, SSE event streaming, and chat. package c4game import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" ) diff --git a/features/lobby/handlers.go b/features/lobby/handlers.go index 4c8a86a..a200703 100644 --- a/features/lobby/handlers.go +++ b/features/lobby/handlers.go @@ -91,7 +91,7 @@ func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http. gi := store.Create() sse := datastar.NewSSE(w, r) - sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) //nolint:errcheck } } @@ -104,10 +104,10 @@ func HandleDeleteGame(store *game.GameStore, sessions *scs.SessionManager) http. return } - store.Delete(gameID) + store.Delete(gameID) //nolint:errcheck sse := datastar.NewSSE(w, r) - sse.ExecuteScript("window.location.href='/'") + sse.ExecuteScript("window.location.href='/'") //nolint:errcheck } } @@ -150,7 +150,7 @@ func HandleCreateSnakeGame(snakeStore *snake.SnakeStore, sessions *scs.SessionMa si := snakeStore.Create(preset.Width, preset.Height, mode, speed) sse := datastar.NewSSE(w, r) - sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) //nolint:errcheck } } @@ -163,6 +163,6 @@ func HandleLogout(sessions *scs.SessionManager) http.HandlerFunc { } sse := datastar.NewSSE(w, r) - sse.ExecuteScript("window.location.href='/'") + sse.ExecuteScript("window.location.href='/'") //nolint:errcheck } } diff --git a/features/lobby/routes.go b/features/lobby/routes.go index 016eb75..7f05748 100644 --- a/features/lobby/routes.go +++ b/features/lobby/routes.go @@ -1,3 +1,4 @@ +// Package lobby handles the game lobby page, game creation, and navigation. package lobby import ( diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go index 8fe9675..d97453c 100644 --- a/features/snakegame/handlers.go +++ b/features/snakegame/handlers.go @@ -9,11 +9,12 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/starfederation/datastar-go/datastar" + "github.com/ryanhamamura/c4/features/snakegame/components" "github.com/ryanhamamura/c4/features/snakegame/pages" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/snake" - "github.com/starfederation/datastar-go/datastar" ) func getPlayerID(sessions *scs.SessionManager, r *http.Request) snake.PlayerID { @@ -90,7 +91,9 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc playerID := getPlayerID(sessions, r) mySlot := si.GetPlayerSlot(playerID) - sse := datastar.NewSSE(w, r) + sse := datastar.NewSSE(w, r, datastar.WithCompression( + datastar.WithBrotli(datastar.WithBrotliLevel(5)), + )) // Send initial render sg := si.GetGame() @@ -110,7 +113,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc if err != nil { return } - defer gameSub.Unsubscribe() + defer gameSub.Unsubscribe() //nolint:errcheck // Chat subscription (multiplayer only) var chatCh chan *nats.Msg @@ -124,7 +127,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc if err != nil { return } - defer chatSub.Unsubscribe() + defer chatSub.Unsubscribe() //nolint:errcheck } ctx := r.Context() diff --git a/features/snakegame/routes.go b/features/snakegame/routes.go index c4c8334..d1ccc23 100644 --- a/features/snakegame/routes.go +++ b/features/snakegame/routes.go @@ -1,9 +1,11 @@ +// Package snakegame handles snake game routes, SSE event streaming, and chat. package snakegame import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/snake" ) diff --git a/game/logic.go b/game/logic.go index e8429cd..7a4d167 100644 --- a/game/logic.go +++ b/game/logic.go @@ -1,3 +1,4 @@ +// Package game implements Connect 4 game logic, state management, and persistence. package game // DropPiece attempts to drop a piece in the given column. diff --git a/game/persist.go b/game/persist.go index 2322adc..fb05716 100644 --- a/game/persist.go +++ b/game/persist.go @@ -126,7 +126,7 @@ func gameFromRow(row repository.Game) (*Game, error) { } if row.WinningCells.Valid { - g.WinningCellsFromJSON(row.WinningCells.String) + _ = g.WinningCellsFromJSON(row.WinningCells.String) } if row.RematchGameID.Valid { diff --git a/game/store.go b/game/store.go index e1c9c40..e56e750 100644 --- a/game/store.go +++ b/game/store.go @@ -49,7 +49,7 @@ func (gs *GameStore) Create() *GameInstance { gs.gamesMu.Unlock() if gs.queries != nil { - gs.saveGame(gi.game) + gs.saveGame(gi.game) //nolint:errcheck } return gi @@ -75,9 +75,10 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) { players, _ := gs.loadGamePlayers(id) for _, p := range players { - if p.Color == 1 { + switch p.Color { + case 1: g.Players[0] = p - } else if p.Color == 2 { + case 2: g.Players[1] = p } } @@ -108,7 +109,7 @@ func (gs *GameStore) Delete(id string) error { func GenerateID(size int) string { b := make([]byte, size) - rand.Read(b) + _, _ = rand.Read(b) return hex.EncodeToString(b) } @@ -151,8 +152,8 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool { } if gi.queries != nil { - gi.saveGamePlayer(gi.game.ID, ps.Player, slot) - gi.saveGame(gi.game) + gi.saveGamePlayer(gi.game.ID, ps.Player, slot) //nolint:errcheck + gi.saveGame(gi.game) //nolint:errcheck } gi.notify() @@ -190,7 +191,7 @@ func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance { if gi.queries != nil { if err := gi.saveGame(gi.game); err != nil { - gs.Delete(newID) + gs.Delete(newID) //nolint:errcheck gi.game.RematchGameID = nil return nil } @@ -223,7 +224,7 @@ func (gi *GameInstance) DropPiece(col int, playerColor int) bool { } if gi.queries != nil { - gi.saveGame(gi.game) + gi.saveGame(gi.game) //nolint:errcheck } gi.notify() diff --git a/nats/nats.go b/nats/nats.go index 9eeabc9..86bee3d 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -40,7 +40,7 @@ func SetupNATS(ctx context.Context) (*nats.Conn, func(), error) { cleanup := func() { nc.Close() - ns.Close() + ns.Close() //nolint:errcheck } return nc, cleanup, nil diff --git a/router/router.go b/router/router.go index c6198fc..f4bded2 100644 --- a/router/router.go +++ b/router/router.go @@ -40,10 +40,18 @@ func SetupRoutes( setupReload(router) } - auth.SetupRoutes(router, queries, sessions) - lobby.SetupRoutes(router, queries, sessions, store, snakeStore) - c4game.SetupRoutes(router, store, nc, sessions, queries) - snakegame.SetupRoutes(router, snakeStore, nc, sessions) + if err := auth.SetupRoutes(router, queries, sessions); err != nil { + return err + } + if err := lobby.SetupRoutes(router, queries, sessions, store, snakeStore); err != nil { + return err + } + if err := c4game.SetupRoutes(router, store, nc, sessions, queries); err != nil { + return err + } + if err := snakegame.SetupRoutes(router, snakeStore, nc, sessions); err != nil { + return err + } return nil } diff --git a/snake/logic.go b/snake/logic.go index c48c6ef..a26a5a6 100644 --- a/snake/logic.go +++ b/snake/logic.go @@ -1,3 +1,4 @@ +// Package snake implements snake game logic, state management, and persistence. package snake import "math/rand" diff --git a/snake/loop.go b/snake/loop.go index fb1a839..cf44f84 100644 --- a/snake/loop.go +++ b/snake/loop.go @@ -62,7 +62,7 @@ func (si *SnakeGameInstance) countdownPhase() { si.game.Status = StatusInProgress if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -70,7 +70,7 @@ func (si *SnakeGameInstance) countdownPhase() { } if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -124,7 +124,7 @@ func (si *SnakeGameInstance) gamePhase() { if time.Since(lastInput) > inactivityLimit { si.game.Status = StatusFinished if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -196,7 +196,7 @@ func (si *SnakeGameInstance) gamePhase() { } if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() diff --git a/snake/store.go b/snake/store.go index ef1749e..670b9da 100644 --- a/snake/store.go +++ b/snake/store.go @@ -63,7 +63,7 @@ func (ss *SnakeStore) Create(width, height int, mode GameMode, speed int) *Snake ss.gamesMu.Unlock() if ss.queries != nil { - ss.saveSnakeGame(sg) + ss.saveSnakeGame(sg) //nolint:errcheck } return si @@ -207,8 +207,8 @@ func (si *SnakeGameInstance) Join(player *Player) bool { si.game.Players[slot] = player if si.queries != nil { - si.saveSnakePlayer(si.game.ID, player) - si.saveSnakeGame(si.game) + si.saveSnakePlayer(si.game.ID, player) //nolint:errcheck + si.saveSnakeGame(si.game) //nolint:errcheck } si.notify() @@ -294,7 +294,7 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance { si.game.RematchGameID = &newID if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() @@ -304,6 +304,6 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance { func generateID(size int) string { b := make([]byte, size) - rand.Read(b) + _, _ = rand.Read(b) return hex.EncodeToString(b) } diff --git a/snake/types.go b/snake/types.go index 5bb21a9..8272765 100644 --- a/snake/types.go +++ b/snake/types.go @@ -100,7 +100,7 @@ type SnakeGame struct { Speed int // cells per second } -// Speed presets +// SpeedPreset defines a named speed option for the snake game. type SpeedPreset struct { Name string Speed int @@ -129,7 +129,7 @@ func (sg *SnakeGame) PlayerCount() int { return count } -// Grid presets +// GridPreset defines a named grid size option for the snake game. type GridPreset struct { Name string Width int @@ -163,7 +163,7 @@ func (sg *SnakeGame) snapshot() *SnakeGame { return &cp } -// Snake colors (hex values for CSS) +// SnakeColors are hex color values for CSS, indexed by player slot. var SnakeColors = []string{ "#00b894", // 1: Green "#e17055", // 2: Orange