Skip to content

feat: Sprint 4 Backend Architecture + User Registration - #78

Merged
chachajona merged 24 commits into
mainfrom
upgrade
Feb 17, 2026
Merged

chachajona merged 24 commits into
mainfrom
upgrade

Conversation

@chachajona

Copy link
Copy Markdown
Collaborator

Summary

This PR completes Sprint 4: Backend Architecture Restructure + User Registration delivering production-grade Go backend architecture, self-service user registration, and casual host multiplayer mode.

Sprint 4 Completed Stories

Story ID Title Points Epic
E8-S01 Create cmd/internal/pkg Structure 3 Backend Architecture
E8-S02 Extract Domain Layer 5 Backend Architecture
E8-S03 Implement Dependency Injection 3 Backend Architecture
E8-S04 Add Error Wrapping & Context 2 Backend Architecture
E6-S10 Rate Limiting Performance Improvements 2 QA & Testing
E9-S04 Expand Shared Component Library 12 Process Automation
E10-S01 Casual Host Room Infrastructure 5 Casual Host
E10-S02 Auth Choice Page (Guest Mode) 5 Casual Host
E10-S03 Lobby Page 5 Casual Host
E10-S04 Game Menu Page 3 Casual Host
E10-S05 Casual Host Page with Controls 5 Casual Host
E10-S06 E2E Testing for Casual Host 5 QA & Testing

Total: 55 points completed


Key Changes

🏗️ Backend Architecture (Epic 8)

  • cmd/internal/pkg Structure: Google Go Style Guide compliant layout
  • Domain Layer: Pure business types in internal/domain/ (12 types)
  • Dependency Injection: App struct with DI container, WebSocket Hub with thread-safe broadcast
  • Error Handling: Custom domain errors, structured logging with internal/logger/

👤 User Registration System (Epic 10 - User Auth)

  • Self-Service Registration: Email/password with bcrypt(cost=12)
  • Password Reset: Token-based flow with Resend email service
  • Profile Management: Avatar upload (Cloudinary), display name, stats
  • Leaderboard: Global and per-event ranking with score history
  • Guest → Registered Prompt: Post-game modal encouraging registration

🎮 Casual Host Mode (Epic 10)

  • PIN-based Rooms: 20-player capacity, host controls
  • Host Dashboard: Player list, bet controls, game state management
  • WebSocket Infrastructure: Dedicated host connection handling
  • E2E Test Suite: 13 stable tests for casual host flow

📦 Shared Component Library (Epic 9)

  • @baucua/shared Package: Consolidated duplicate UI code
  • Reusable Components: Dice, LoadingScreen, DragonProgressBar, FallingParticles
  • Shared Hooks: useSound with volume control, useSymbolScramble
  • Tet-Themed Assets: 8 avatar options, cloud animations, lantern effects

🔒 Security & Performance (Epic 6)

  • Rate Limiting: Per-connection limiters, integration tests
  • Asset Protection: Vercel headers, Vite pipeline hardening
  • Graceful Shutdown: 30s timeout, client notification, RFC close frames

Technical Highlights

Backend Restructure

backend/
├── cmd/server/main.go          # Entry point
├── internal/
│   ├── app/                    # DI container, app lifecycle
│   ├── config/                 # Configuration loading
│   ├── domain/                 # Pure business types
│   ├── handler/                # HTTP handlers
│   ├── middleware/             # Auth, CSRF, rate limit
│   ├── repository/             # Database access
│   ├── service/                # Business logic
│   └── websocket/              # WS hub and handlers

New API Endpoints

Method Endpoint Description
POST /register Self-service user registration
POST /forgot-password Request password reset
POST /reset-password Reset with token
GET /profile/history Player score history
GET /profile/stats Player lifetime stats
GET /leaderboard Global rankings
POST /profile/avatar Upload avatar

Build Verification

  • Backend builds: go build ./... ✅
  • Backend tests: go test ./... (85+ tests) ✅
  • Admin builds: pnpm run build ✅
  • Admin tests: pnpm test (173 tests) ✅
  • Client builds: pnpm run build ✅
  • Client tests: pnpm test (393+ tests) ✅
  • E2E tests: pnpm test (from e2e/) ✅

Testing

  • Backend builds: go build ./...
  • Backend tests pass: go test ./... (85+ tests)
  • Admin builds: pnpm run build
  • Admin lint passes: pnpm run lint
  • Admin tests pass: pnpm test (173 tests)
  • Client builds: pnpm run build
  • Client lint passes: pnpm run lint
  • Client tests pass: pnpm test (393+ tests)
  • E2E tests pass: pnpm test (from e2e/)
  • Go race detector: go test ./... -race

Sprint Progress

Sprint Status Points
Sprint 1: Infrastructure ✅ Complete 22/22
Sprint 2: User Experience ✅ Complete 42/42
Sprint 3: QA & Security ✅ Complete 44/44
Sprint 4: Backend Architecture ✅ Complete 55/55
Sprint 5: Polish & Launch 🔜 Next 0/11

Overall Progress: 163/175 points (93%)


Post-Merge Actions

  1. Verify Vercel deployments for admin and client
  2. Test user registration flow in production
  3. Verify casual host mode with 20 players
  4. Run production smoke tests
  5. Begin Sprint 5 (Polish & Launch Preparation)

chachajona and others added 24 commits February 2, 2026 02:33
…ayout

Backend Structure (backend/):
- Create cmd/server/main.go entry point with graceful shutdown support
- Create internal/app/app.go with App struct for dependency injection
- Create internal/config/config.go with comprehensive env configuration
- Create internal/domain/types.go with core business entities (User, Team, Bet, GameSession, etc.)
- Create placeholder packages: handler, middleware, repository/postgres, service, websocket
- Update .golangci.yml with version: "2" for compatibility

Code Review Fixes:
- Remove unused imports (io, os) from internal/app/app.go
- Replace custom string helpers with standard strings package in internal/config/config.go

BMAD Artifacts (_bmad-output/implementation-artifacts/):
- Add E8-S01 implementation story with task breakdown
- Document all acceptance criteria as completed
- Record code review findings and fixes applied

Sprint Docs (_bmad-output/sprint-status.yaml):
- Mark E8-S01 as completed (3 points)
- Update Sprint 4 status: in_progress, 3/15 points completed
- Update project progress: 91/137 points (66%)

Build Verification:
- go build ./... succeeds with no errors
- go test ./... passes all tests
- No import cycles detected

Architecture Notes:
- Original main.go remains primary entry point during migration
- New cmd/server/main.go demonstrates target structure
- Internal packages prepared for E8-S02 (Extract Domain Layer)

Closes #56
…tities

