R6(a): de-stage code, docs, READMEs; split stage6_test
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN) references from comments, doc-comments, service READMEs, the current-state docs (ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the .fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage history. - Rename the only stage-named identifiers: registerStage8 -> registerSocialOps, registerStage11 -> registerLinkOps (gateway transcode). - Split stage6_test.go: TestEmailLoginFlow -> email_test.go, TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go. - Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments). go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
This commit is contained in:
@@ -93,13 +93,13 @@ type gameDTO struct {
|
||||
MoveCount int `json:"move_count"`
|
||||
EndReason string `json:"end_reason"`
|
||||
// LastActivityUnix is the lobby sort key: the current turn's start for an active
|
||||
// game, the finish time once finished (Stage 17).
|
||||
// game, the finish time once finished.
|
||||
LastActivityUnix int64 `json:"last_activity_unix"`
|
||||
Seats []seatDTO `json:"seats"`
|
||||
}
|
||||
|
||||
// moveResultDTO is the outcome of a committed move. Rack carries the actor's refilled rack as
|
||||
// wire alphabet indices and BagLen the bag size after the draw (R4), so the mover renders the
|
||||
// wire alphabet indices and BagLen the bag size after the draw, so the mover renders the
|
||||
// next state from the response without a follow-up state fetch.
|
||||
type moveResultDTO struct {
|
||||
Move moveRecordDTO `json:"move"`
|
||||
@@ -109,15 +109,14 @@ type moveResultDTO struct {
|
||||
}
|
||||
|
||||
// alphabetEntryDTO is one letter of a variant's alphabet (its index, concrete letter and
|
||||
// tile value), embedded in the state view for display only when the client requests it
|
||||
// (Stage 13).
|
||||
// tile value), embedded in the state view for display only when the client requests it.
|
||||
type alphabetEntryDTO struct {
|
||||
Index int `json:"index"`
|
||||
Letter string `json:"letter"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
// stateDTO is a player's view of a game. Rack carries wire alphabet indices (Stage 13; a
|
||||
// stateDTO is a player's view of a game. Rack carries wire alphabet indices (a
|
||||
// blank is engine.BlankIndex). Alphabet is present only when the request asked for it.
|
||||
type stateDTO struct {
|
||||
Game gameDTO `json:"game"`
|
||||
@@ -236,7 +235,7 @@ func moveRecordDTOFrom(m engine.MoveRecord) moveRecordDTO {
|
||||
}
|
||||
|
||||
// moveResultDTOFrom projects a committed move result into its DTO, encoding the actor's rack as
|
||||
// wire alphabet indices (Stage 13; R4).
|
||||
// wire alphabet indices.
|
||||
func moveResultDTOFrom(r game.MoveResult) (moveResultDTO, error) {
|
||||
rack, err := engine.EncodeRack(r.Game.Variant, r.Rack)
|
||||
if err != nil {
|
||||
@@ -251,7 +250,7 @@ func moveResultDTOFrom(r game.MoveResult) (moveResultDTO, error) {
|
||||
}
|
||||
|
||||
// stateDTOFrom projects a player's state view into its DTO, encoding the rack as wire
|
||||
// alphabet indices (Stage 13). When includeAlphabet is set it also embeds the variant's
|
||||
// alphabet indices. When includeAlphabet is set it also embeds the variant's
|
||||
// display table, which the client caches per variant and renders the rack with.
|
||||
func stateDTOFrom(v game.StateView, includeAlphabet bool) (stateDTO, error) {
|
||||
rack, err := engine.EncodeRack(v.Game.Variant, v.Rack)
|
||||
|
||||
@@ -18,11 +18,10 @@ import (
|
||||
"scrabble/backend/internal/social"
|
||||
)
|
||||
|
||||
// registerRoutes wires the Stage 6 REST handlers onto the /api/v1 groups. The
|
||||
// registerRoutes wires the REST handlers onto the /api/v1 groups. The
|
||||
// internal group is gateway-only (the gateway authenticates and forwards); the
|
||||
// user group requires X-User-ID; the admin group is reached through the gateway's
|
||||
// Basic-Auth proxy. This is the representative vertical slice — further domain
|
||||
// operations follow the same pattern (PLAN.md Stage 6).
|
||||
// Basic-Auth proxy.
|
||||
func (s *Server) registerRoutes() {
|
||||
if s.sessions != nil && s.accounts != nil {
|
||||
in := s.internal
|
||||
@@ -32,13 +31,13 @@ func (s *Server) registerRoutes() {
|
||||
in.POST("/sessions/email/login", s.handleEmailLogin)
|
||||
in.POST("/sessions/resolve", s.handleResolveSession)
|
||||
in.POST("/sessions/revoke", s.handleRevokeSession)
|
||||
// Out-of-app push routing for the platform side-service (Stage 9): the
|
||||
// Out-of-app push routing for the platform side-service: the
|
||||
// gateway resolves a recipient's Telegram chat + language + in-app-only flag
|
||||
// before delivering an out-of-app notification.
|
||||
in.POST("/push-target", s.handlePushTarget)
|
||||
}
|
||||
if s.ratewatch != nil {
|
||||
// The gateway's periodic rate-limiter rejection summary (R3): feeds the
|
||||
// The gateway's periodic rate-limiter rejection summary: feeds the
|
||||
// admin console's throttled view and the high-rate auto-flag.
|
||||
s.internal.POST("/ratelimit/report", s.handleRateLimitReport)
|
||||
}
|
||||
@@ -49,7 +48,7 @@ func (s *Server) registerRoutes() {
|
||||
u.GET("/stats", s.handleStats)
|
||||
}
|
||||
if s.links != nil {
|
||||
// Account linking & merge (Stage 11). The request step always mails a code;
|
||||
// Account linking & merge. The request step always mails a code;
|
||||
// a required merge is revealed only after the code is verified, and the
|
||||
// irreversible merge is an explicit second step.
|
||||
u.POST("/link/email/request", s.handleLinkEmailRequest)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// The /api/v1/user account handlers wire profile editing, email binding and the
|
||||
// statistics read (Stage 8). They follow handlers_user.go: X-User-ID identity, a
|
||||
// statistics read. They follow handlers_user.go: X-User-ID identity, a
|
||||
// domain call, a JSON DTO. Profile editing overwrites the full editable set, so the
|
||||
// client sends the complete desired profile.
|
||||
|
||||
|
||||
@@ -560,7 +560,7 @@ func (s *Server) consolePostBroadcast(c *gin.Context) {
|
||||
|
||||
// consoleThrottled renders the rate-limit observability page: the recent
|
||||
// gateway-reported throttle episodes (in-memory, reset on a backend restart)
|
||||
// and the accounts currently carrying the soft high-rate flag (R3).
|
||||
// and the accounts currently carrying the soft high-rate flag.
|
||||
func (s *Server) consoleThrottled(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var view adminconsole.ThrottledView
|
||||
@@ -595,7 +595,7 @@ func (s *Server) consoleThrottled(c *gin.Context) {
|
||||
}
|
||||
|
||||
// consoleClearHighRateFlag clears the soft high-rate marker — the operator's
|
||||
// reversible review action (R3).
|
||||
// reversible review action.
|
||||
func (s *Server) consoleClearHighRateFlag(c *gin.Context) {
|
||||
id, ok := s.consoleUUID(c, "/_gm/users")
|
||||
if !ok {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// The /api/v1/user/blocks/* handlers wire the per-user block list (Stage 8). A block
|
||||
// The /api/v1/user/blocks/* handlers wire the per-user block list. A block
|
||||
// is mutual in effect (the social checks apply it both ways) and severs any
|
||||
// friendship between the pair. They reuse the friend handlers' targetIDRequest and
|
||||
// account-ref resolution.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// The /api/v1/user/friends/* handlers wire the social friend graph (Stage 8): the
|
||||
// The /api/v1/user/friends/* handlers wire the social friend graph: the
|
||||
// befriend-an-opponent request flow, the one-time friend-code path, and the
|
||||
// friends/incoming lists. They follow handlers_user.go: X-User-ID identity, a domain
|
||||
// call, a JSON DTO. Account ids are projected to {id, display_name} refs resolved
|
||||
|
||||
@@ -12,10 +12,9 @@ import (
|
||||
"scrabble/backend/internal/game"
|
||||
)
|
||||
|
||||
// The handlers below extend the Stage 6 vertical slice with the remaining game and
|
||||
// chat operations the UI needs (PLAN.md Stage 7). They follow the same pattern as
|
||||
// handlers_user.go: X-User-ID identity, the domain service call, a JSON DTO mapped
|
||||
// from the result.
|
||||
// The handlers below cover the game and chat operations the UI needs. They follow
|
||||
// the same pattern as handlers_user.go: X-User-ID identity, the domain service
|
||||
// call, a JSON DTO mapped from the result.
|
||||
|
||||
// hintResultDTO is the top-ranked move plus the remaining hint budget.
|
||||
type hintResultDTO struct {
|
||||
@@ -53,7 +52,7 @@ type chatListDTO struct {
|
||||
}
|
||||
|
||||
// exchangeRequest swaps the given rack tiles back into the bag. Tiles are wire alphabet
|
||||
// indices (Stage 13); a blank is engine.BlankIndex.
|
||||
// indices; a blank is engine.BlankIndex.
|
||||
type exchangeRequest struct {
|
||||
Tiles []int `json:"tiles"`
|
||||
}
|
||||
@@ -211,7 +210,7 @@ func (s *Server) handleEvaluate(c *gin.Context) {
|
||||
}
|
||||
|
||||
// handleCheckWord looks a word up in the game's pinned dictionary. The word arrives as
|
||||
// repeated ?idx= alphabet indices (Stage 13); the backend decodes them to the concrete
|
||||
// repeated ?idx= alphabet indices; the backend decodes them to the concrete
|
||||
// word for the lookup and echoes that concrete word back for the client's result cache.
|
||||
func (s *Server) handleCheckWord(c *gin.Context) {
|
||||
_, gameID, ok := s.userGame(c)
|
||||
@@ -242,7 +241,7 @@ func (s *Server) handleCheckWord(c *gin.Context) {
|
||||
}
|
||||
|
||||
// queryIndexes parses repeated integer query parameters (e.g. ?idx=2&idx=0) into a slice.
|
||||
// It carries a word-check query as alphabet indices on a GET (Stage 13).
|
||||
// It carries a word-check query as alphabet indices on a GET.
|
||||
func queryIndexes(c *gin.Context, key string) ([]int, error) {
|
||||
raw := c.QueryArray(key)
|
||||
out := make([]int, 0, len(raw))
|
||||
@@ -326,7 +325,7 @@ type draftTileDTO struct {
|
||||
Blank bool `json:"blank"`
|
||||
}
|
||||
|
||||
// draftDTO is a player's persisted client-side composition for a game (Stage 17): the
|
||||
// draftDTO is a player's persisted client-side composition for a game: the
|
||||
// preferred rack tile order (an opaque client string) and the board tiles laid but not yet
|
||||
// submitted. The gateway forwards the JSON verbatim; this layer owns its shape.
|
||||
type draftDTO struct {
|
||||
@@ -352,7 +351,7 @@ func (d draftDTO) toDomain() game.Draft {
|
||||
return game.Draft{RackOrder: d.RackOrder, BoardTiles: tiles}
|
||||
}
|
||||
|
||||
// handleGetDraft returns the player's saved composition for a game (Stage 17), or an empty
|
||||
// handleGetDraft returns the player's saved composition for a game, or an empty
|
||||
// draft when none is stored.
|
||||
func (s *Server) handleGetDraft(c *gin.Context) {
|
||||
uid, gameID, ok := s.userGame(c)
|
||||
@@ -367,7 +366,7 @@ func (s *Server) handleGetDraft(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, draftDTOFrom(d))
|
||||
}
|
||||
|
||||
// handleSaveDraft upserts the player's composition for a game (Stage 17). The service
|
||||
// handleSaveDraft upserts the player's composition for a game. The service
|
||||
// rejects a non-player with ErrNotAPlayer.
|
||||
func (s *Server) handleSaveDraft(c *gin.Context) {
|
||||
uid, gameID, ok := s.userGame(c)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"scrabble/backend/internal/lobby"
|
||||
)
|
||||
|
||||
// The /api/v1/user/invitations/* handlers wire friend-game invitations (Stage 8):
|
||||
// The /api/v1/user/invitations/* handlers wire friend-game invitations:
|
||||
// create a 2-4 player invitation, accept/decline as an invitee, cancel as the
|
||||
// inviter, and list the open invitations touching the caller. Display names for the
|
||||
// inviter and invitees are resolved from the account store.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"scrabble/backend/internal/link"
|
||||
)
|
||||
|
||||
// The /api/v1/user/link handlers drive account linking & merge (Stage 11). The
|
||||
// The /api/v1/user/link handlers drive account linking & merge. The
|
||||
// request step always mails a code (no pre-send "taken" signal, so a probe cannot
|
||||
// enumerate registered emails); confirm reveals a required merge only after the
|
||||
// code is verified; merge performs the irreversible consolidation behind an
|
||||
|
||||
@@ -23,7 +23,7 @@ type rateLimitReportEntry struct {
|
||||
}
|
||||
|
||||
// handleRateLimitReport ingests one gateway rejection report into the rate
|
||||
// watch — the admin console's throttled view and the high-rate auto-flag (R3).
|
||||
// watch — the admin console's throttled view and the high-rate auto-flag.
|
||||
// Internal, gateway-only: like sessions/resolve it trusts the network segment.
|
||||
// Malformed individual entries are skipped by the watch itself.
|
||||
func (s *Server) handleRateLimitReport(c *gin.Context) {
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestResolveSessionRejectsEmptyToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateLimitReportEndpoint covers the internal R3 report route: a malformed
|
||||
// TestRateLimitReportEndpoint covers the internal report route: a malformed
|
||||
// body is a 400, a valid report lands in the rate watch with 204.
|
||||
func TestRateLimitReportEndpoint(t *testing.T) {
|
||||
watch := ratewatch.New(ratewatch.DefaultConfig(), nil, nil)
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *Server) handleProfile(c *gin.Context) {
|
||||
}
|
||||
|
||||
// submitPlayRequest places tiles in a direction on the player's turn. Each tile's Letter
|
||||
// is a wire alphabet index (Stage 13); for a blank it is the designated letter's index.
|
||||
// is a wire alphabet index; for a blank it is the designated letter's index.
|
||||
type submitPlayRequest struct {
|
||||
Dir string `json:"dir"`
|
||||
Tiles []struct {
|
||||
@@ -39,7 +39,7 @@ type submitPlayRequest struct {
|
||||
}
|
||||
|
||||
// tilesFromRequest maps a play/evaluate request's index-addressed tiles to engine tile
|
||||
// records for the game's variant (Stage 13: a placed blank carries its designated letter's
|
||||
// records for the game's variant (a placed blank carries its designated letter's
|
||||
// index with Blank set). An out-of-range index surfaces as engine.ErrIllegalPlay (HTTP 400).
|
||||
func tilesFromRequest(variant engine.Variant, req submitPlayRequest) ([]engine.TileRecord, error) {
|
||||
tiles := make([]engine.TileRecord, 0, len(req.Tiles))
|
||||
@@ -94,7 +94,7 @@ func (s *Server) handleSubmitPlay(c *gin.Context) {
|
||||
}
|
||||
|
||||
// handleGameState returns the player's view of a game.
|
||||
// handleHideGame hides a finished game from the caller's own lobby list (Stage 17).
|
||||
// handleHideGame hides a finished game from the caller's own lobby list.
|
||||
func (s *Server) handleHideGame(c *gin.Context) {
|
||||
uid, ok := userID(c)
|
||||
if !ok {
|
||||
|
||||
@@ -50,20 +50,20 @@ type Deps struct {
|
||||
// func skips the session-readiness check.
|
||||
SessionsReady func() bool
|
||||
// Sessions, Accounts and Games are the identity, account and game-domain
|
||||
// services the Stage 6 REST handlers route to.
|
||||
// services the REST handlers route to.
|
||||
Sessions *session.Service
|
||||
Accounts *account.Store
|
||||
Games *game.Service
|
||||
// Social, Matchmaker, Invitations and Emails are the Stage 4 domain services
|
||||
// the Stage 6 REST handlers route to.
|
||||
// Social, Matchmaker, Invitations and Emails are the domain services
|
||||
// the REST handlers route to.
|
||||
Social *social.Service
|
||||
Matchmaker *lobby.Matchmaker
|
||||
Invitations *lobby.InvitationService
|
||||
Emails *account.EmailService
|
||||
// Links drives account linking & merge (Stage 11): the /api/v1/user/link
|
||||
// Links drives account linking & merge: the /api/v1/user/link
|
||||
// endpoints. A nil Links disables them.
|
||||
Links *link.Service
|
||||
// Registry holds the resident dictionaries; the admin console (Stage 10) reads
|
||||
// Registry holds the resident dictionaries; the admin console reads
|
||||
// its versions and hot-reloads new ones. DictDir is the dictionary directory a
|
||||
// reload reads a version subdirectory from. A nil Registry disables the console.
|
||||
Registry *engine.Registry
|
||||
@@ -72,7 +72,7 @@ type Deps struct {
|
||||
// nil when BACKEND_CONNECTOR_ADDR is unset (broadcasts show a "not configured"
|
||||
// notice).
|
||||
Connector *connector.Client
|
||||
// RateWatch ingests the gateway's rate-limiter rejection reports (R3): the
|
||||
// RateWatch ingests the gateway's rate-limiter rejection reports: the
|
||||
// admin console's throttled view + the high-rate auto-flag. A nil RateWatch
|
||||
// disables the internal report endpoint and the console view.
|
||||
RateWatch *ratewatch.Watch
|
||||
@@ -196,16 +196,16 @@ func (s *Server) UserGroup() *gin.RouterGroup { return s.user }
|
||||
// InternalGroup returns the gateway-facing internal route group.
|
||||
func (s *Server) InternalGroup() *gin.RouterGroup { return s.internal }
|
||||
|
||||
// Social returns the social domain service for the handlers added in Stage 6.
|
||||
// Social returns the social domain service for the handlers.
|
||||
func (s *Server) Social() *social.Service { return s.social }
|
||||
|
||||
// Matchmaker returns the in-memory matchmaking pool for the Stage 6 handlers.
|
||||
// Matchmaker returns the in-memory matchmaking pool for the handlers.
|
||||
func (s *Server) Matchmaker() *lobby.Matchmaker { return s.matchmaker }
|
||||
|
||||
// Invitations returns the friend-game invitation service for the Stage 6 handlers.
|
||||
// Invitations returns the friend-game invitation service for the handlers.
|
||||
func (s *Server) Invitations() *lobby.InvitationService { return s.invitations }
|
||||
|
||||
// Emails returns the email confirm-code service for the Stage 6 handlers.
|
||||
// Emails returns the email confirm-code service for the handlers.
|
||||
func (s *Server) Emails() *account.EmailService { return s.emails }
|
||||
|
||||
// Handler returns the underlying HTTP handler. It lets tests drive the server
|
||||
|
||||
Reference in New Issue
Block a user