Domain Layer (backend/internal/domain/types.go):
- Add database entity types: Event, GameStatePersist, GameRoundRecord, BetRecord
- Extend existing domain types with proper JSON tags and documentation
- Ensure zero dependencies on gin, sql, or websocket packages
- Total: 12 domain types covering all business entities
Service Layer (backend/internal/service/):
- Create scoring.go with ScoringService struct and 4 methods:
  - CalculateScore: single bet scoring with dice count capping
  - CalculateTotalScore: aggregate player scores across all bets
  - GetBetCount: lookup dice count for specific symbols
  - CalculateTeamScore: aggregate individual scores to team totals
- Create comprehensive test suite (scoring_test.go):
  - 12 test functions with 34 sub-tests
  - Coverage: single/double/triple matches, edge cases, zero bets
  - Real-world scenarios and team aggregation tests
- Zero external dependencies (only internal/domain + stdlib)
Documentation Updates:
- Update backend/AGENTS.md with Domain Layer and Service Layer sections
- Document all 12 domain types with descriptions
- Add ScoringService API reference with method signatures
- Update root AGENTS.md score calculation reference
- Mark internal packages: domain/service populated, others E8-S03
BMAD Artifacts (_bmad-output/implementation-artifacts/):
- Create E8-S02 story with complete task breakdown
- Mark all 5 tasks complete (T1-T5)
- Document acceptance criteria as all passing
- Add implementation notes for import cycle prevention
- Record test results: 34 tests passing, no race conditions
Build Verification:
- go build ./... succeeds with no errors
- go test ./internal/service/... passes (34 sub-tests)
- go test ./... -race passes with no race conditions
- No import cycles detected between packages
Architecture Notes:
- Domain package: pure business types only
- Service package: business logic with domain dependency only
- Root scoring_test.go retained for integration testing
- Foundation ready for E8-S03 (Dependency Injection)

Closes [#57]
…d WebSocket Hub

DI Container (backend/internal/app/app.go):
- Create App struct holding all dependencies (Config, DB, Store, Router, Server, Hub, ScoringService)
- Implement New(cfg) constructor that wires all dependencies together
- Add lifecycle management with Run() and Shutdown() methods
- Add sync.Once protection to prevent double-close panics during shutdown
- Return errors via channel instead of log.Fatalf to allow cleanup defers to run
- Add 10-second timeout to DB ping to prevent startup hangs
- Close DB connection on ping failure to prevent connection leaks
- Create buildConnectionString() helper for PostgreSQL URL construction
- Load environment from .env files with LoadEnv() function

WebSocket Hub (backend/internal/websocket/hub.go):
- Create Hub struct encapsulating clients map, pinClients map, and admin connection
- Implement per-connection write locks (writeLocks map) to prevent concurrent write panics
- Add safeWrite() and safeClose() helpers for thread-safe connection operations
- Fix deadlock: Copy client lists before releasing mutex, then perform I/O without holding lock
- Implement BroadcastToAll() for sending to all connected clients (legacy + PIN)
- Implement BroadcastToTeam() for team-specific message distribution
- Implement BroadcastToAdmin() for admin-only communication
- Add connection lifecycle: RegisterClient, UnregisterClient, RegisterPINClient, UnregisterPINClient
- Add CloseAllConnections() for graceful shutdown with proper close control frames
- Use RWMutex for better read concurrency on client lookups

Entry Point (backend/cmd/server/main.go):
- Reduce entry point from 342 lines to 26 lines
- Implement clean bootstrap: LoadEnv -> config.Load -> app.New -> application.Run
- Remove duplicate server setup logic (delegated to internal/app)
- Add error handling for application initialization failures

Project Documentation Updates:
- Update backend/AGENTS.md: Mark domain, service, config, app, websocket packages as populated
- Update anti-patterns section to reflect DI infrastructure progress
- Update E8-S02-extract-domain-layer.md: Mark E8-S03 dependency as complete

Sprint Tracking Updates:
- Update sprint-status.yaml: Mark E8-S03 as completed with completion date 2026-02-02
- Update sprint-plan-YearEndWebGame-2026-01-09.md: Add completion status to E8-S03 row
- Mark E8-S03 story as done with implementation notes and Oracle review findings
- Update dependencies graph to show E8-S01/E8-S02/E8-S03 completion chain
- Add version 2.9 to document history with E8-S03 completion details

Implementation Artifact (_bmad-output/implementation-artifacts/):
- Create comprehensive E8-S03 story documentation
- Document Acceptance Criteria with completion status (4 of 5 completed, 1 deferred)
- Record Oracle code review findings: 13 issues identified, 6 fixed, 7 deferred to E8-S04
- Document critical fixes: Hub deadlock resolution, concurrent write panic prevention
- Document medium fixes: shutdown safety, error handling, DB timeouts, connection cleanup
- Add Implementation Summary with file list and line counts
- Include Dependency Injection Pattern examples (before/after comparison)
- Document Remaining Global State for future stories (E8-S04)
- Add Review Follow-ups section with 15 Oracle review items categorized by severity
- Record Code Review Checklist completion

Oracle Code Review Fixes Applied:
- CRITICAL: Hub deadlock resolved by copying client lists before I/O operations
- CRITICAL: Hub concurrent write panic fixed with per-connection write locks
- MEDIUM: Shutdown double-close panic prevented with sync.Once protection
- MEDIUM: log.Fatalf replaced with error channel to allow cleanup defers
- MEDIUM: DB connection leak fixed by closing on ping failure
- MEDIUM: DB ping hang fixed with 10-second timeout context

Deferred to E8-S04 (Backend Architecture continuation):
- Global state elimination in root package (var db, var clients, var admin)
- Route and handler registration in DI container
- Security middleware porting (CSRF, origin checks)
- Handler migration from root package to internal/handler/

Build Verification:
- go build ./cmd/server/... succeeds with no errors
- go build ./internal/app/... succeeds with no errors
- go build ./internal/websocket/... succeeds with no errors
- go build ./internal/config/... succeeds with no errors
- All existing tests pass (backward compatible with global state)

Architecture Notes:
- App struct: central DI container with dependency wiring
- Hub struct: thread-safe WebSocket connection management
- Per-connection write locks prevent concurrent write panics
- RWMutex for read-heavy operations (client lookups)
- sync.Once ensures shutdown happens exactly once
- All new code follows Go standard project layout (cmd/internal/pkg)
- No global variables in new internal packages
- Backward compatible: existing tests use old global structure
- Clean path forward for E8-S04 handler migration

Closes [#58](#58)
…tured logging

Domain Error Layer (backend/internal/domain/errors.go):
- Create GameError interface with Code() and IsRetryable() methods
- Implement custom error types with unwrap support: SessionError, GameStateError, BettingError, DatabaseError, ValidationError, WebSocketError
- Define 30+ sentinel errors for consistent failure modes (e.g., ErrSessionNotFound, ErrBettingClosed)
- Add type-safe error checking helpers (IsSessionError, IsDatabaseError, etc.)
- Use private fields (code, message) with public accessors to enforce encapsulation
Structured Logging (backend/internal/logger/logger.go):
- Create Logger struct with level-based filtering and JSON/Text output modes
- Implement context-aware logging to track RequestID, UserID, SessionID, and PIN
- Add automatic error code extraction from domain errors via interface assertion
- Add caller tracing (File, Line, Function) for ERROR and FATAL levels
- Implement safe alternating key-value pair to map conversion (toMap helper)
- Add package-level convenience functions using a DefaultLogger instance
Error Handling & Wrapping Implementation:
- session.go: Replace legacy errors with domain.SessionError and wrap with %w for context
- game_session.go: Replace GameSessionError with domain.SessionError; update 10+ return paths
- db.go: Wrap SQL errors with operation context; add missing rows.Err() checks; log Close errors
- websocket.go: Add 40+ error checks for WriteJSON, Close, and SetDeadline; implement tagged switch for GameState
- handlers.go: Update error responses to use errors.Is checking against domain sentinels
- result.go/auth.go/app.go: Fix unhandled errors in Close/Rollback/Write operations
Lint & Code Quality Fixes (golangci-lint):
- Fix 82 issues identified by revive, goconst, staticcheck, errcheck, and gocyclo
- Extract "true" string literal to envTrue constant in internal/config
- Add required package and export comments across all new internal packages
- Add gocyclo/maintidx suppressions for large legacy functions (HandleUserMessage, main)
- Convert GameState logic to exhaustive switch with validation
- Implement blank import comments for Sentry/Postgres side-effects
Project Documentation Updates:
- Update backend/AGENTS.md: Document new domain/errors and logger/logger packages
- Update root AGENTS.md: Reflect structured logging and error wrapping conventions
- Document new error checking pattern for handlers and services
Sprint Tracking Updates:
- Update sprint-status.yaml: Mark E8-S04 as completed (2 points)
- Update sprint-plan: Mark E8-S04 as done with 2026-02-02 completion date
- Record final verification: go test ./... (all passing) and golangci-lint (0 issues)
Implementation Artifact (_bmad-output/implementation-artifacts/):
- Create E8-S04 story documentation with 5-phase implementation breakdown
- Document 100% completion of all 4 Acceptance Criteria
- Record testing strategy results including unit tests and linter verification
- Document "Target State" code examples for future reference
Architecture Notes:
- Error wrapping ensures full chain preservation for Sentry/Logging
- Domain errors decouple business logic from HTTP/SQL implementation details
- Context-aware logging allows for distributed tracing via RequestID
- sync.Once and atomic-like checks added to connection cleanups
- Foundation ready for Phase 4 (Feature parity with new architecture)

Closes #59
…ackage

Shared Package (packages/shared/):
- Add GameSymbol, GamePhase, Bet types to types/game.ts
- Add ALL_GAME_SYMBOLS constant for exhaustive symbol lists
- Create SYMBOL_ICONS mapping with PNG imports in assets/icons/
- Add utility functions: getSymbolIcon, getSymbolEmoji, toSymbolId, toGameSymbol
- Create useSound hook with stable callbacks (useCallback/useMemo)
- Fix volume handling with nullish coalescing (volume ?? 1)
- Fix race condition in stop/pause timeouts with captured Howl instance
- Create Dice component with icon/emoji modes and size variants
- Create useSymbolScramble hook for rolling animation
- Add 8 useSound tests and 16 useSymbolScramble tests
- Update package.json exports, remove dead ./theme/* path
- Make root index.ts types-only for tree-shaking

Admin Migration (admin/):
- Update types/bau-cua-tom-ca.ts to re-export from shared
- Update Dice.tsx to re-export from shared component
- Update useSound.ts to re-export from shared hook
- Update BettingStats.tsx and RoundHistoryLog.tsx to use getSymbolIcon

Client Migration (client/):
- Update types/bau-cua-tom-ca.ts to re-export from shared
- Update Dice.tsx to re-export from shared component
- Update SymbolTile.tsx to use getSymbolIcon from shared
- Update Dice.test.tsx for new component API

Documentation & BMAD Artifacts:
- Create E9-S04 implementation artifact with code review results
- Update sprint-status.yaml with E9-S04 completion
- Add comprehensive README to @baucua/shared package

Test Results:
- Admin: 173 tests passing
- Client: 393 tests passing
- Shared: 24 new tests (8 useSound + 16 useSymbolScramble)
- Total: 590 tests passing, all builds succeed

Code Review: 9 issues fixed (3 Critical, 3 High, 2 Medium, 1 Low)

Closes #64
… integration tests

Backend Implementation (backend/):
- Add `MessageLimiter` field to `Client` struct (per-connection atomic limiter)
- Create `NewMessageLimiter()` factory with 20/sec limit and burst
- Refactor `UserJoin` loop to use `CheckMessageRateLimitDirect()` (lock-free)
- Update all Join handlers (`handlePINBasedJoin`, `handleEphemeralSessionJoin`, `handlePlayerJoin`) to accept `*rate.Limiter` as parameter
- Remove global `messageLimitMutex` and `messageLimiters` map (contention source)
- Delete dead code: `checkMessageRateLimit()`, `cleanupMessageLimiter()`, `cleanupMessageLimiterByKey()`
- Reduce log amplification: move message logging after rate limit check
- Fix fail-open security flaw: `CheckMessageRateLimitDirect(nil)` now returns false

Integration Tests (backend/ratelimit_integration_test.go):
- Add `TestAdminJoinHTTPRateLimit`: Verifies 429 response on real HTTP handler
- Add `TestIPExtractionWithXForwardedFor`: Tests X-Forwarded-For, X-Real-IP, RemoteAddr fallback
- Add `TestLimiterNotResetOnJoin`: Verifies limiter persistence across join phases
- Add `TestRateLimitStatsWithPerConnectionLimiter`: Verifies stats tracking
- Add `setupRateLimitTestRouter()` helper for realistic HTTP testing
- Add `initRateLimiting()` calls to fix test order dependencies

BMAD Artifacts (_bmad-output/implementation-artifacts/):
- Create E6-S10 implementation story with verification logs
- Create E6-S11 backlog story for optional rate limiting hardening
- Update change logs with verification results

Code Review Verification (Oracle):
- Status: ✅ APPROVED
- CRITICAL Fix: Limiter bypass via Join-triggered reset verified fixed
- CRITICAL Fix: Real HTTP integration test verified
- HIGH Fix: Per-connection limiter semantics verified
- HIGH Fix: Fail-closed nil check verified
- HIGH Fix: Dead code removal verified

Test Results:
- All tests pass with `-race -count=1`
- Race detector clean
- No regressions in existing rate limiting behavior

Closes #67
…factor

Client Implementation (client/):
- Create standalone 'LoadingPage' with custom 'Dragon Scroll' progress bar
- Implement 'FallingParticles' effect with raining coins/envelopes
- Add 'Press Start' interaction flow (Loading -> Bắt Đầu Button -> Fade Out -> Login)
- Configure 'useAssetPreloader' for critical game assets
- Update routing to make '/loading' the entry point (Splash -> PIN Flow)
- Integrate high-quality WebP assets (Start Button, Background, Characters)

Shared Components (packages/shared/):
- Create 'LoadingScreen' generic wrapper with error handling and timeouts
- Create 'LoadingSpinner' with size/color variants
- Create 'LoadingProgress' with Framer Motion animations
- Create 'FallingParticles' engine for background effects
- Export optimized WebP assets via 'assets/image/index.ts'
- Add 24 new tests covering all new components

Admin Refactor (admin/):
- Refactor 'LoadingPage' to use shared 'LoadingScreen'
- Implement per-asset timeouts (8s) and global timeout (30s)
- Fix artificial 99% progress cap (now shows true progress)
- Add 'Retry' functionality for failed assets

Documentation & Artifacts:
- Update AGENTS.md with new 'Dragon Splash' connection flow
- Update sprint-status.yaml marking E9-S05 as completed
- Update story file with final technical design and verification logs

Verification:
- Visuals verified via Playwright (Desktop & Mobile)
- Client/Admin/Shared tests passing (100% success)
- Accessibility (ARIA, keyboard nav) and Performance (GPU transforms) optimized
…er capacity

Backend Implementation (backend/):
- Create CasualRoomManager with room lifecycle (create, get, kick, close)
- Add /host/join WebSocket endpoint for casual host connections
- Implement 20-player capacity limit (MaxCasualHostPlayers constant)
- Add 6-digit PIN generation with collision retry (max 10 attempts)
- Implement 4-hour room expiry with auto-cleanup goroutine
- Add input validation: nickname (1-50 chars), teamID (0-4)
- Return deep copies from GetRoom/GetPlayer to prevent race conditions
- Make StartCleanup idempotent with sync.Once
- Document mutex lock order in websocket.go header

WebSocket Messages:
- CreateRoom: Host creates room, receives PIN
- KickPlayer: Host removes player from room
- SetPhase: Host controls game phase
- DiceResult: Host submits dice results
- CloseRoom: Host ends session

Tests (backend/casual_room_test.go):
- Add 22 unit tests covering all acceptance criteria
- Test room creation, retrieval, expiry
- Test player add/kick/remove with validation
- Test capacity enforcement (20 players)
- Test host ownership validation
- Test concurrent operations with -race flag

Documentation:
- Update AGENTS.md with casual host infrastructure
- Update backend/AGENTS.md with CasualRoom type and handlers
- Update TESTING.md with casual_room_test.go (22 tests)

Code Review: 8 issues fixed (3 High, 4 Medium, 1 Low)

Closes #71
… persistence

Frontend Implementation (client/):
- Create AuthChoicePage component with Guest/Login buttons
- Add /auth route for authentication choice screen
- Implement guest mode with UUID v4 generation and localStorage persistence
- Add sessionStorage fallback when localStorage unavailable
- Extend authSlice with isGuest, guestID fields and setGuestUser action
- Implement guest session rehydration on app initialization
- Update LoadingPage to navigate to /auth instead of /login
- Add Vietnamese UI text and warning messages for storage limitations
- Use namespaced probe key (__baucua_storage_probe__) to avoid collisions

Guest Mode Features:
- "Bắt Đầu Ngay" button generates UUID, stores in storage, redirects to /home
- "Đăng Nhập" button redirects to existing /login flow
- Tết theme (red/gold) with FallingParticles background
- Mobile-first responsive design (flex-col mobile, flex-row md+)
- ARIA labels and keyboard navigation support
- Session persistence warnings in Vietnamese

State Management:
- Guest session survives page refresh via storage rehydration
- Proper logout cleanup (removes from both localStorage and sessionStorage)
- UUID fallback to timestamp-based ID if uuidv4() fails

Tests (client/src/pages/__tests__/AuthChoicePage.test.tsx):
- Add 20 unit tests covering all acceptance criteria
- Component rendering tests (5 tests)
- Guest mode flow tests (6 tests) - UUID, storage, dispatch, navigation
- Login mode flow tests (2 tests)
- LocalStorage fallback tests (3 tests) - sessionStorage, warnings
- Responsive design tests (2 tests)
- Accessibility tests (2 tests)

Dependencies:
- Add uuid v13.0.0 (RFC4122 UUID generation with built-in TypeScript types)
- Remove unnecessary @types/uuid (deprecated, uuid ships types)

Code Review: 9 issues fixed (3 Critical, 4 Medium, 2 Low)

Closes #72
…anup

Phase 6.2 - Delete Legacy Root Files:
- Delete websocket.go (2513 lines) - legacy WebSocket handlers (dead code)
- Delete auth.go, csrf.go, ratelimit.go, handlers.go (125 lines) - thin wrappers
- Delete db.go, game.go, game_state.go, history.go, result.go (778 lines) - repository wrappers
- Delete repo_globals.go, service_globals.go (26 lines) - global instances
- Delete scoring_test.go, shutdown_test.go, player_summary_test.go from root
- Total: ~3500 lines of migrated/dead code removed

Phase 6.3 - Final Test Migration:
- Move player_summary_test.go → internal/websocket/player_summary_test.go
  - Update to use domain.Users, domain.User types
  - Test internal websocket functions: toPlayerPayloads(), calculatePlayerSummary()
- Move shutdown_test.go → internal/app/shutdown_test.go
  - Test App.IsShuttingDown() instead of root globals
  - Test actual health endpoints via App.RegisterRoutes()
  - Add TestWSHandlerIsBettingPhase using StateManager
  - Bug fix: Set ShuttingDown flag in App.Shutdown() method

Migration Summary - Files Moved to internal/:
- internal/middleware/: auth.go, auth_test.go, csrf.go, csrf_test.go, ratelimit.go, ratelimit_test.go, ratelimit_integration_test.go, helpers.go
- internal/handler/: game.go, game_test.go, auth.go, history.go, prize.go, result.go, user.go, types.go
- internal/repository/: interfaces.go, postgres/user.go, postgres/team.go, postgres/gamestate.go, postgres/prize.go, postgres/result.go
- internal/service/: game_session.go, game_session_test.go, session.go, session_test.go, casual_room.go, casual_room_test.go
- internal/state/: game.go (GameStateManager)
- internal/websocket/: handler.go, broadcast.go, hub.go, player_summary_test.go
- internal/app/: shutdown_test.go

Root Directory Final State:
- main.go (25 lines) - thin bootstrap calling internal/app
- internal/ - all production code (handler, middleware, repository, service, state, websocket)
- go.mod, go.sum - dependencies
- configs (.env.example, .golangci.yml, Dockerfile, railway.json, railway.toml)
- docs (AGENTS.md, *.md documentation)
- migrations/, load_test/, cmd/server/

Build Verification:
- go build ./... succeeds with zero errors
- go test ./... passes (126 tests)
- No import cycles detected
- No root package globals (except main.go bootstrap)
- No init() functions with goroutine side effects
- main.go: 25 lines (target: <50 lines)

Architecture Notes:
- All business logic encapsulated in internal/ packages
- Clean package boundaries: domain → service → repository → handler → middleware
- No circular imports between packages
- Single source of truth for types: internal/domain
- App struct owns all dependencies via dependency injection
- WebSocket handlers use injected StateManager (no global state)
- Tests exercise production code paths

Closes #59
…ponsive grid

Frontend Implementation (client/src/pages/LobbyPage.tsx):

- Full-screen layout with bg-gradient-tet Tết-themed background

- Header: "Chọn Trò Chơi" with golden Tết styling (#B8860B)

- Subheader: "Chọn trò chơi bạn muốn tham gia"

- Responsive grid: 1 column mobile, 2 columns tablet, 3 columns desktop

- Active Bầu Cua card: clickable with hover lift effect, navigates to /baucua

- Keyboard accessible: Enter/Space key handlers, tabIndex={0}

- ARIA: aria-label="Play Bau Cua Tom Ca", role="button"

- Coming Soon cards (Lucky Roll, Poker Việt): disabled with aria-disabled="true"

- Framer Motion animations with useReducedMotion accessibility support

UI Infrastructure (client/src/components/ui/):

- Add Badge component with cva variants: default, secondary, destructive, outline, success

- Success variant: green background for "Sẵn Sàng" status

- Add @Utility bg-gradient-tet to index.css for Tết theme

Routing & Auth Flow (client/src/routes/router.tsx):

- Add /lobby route with PrivateRoute protection

- Add /baucua placeholder redirecting to /bau-cua-tom-ca

- Update AuthChoicePage guest flow to navigate to /lobby

- Update LoadingPage to navigate to /lobby

Tests (client/src/pages/__tests__/LobbyPage.test.tsx):

- Add 15 unit tests covering rendering, navigation, accessibility

- Component rendering: header, subheader, grid, active/disabled cards

- Navigation: active card click, disabled cards non-interactive

- Accessibility: ARIA labels, keyboard focus, Enter/Space keys

- Update AuthChoicePage tests for /lobby redirect

Test Results:

- LobbyPage tests: 15/15 passing

- Full client suite: 432/432 passing

- Linting: 0 errors

Closes [#73](#73)
Frontend Implementation (client/src/pages/BauCuaMenuPage.tsx):

- Create BauCuaMenuPage component with Host and Join option cards
- Full-screen layout with bg-gradient-tet Tết-themed background
- Header: "Bầu Cua Tôm Cá" with golden Tết styling (#B8860B)
- Subheader: "Chọn cách tham gia" with brown text (#8B4513)
- Host Card (🎮): "Tạo Phòng Chơi" with "Chủ phòng" badge, 20-player limit note
- Join Card (🔢): "Tham Gia Bằng Mã PIN" with "Ngường chơi" outline badge
- Both cards: min-h-[200px], hover lift effect, border highlight, shadow
- Keyboard accessible: Enter/Space key handlers, tabIndex={0}, role="button"
- ARIA: aria-label="Host a new game room", aria-label="Join existing game with PIN"
- Back button: "← Quay Lại" navigates to /lobby with ghost variant styling
- Optional tutorial button: "📖 Hướng Dẫn Chơi" (placeholder)
- Framer Motion animations with useReducedMotion accessibility support
- Staggered entry: header → subheader → cards → tutorial button

Routing & Navigation (client/src/routes/router.tsx):

- Replace /baucua redirect with BauCuaMenuPage component
- Wrap /baucua route in PrivateRoute for authentication protection
- Lazy import BauCuaMenuPage for code splitting
- Host card navigates to /baucua/host (E10-S05 placeholder)
- Join card navigates to /login (reuses existing JoinFlow)
- Maintain existing /bau-cua-tom-ca route unchanged

Responsive Design:

- Mobile-first: flex-col layout for stacked cards
- Desktop (md+): flex-row for side-by-side cards with gap-6
- Equal card sizing with flex-1
- Touch-friendly targets (min 200px height)

Accessibility (a11y):

- Full keyboard navigation support (Tab, Enter, Space)
- Visible focus indicators with ring styling
- ARIA labels on interactive elements
- Semantic HTML structure
- Screen reader friendly descriptions

Tests (client/src/pages/__tests__/BauCuaMenuPage.test.tsx):

- Add 22 unit tests covering all acceptance criteria
- Component rendering tests (6 tests): header, subheader, cards, back button
- Navigation tests (3 tests): Host card → /baucua/host, Join card → /login, Back → /lobby
- Accessibility tests (10 tests): aria-labels, role="button", keyboard focus, Enter/Space keys
- Responsive design tests (2 tests): flex-col, md:flex-row classes
- All tests passing: pnpm vitest run src/pages/__tests__/BauCuaMenuPage.test.tsx

Bug Fixes:

- Fix Vietnamese typo: "Ngường chơi" → "Ngường chơi" (lines 120, 142)
- Update test expectations to match corrected text

Build Verification:

- TypeScript: pnpm tsc --noEmit (0 errors)
- Lint: pnpm lint (0 warnings)
- Tests: pnpm test (393+ existing + 22 new, 0 regressions)
- Build: pnpm build (success)

Closes #74
…et storage

Backend Infrastructure:
- Add per-room bet storage to CasualRoomManager (StoreBet, GetPlayerBets, ClearBets)
- Extend CasualHostMsg struct with Bet and Duration fields
- Implement player-to-casual-room join flow (REST API + WebSocket)
- Add DiceResult scoring logic with user-score broadcast and leaderboard
- Implement timer broadcast (set-time message) and player notifications
- Wire casual room WebSocket handlers for host and player connections

Frontend Components (HostControls/):
- PINDisplay: Large monospace PIN display with 3-digit spacing, copy button, player count
- PlayerList: 20-slot grid with 👑 crown for host, kick buttons, confirmation modal
- HostControlBar: Phase-dependent controls (Waiting/Betting/Summarizing/Rolling/Finalizing)
- HostStatus: Sticky status bar showing round, player count, phase, host score

WebSocket Infrastructure:
- HostWebSocketService: Separate service for /host/join with CSRF auth, heartbeat, reconnection
- HostWebSocketProvider: Context provider for host WebSocket
- useCasualHost: Game control hook with dice rolling, timer, kick functionality
- casualHostSlice: Redux state management for casual host state

UI Components:
- ConfirmModal: Accessible modal with focus trap, ESC close, Framer Motion animations

Page:
- CasualHostPage: Dual-role layout with PIN display, controls, player management, betting
- Route /baucua/host registered with lazy loading and auth protection

Test Results:
- Backend: 6 packages passed
- Frontend: 24 test files, 454 tests passed
- Build: Successful (CasualHostPage: 28.03 kB)

Closes #75
… flow with 13 stable tests

E2E Test Suite (e2e/):

- casual-host-room.test.ts: Room creation and 6-digit PIN validation

- casual-host-players.test.ts: Player join, kick, host-not-in-list, modal confirmation

- casual-host-betting.test.ts: Betting phase, bet flow, dice result, next round

- casual-host-disconnect.test.ts: Room close on host disconnect, player cleanup

- casual-host-capacity.test.ts: 20-player capacity enforcement and 21st rejection

Test Infrastructure (e2e/test-utils.ts, fixtures/types.ts):

- Add loginAsTestUser() helper for e2e-host authentication

- Add setupHostWebSocket() for room creation via WebSocket

- Add setupPlayerWebSocket() for player join flow

- Add hostSend() for host WebSocket message dispatch

- Add cleanupHostWebSocket() for proper test isolation

- Extend Window interface for hostMessages/playerMessages tracking

Configuration Fix (e2e/playwright.config.ts):

- Add VITE_API_URL to webServer environment variables

- Add VITE_WS_URL to webServer environment variables

- Fixes modal confirmation test that was previously skipped

Frontend Fix (client/src/hooks/useCasualHost.tsx):

- Add toStorePlayer() helper for player payload normalization

- Handle playerID/id field variations from different message sources

- Fix player tracking inconsistencies in kick and join flows

Backend Support:

- backend/migrations/006_e2e_test_user.sql: Seed e2e-host test user

- backend/internal/websocket/handler.go: Remove debug console logging

- client/src/components/HostControls/: Minor updates for test selectors

Acceptance Criteria Coverage:

- AC-2: PIN validation (6 digits, unique generation)

- AC-3: Player joins via PIN with immediate list updates

- AC-4: Host kicks player with confirmation modal (unskipped)

- AC-5: Host excluded from player list

- AC-6: Host betting flow during Betting phase

- AC-7: Room closes when host disconnects

- AC-8: 20-player capacity enforcement

Stability Verification:

- Run 1: 13 passed (19.9s)

- Run 2: 13 passed (19.3s)

- Run 3: 13 passed (20.2s)

- Zero flaky tests confirmed across all runs

Documentation:

- Add E10-S06-integration-testing.md with complete specification

- Document test patterns and verification results

Closes #76
…vercel.json headers and Vite pipeline migration

Security Headers (admin/vercel.json, client/vercel.json):
- Add Content-Security-Policy with frame-ancestors 'none' to block iframe embedding
- Add X-Frame-Options: DENY for backward compatibility
- Add X-Content-Type-Options: nosniff to prevent MIME sniffing
- Add Referrer-Policy: strict-origin-when-cross-origin
- Add Permissions-Policy: camera=(), microphone=(), geolocation=()
- Configure connect-src to allow WebSocket to Railway backend and Sentry

Asset Migration - Admin (admin/src/assets/):
- Move bg.png from public/ to src/assets/ for content hashing
- Move all 5 MP3 sound files from public/sounds/ to src/assets/sounds/
- Create barrel export (index.ts) for Vite-imported sounds
- Update useSound hook callers to use basePath: '' with imported URLs

Asset Migration - Client (client/src/assets/):
- Move bg.png from public/ to src/assets/ for content hashing

Component Updates - Admin (admin/src/):
- RootLayout.tsx: Import bg.png via Vite, use inline style for background
- LoginPage.tsx: Import bg.png via Vite, use inline style for background
- HomePage.tsx: Use barrel-exported homeSound with basePath: ''
- BauCuaTomCaPage.tsx: Use barrel-exported bauCuaSound with basePath: ''
- LuckyDraw/Machine.tsx: Use barrel-exported luckyDrawBgSound and luckyDrawRewardSound
- hooks/useBauCuaTomCa.ts: Use barrel-exported shakingDiceSound

Component Updates - Client (client/src/):
- JoinFlow/JoinFlow.tsx: Import bg.png via Vite, use inline style for background

Shared Package (packages/shared/src/audio/):
- useSound.ts: Update to accept Vite-imported URLs (backward-compatible)
- Detect absolute URLs, data URIs, and Vite-hashed paths without basePath prefix

BMAD Artifacts (_bmad-output/implementation-artifacts/):
- Create E6-S12-asset-protection-hardening.md with complete implementation spec
- Document corrected architecture (vercel.json instead of Go middleware)
- Include acceptance criteria, test scenarios, and verification commands

Testing:
- Admin tests: 175 passed
- Client tests: 454 passed
- Shared tests: 37 passed
- Total: 666 tests passing
- Build verification: both admin and client produce hashed assets

Closes #77
…ticle effects

LoadingPage Modularization:
- Split monolithic LoadingPage.tsx into focused modules:
  - configs.ts: Desktop/mobile cloud and decoration configurations
  - index.tsx: Main component with particles integration
  - particlesConfig.ts: tsparticles fireworks preset customization
- Add responsive design with useIsMobile hook (kebab-case naming)
- Implement mobile-optimized animal character layout (Cat, Dog, Bear, Bunny, Dino)
- Desktop uses consolidated character image; mobile uses individual sprites

Visual Enhancements:
- Integrate @tsparticles/react with fireworks preset for background effects
- Add FloatingClouds component with parallax support (disable for mobile)
- Add FloatingDecorations component (coins, lanterns, red envelopes)
- Create DragonProgressBar shared component with custom frame/fill images
- New assets: cloud layers, ground layers, 5 animal characters, progress bar

Asset Loading Refactor:
- Replace Image/Audio-specific preloaders with unified fetch-based preloadAsset()
- Simplify useAssetPreloader hooks in both admin and client
- Remove verbose console logging from asset loading (silent failures)
- Add type-safe glob imports with explicit <string> type annotations

New Shared Components (@baucua/shared):
- DragonProgressBar: Animated progress bar with Vietnamese percentage label
- FloatingClouds: Parallax-enabled cloud positioning system
- FloatingDecorations: Glowing floating elements with configurable positions
- Cloud system: 9 cloud variants exported via index.ts

Package Dependencies:
- Add @tsparticles/engine, @tsparticles/preset-fireworks, @tsparticles/react
- Update pnpm-lock.yaml with particle engine dependencies

Test Updates:
- Fix mock paths: useIsMobile → use-mobile, useAssetPreloader → use-asset-preloader
- Add DragonProgressBar mock with onFillComplete callback support
- Update asset mocks for new GAME_ASSETS, ANIMAL_ASSETS exports

File Structure Changes:
- Delete: client/src/hooks/useAssetPreloader.ts (camelCase)
- Delete: client/src/pages/LoadingPage.tsx (monolithic)
- Create: client/src/hooks/use-asset-preloader.ts (kebab-case)
- Create: client/src/hooks/use-mobile.ts (breakpoint detection)
- Create: client/src/pages/LoadingPage/ directory (modular structure)

Build Verification:
- All TypeScript diagnostics pass
- ESLint + Prettier pre-commit hooks pass
- 47 files changed: +1,679/-591 lines
…and festive UI

Visual Assets (packages/shared/src/assets/image/):
- Add 6 new WebP assets: heading_auth, btn_start, btn_login, cloud_texture,
  flower_branch, golden_cloud
- Export all assets from index.ts for cross-package usage

Animation System (client/src/index.css):
- Replace FallingParticles background with multi-layer cloud scroll system
- Add 4 CSS keyframe animations: scroll-clouds (60s), scroll-clouds-slow (90s),
  scroll-clouds-reverse (75s), scroll-clouds-reverse-slow (120s)
- Create Tailwind utility classes for all animation variants

AuthChoicePage Redesign (client/src/pages/AuthChoicePage.tsx):
- Add 5-layer parallax cloud background with varying opacity (10-25%)
- Replace text title with festive heading image (HeadingAuth)
- Add mirrored FlowerBranch decorations at top corners
- Add mirrored GoldenCloud elements at bottom with glowing shadows
- Implement custom image buttons (BtnStart, BtnLogin) with:
  * Rotating golden glow ring hover effects
  * Floating Coin/Envelope particle animations
- Update responsive layout (82% mobile, 30-31% desktop)
- Remove Button component dependency

Tests (client/src/pages/__tests__/AuthChoicePage.test.tsx):
- Update test expectations for new UI structure and interactions
- Maintain coverage for storage warnings and accessibility attributes

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <[email protected]>
…hoicePage into directory

AuthChoicePage (client/src/pages/AuthChoicePage/):
- Move AuthChoicePage.tsx into directory structure (AuthChoicePage/index.tsx)
- Add configs.ts as non-runtime reference module documenting cloud layer
  configurations, decorative element positions, button animation timings,
  and storage/warning constants (CLOUD_LAYERS, DECORATIVE_ELEMENTS,
  BUTTON_DECORATIONS, MOTION_TIMINGS, STORAGE_CONFIG, WARNING_MESSAGES)
- Add 5-layer parallax cloud background using CloudTexture with varying
  opacity (10-25%) and alternating scroll/scroll-reverse animations
- Replace text title with HeadingAuth image; add mirrored FlowerBranch
  corner decorations and GoldenCloud elements with gold drop-shadow at
  bottom corners
- Implement image-based buttons (BtnStart, BtnLogin) with rotating conic-
  gradient glow ring on hover and floating Coin/Envelope particle animations
- Guest flow: generateGuestID() -> dispatches setGuestUser -> navigate('/nickname')
- Responsive widths: 82% mobile, 30-31% desktop; removes Button component dep

LoginPage (client/src/pages/LoginPage.tsx):
- Rewrite with themed background matching AuthChoicePage visual language:
  5-layer cloud parallax, FlowerBranch and GoldenCloud corner decorations
- Glass Card form (bg-[#F5F0E8]/95) with themed inputs (bg-[#E8E1D8],
  border-[#8B1F14]/20) for username and password
- Auto-focus username field on mount via useRef; Enter key advances focus
  to password field, second Enter submits the form
- Inline form validation with Vietnamese error messages; RTK Query
  useLoginMutation dispatches setCredentials on success -> navigate('/lobby')
- Back button navigates to /auth; submit button disabled during loading
  with Loader2 spinner

NicknamePage (client/src/pages/NicknamePage.tsx):
- New page for guest players to set their display name after choosing guest mode
- Guards unauthenticated access: redirects to /auth if guestID is absent
  from Redux state
- Input validated against JOIN_CONSTRAINTS (NICKNAME_MIN_LENGTH,
  NICKNAME_MAX_LENGTH) with live character counter overlay
- On success: dispatches setGuestUser({ guestID, nickname }) -> navigate('/lobby')
- Same festive background design (5-layer clouds, FlowerBranch, GoldenCloud)
  as AuthChoicePage and LoginPage

Auth Slice (client/src/stores/actions/authSlice.ts):
- Add guestID field to AuthState and getStoredGuestNickname() initializer
- Restore full guest state on init: use persisted nickname as username when
  available, otherwise fall back to Guest-{id[:6]} placeholder
- setGuestUser reducer accepts optional nickname payload and persists it to
  localStorage; logout clears guestID from both localStorage and sessionStorage

Tests (client/src/pages/__tests__/):
- AuthChoicePage.test.tsx: update expectations for image-button structure,
  storage warning data-testid attributes, and aria-label selectors
- LoginPage.test.tsx: form rendering, validation errors, keyboard navigation
  (Enter to advance/submit), loading state, success flow, back navigation,
  and RTK Query mutation mocking
- NicknamePage.test.tsx: auth guard redirect, empty/short/long validation,
  character counter, Enter key submit, back navigation, and successful
  dispatch + navigate flow
…icatedLayout

App (client/src/App.tsx):
- Remove WebSocketProvider and ConnectionStatus from root App wrapper
- Mount SessionRestorer as a sibling to AppRouter so session verification
  fires application-wide without coupling root to WebSocket lifecycle

WebSocketProvider (client/src/providers/WebSocketProvider.tsx):
- Extract session restoration logic (useVerifySessionQuery + setCredentials
  dispatch) into the standalone SessionRestorer component
- Remove useDispatch, useVerifySessionQuery, and setCredentials imports
- Fix reconnect attempt counter: cap increment with Math.min to prevent
  unbounded growth beyond maxReconnectAttempts

SessionRestorer (client/src/components/SessionRestorer.tsx):
- New renderless component: runs useVerifySessionQuery and dispatches
  setCredentials when session data arrives and no user is in Redux state
- Mounted at App root so cookie-based session restoration fires on every
  route including unauthenticated ones

Optional WebSocket Hooks (client/src/hooks/):
- useOptionalWebSocket.ts: returns WebSocketContext via useContext without
  throwing when provider is absent (returns null outside AuthenticatedLayout)
- useOptionalWebSocketMessage.ts: registers a typed message handler on
  wsManager only when context is non-null; silently no-ops on public routes

JoinFlow (client/src/components/JoinFlow/JoinFlow.tsx):
- Replace useWebSocketMessage with useOptionalWebSocketMessage for the
  auto-assign-changed handler so JoinFlow renders safely on unauthenticated
  routes that lack a WebSocket provider

AuthenticatedLayout (client/src/layouts/AuthenticatedLayout.tsx):
- New layout component: wraps Outlet with WebSocketProvider and renders
  ConnectionStatus banner, scoping WebSocket lifecycle to authenticated routes

RootLayout (client/src/layouts/RootLayout.tsx):
- Delete dead layout file superseded by AuthenticatedLayout

Router (client/src/routes/router.tsx):
- Group authenticated routes (/lobby, /home, /bau-cua-tom-ca, /baucua,
  /baucua/host) under a parent <Route element={<AuthenticatedLayout />}>
- Add /nickname route (public, no WebSocket) for NicknamePage
- Remove RootLayout import; add AuthenticatedLayout and NicknamePage lazy imports

Tests (client/src/components/JoinFlow/__tests__/JoinFlow.test.tsx):
- Update mock: replace useWebSocketMessage with useOptionalWebSocketMessage
Router Refactoring (client/src/routes/):
- Restructure 'router.tsx' to separate lobby from WebSocket layout
- Move '/lobby' route outside 'AuthenticatedLayout' (removes WebSocket dependency)
- Add '/join' route for PIN-based game entry (outside WebSocket scope)
- Create 'JoinPage.tsx' as thin wrapper for JoinFlow component
- Preserve game routes ('/bau-cua-tom-ca', '/baucua', '/baucua/host') inside AuthenticatedLayout

JoinFlow Component (client/src/components/JoinFlow/):
- Add 'useSearchParams' to read '?game=' query parameter for post-join navigation
- Implement nickname skip logic (pre-fill from Redux when 'auth.user.username' exists)
- Add 'nicknameWasSkipped' state tracking for correct back-navigation behavior
- Fix post-completion navigation: 'navigate('/loading')' → 'navigate('/' + gameParam)'
- Update 'handleBack' to conditionally navigate team→pin when nickname was skipped
- Add 'useSelector' for Redux state integration

Navigation Updates (client/src/pages/):
- Update 'BauCuaMenuPage' Join action: 'navigate('/login')' → 'navigate('/join?game=bau-cua-tom-ca')'
- Preserve Host and Back navigation handlers (unchanged)

Test Updates (client/src/):
- Update 'BauCuaMenuPage.test.tsx': 3 assertions from '/login' to '/join?game=bau-cua-tom-ca'
- Update 'JoinFlow.test.tsx': 2 assertions from '/loading' to '/bau-cua-tom-ca'
- Fix test names to reflect new navigation targets

Verification:
- TypeScript compilation clean ('npx tsc --noEmit' exit 0)
- Full test suite passing (496 tests passed, 0 failures, 10 pre-existing skips)
- No '/loading' references in JoinFlow navigation (verified via grep)
- Guest flow: nickname → lobby → game menu → JoinFlow → game (working)
- Login flow: login → lobby → game menu → JoinFlow → game (working)
- Host flow preserved: '/baucua' → Host → '/baucua/host' (unchanged)
…t accounts, avatars, and leaderboards

Database Migration (backend/migrations/007_user_registration.sql):
- Add Email, PasswordHash, AvatarURL, Role columns to users table
- Create score_history table for per-game score persistence
- Create password_reset_tokens table for secure password reset flow
- All columns nullable to preserve backward compat; idempotent
Backend Registration & Auth (backend/internal/handler/):
- register.go: POST /register with mail.ParseAddress validation, bcrypt(cost=12), session, 409 on dup
- auth.go: VerifyCredentials tries bcrypt (Email/Username) first, falls back to legacy Phone
- password_reset.go: POST /forgot-password (always 200, no enumeration), POST /reset-password with token
- avatar.go: POST /profile/avatar (Cloudinary, 2MB, http.DetectContentType validation)
- score.go: GET /profile/history, GET /profile/stats, GET /leaderboard, GET /leaderboard/:eventId
Backend Domain & Repository (backend/internal/):
- domain/types.go: extend User with Email, PasswordHash, AvatarURL, Role, CreatedAt
- domain/score.go: ScoreEntry, LifetimeStats, LeaderboardEntry types
- repository/postgres/user.go: CreateUser, GetUserByEmail, ResetToken methods
- repository/postgres/score.go: ScoreRepo with all CRUD + ranking queries
Backend Services (backend/internal/service/):
- email.go: EmailService with Resend SDK for password reset (Vietnamese HTML)
- avatar.go: AvatarService with Cloudinary SDK (c_fill,w_200,h_200, webp)
- cmd/migrate_bcrypt/: one-time CLI to hash existing Phone → PasswordHash
Client Registration & Auth (client/src/pages/):
- RegisterPage.tsx: email/password/confirm/displayName form, Vietnamese, Framer Motion
- ForgotPasswordPage.tsx, ResetPasswordPage.tsx: password reset flow
- LoginPage.tsx: add 'Quên mật khẩu?' and 'Chưa có tài khoản? Đăng ký' links
- AuthChoicePage/index.tsx: add 'Tạo tài khoản mới' register link
Client Profile & Leaderboard (client/src/pages/):
- ProfilePage.tsx: stats grid, paginated history, display name settings
- LeaderboardPage.tsx: ranked list, gold/silver/bronze styling, current user highlight
Client Avatar System (client/src/components/AvatarPicker/):
- AvatarPicker.tsx: tabbed modal 'Có sẵn' (predefined grid) + 'Tải lên' (file upload)
- AvatarDisplay.tsx: reusable avatar with initial fallback
Client Post-Game Prompt (client/src/components/PostGamePrompt/):
- PostGamePrompt.tsx: dismissible modal for guests with 'Đăng Ký Ngay' / 'Để Sau'
- usePostGamePrompt.ts: phase detection + sessionStorage guard
Client State (client/src/stores/):
- authApi.ts: register, forgotPassword, resetPassword mutations
- profileApi.ts: getProfileStats, getProfileHistory, updateProfile, leaderboard, avatar uploads
- router.tsx: /register, /forgot-password, /reset-password (public), /profile, /leaderboard (private)
Shared Package (packages/shared/):
- src/assets/avatars/: 8 Tet-themed WebP (dragon, phoenix, lotus, lantern, etc.)
- src/constants/avatars.ts: PREDEFINED_AVATARS with Vietnamese names
- package.json: add avatars export map
Code Review Fixes (Oracle-verified):
- SECURITY: Fix username/email collision, token replay (transaction), Content-Type spoofing
- BUG: Fix email case sensitivity (ToLower), legacy login (preserve casing), win rate (*100), FormData cast
- WARNING: Fix unbounded limit query (max 100), Cloudinary double-nesting, password trimming
Build Verification: go build ./... ✅, go test ./... ✅, pnpm build ✅
- Migrate .golangci.yml to v2 format (version: 2)
- Update GitHub Actions workflow to use golangci-lint v2
- Remove deprecated --out-format flag (not supported in v2)

This fixes the CI lint failure caused by v1.64 not supporting v2 config.
@chachajona
chachajona merged commit 8dbe15c into main Feb 17, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant