package backendclient import ( "context" "net/http" "net/url" ) // The structs below mirror the backend's JSON DTOs (backend/internal/server // /dto.go). The transcode layer maps them to and from the FlatBuffers edge // payloads. // SessionResp is the credential minted by an auth operation. type SessionResp struct { Token string `json:"token"` UserID string `json:"user_id"` IsGuest bool `json:"is_guest"` DisplayName string `json:"display_name"` } // ProfileResp is an account's own profile. type ProfileResp struct { UserID string `json:"user_id"` DisplayName string `json:"display_name"` PreferredLanguage string `json:"preferred_language"` TimeZone string `json:"time_zone"` AwayStart string `json:"away_start"` AwayEnd string `json:"away_end"` HintBalance int `json:"hint_balance"` BlockChat bool `json:"block_chat"` BlockFriendRequests bool `json:"block_friend_requests"` IsGuest bool `json:"is_guest"` } // TileJSON is one placed tile, used in both play requests and move responses. type TileJSON struct { Row int `json:"row"` Col int `json:"col"` Letter string `json:"letter"` Blank bool `json:"blank"` } // MoveRecordResp is a decoded move. type MoveRecordResp struct { Player int `json:"player"` Action string `json:"action"` Dir string `json:"dir"` MainRow int `json:"main_row"` MainCol int `json:"main_col"` Tiles []TileJSON `json:"tiles"` Words []string `json:"words"` Count int `json:"count"` Score int `json:"score"` Total int `json:"total"` } // SeatResp is one seat's public standing. type SeatResp struct { Seat int `json:"seat"` AccountID string `json:"account_id"` DisplayName string `json:"display_name"` Score int `json:"score"` HintsUsed int `json:"hints_used"` IsWinner bool `json:"is_winner"` } // GameResp is the shared game summary. type GameResp struct { ID string `json:"id"` Variant string `json:"variant"` DictVersion string `json:"dict_version"` Status string `json:"status"` Players int `json:"players"` ToMove int `json:"to_move"` TurnTimeoutSecs int `json:"turn_timeout_secs"` MoveCount int `json:"move_count"` EndReason string `json:"end_reason"` Seats []SeatResp `json:"seats"` } // MoveResultResp is the outcome of a committed move. type MoveResultResp struct { Move MoveRecordResp `json:"move"` Game GameResp `json:"game"` } // StateResp is a player's view of a game. type StateResp struct { Game GameResp `json:"game"` Seat int `json:"seat"` Rack []string `json:"rack"` BagLen int `json:"bag_len"` HintsRemaining int `json:"hints_remaining"` } // MatchResp reports an auto-match outcome. type MatchResp struct { Matched bool `json:"matched"` Game *GameResp `json:"game,omitempty"` } // ChatResp is a stored chat message. type ChatResp struct { ID string `json:"id"` GameID string `json:"game_id"` SenderID string `json:"sender_id"` Kind string `json:"kind"` Body string `json:"body"` CreatedAtUnix int64 `json:"created_at_unix"` } // TelegramAuth provisions/finds the Telegram account and mints a session. func (c *Client) TelegramAuth(ctx context.Context, externalID string) (SessionResp, error) { var out SessionResp err := c.do(ctx, http.MethodPost, "/api/v1/internal/sessions/telegram", "", "", map[string]string{"external_id": externalID}, &out) return out, err } // GuestAuth provisions a guest account and mints a session. func (c *Client) GuestAuth(ctx context.Context) (SessionResp, error) { var out SessionResp err := c.do(ctx, http.MethodPost, "/api/v1/internal/sessions/guest", "", "", struct{}{}, &out) return out, err } // EmailRequest asks the backend to mail a login code. func (c *Client) EmailRequest(ctx context.Context, email string) error { return c.do(ctx, http.MethodPost, "/api/v1/internal/sessions/email/request", "", "", map[string]string{"email": email}, nil) } // EmailLogin verifies a login code and mints a session. func (c *Client) EmailLogin(ctx context.Context, email, code string) (SessionResp, error) { var out SessionResp err := c.do(ctx, http.MethodPost, "/api/v1/internal/sessions/email/login", "", "", map[string]string{"email": email, "code": code}, &out) return out, err } // ResolveSession maps a token to its account id (gateway session-cache miss). func (c *Client) ResolveSession(ctx context.Context, token string) (string, error) { var out struct { UserID string `json:"user_id"` } err := c.do(ctx, http.MethodPost, "/api/v1/internal/sessions/resolve", "", "", map[string]string{"token": token}, &out) return out.UserID, err } // Profile returns the authenticated account's profile. func (c *Client) Profile(ctx context.Context, userID string) (ProfileResp, error) { var out ProfileResp err := c.do(ctx, http.MethodGet, "/api/v1/user/profile", userID, "", nil, &out) return out, err } // SubmitPlay commits a placement on the player's turn. func (c *Client) SubmitPlay(ctx context.Context, userID, gameID, dir string, tiles []TileJSON) (MoveResultResp, error) { var out MoveResultResp body := map[string]any{"dir": dir, "tiles": tiles} err := c.do(ctx, http.MethodPost, "/api/v1/user/games/"+url.PathEscape(gameID)+"/play", userID, "", body, &out) return out, err } // GameState returns the player's view of a game. func (c *Client) GameState(ctx context.Context, userID, gameID string) (StateResp, error) { var out StateResp err := c.do(ctx, http.MethodGet, "/api/v1/user/games/"+url.PathEscape(gameID)+"/state", userID, "", nil, &out) return out, err } // Enqueue joins the auto-match pool for a variant. func (c *Client) Enqueue(ctx context.Context, userID, variant string) (MatchResp, error) { var out MatchResp err := c.do(ctx, http.MethodPost, "/api/v1/user/lobby/enqueue", userID, "", map[string]string{"variant": variant}, &out) return out, err } // Poll reports whether the caller has been paired since queueing. func (c *Client) Poll(ctx context.Context, userID string) (MatchResp, error) { var out MatchResp err := c.do(ctx, http.MethodGet, "/api/v1/user/lobby/poll", userID, "", nil, &out) return out, err } // ChatPost stores a chat message, forwarding the client IP for moderation. func (c *Client) ChatPost(ctx context.Context, userID, gameID, body, clientIP string) (ChatResp, error) { var out ChatResp err := c.do(ctx, http.MethodPost, "/api/v1/user/games/"+url.PathEscape(gameID)+"/chat", userID, clientIP, map[string]string{"body": body}, &out) return out, err } // HintResultResp is the top-ranked move plus the remaining hint budget. type HintResultResp struct { Move MoveRecordResp `json:"move"` HintsRemaining int `json:"hints_remaining"` } // EvalResultResp is an unlimited move preview. type EvalResultResp struct { Legal bool `json:"legal"` Score int `json:"score"` Words []string `json:"words"` } // WordCheckResp is a dictionary lookup outcome. type WordCheckResp struct { Word string `json:"word"` Legal bool `json:"legal"` } // HistoryResp is a game's decoded move journal. type HistoryResp struct { GameID string `json:"game_id"` Moves []MoveRecordResp `json:"moves"` } // GameListResp is the caller's games for the lobby. type GameListResp struct { Games []GameResp `json:"games"` } // ChatListResp is a game's chat history. type ChatListResp struct { Messages []ChatResp `json:"messages"` } func (c *Client) gamePath(gameID, suffix string) string { return "/api/v1/user/games/" + url.PathEscape(gameID) + suffix } // Pass forfeits the player's turn. func (c *Client) Pass(ctx context.Context, userID, gameID string) (MoveResultResp, error) { var out MoveResultResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/pass"), userID, "", struct{}{}, &out) return out, err } // Exchange swaps the chosen rack tiles back into the bag. func (c *Client) Exchange(ctx context.Context, userID, gameID string, tiles []string) (MoveResultResp, error) { var out MoveResultResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/exchange"), userID, "", map[string]any{"tiles": tiles}, &out) return out, err } // Resign resigns the player from the game. func (c *Client) Resign(ctx context.Context, userID, gameID string) (MoveResultResp, error) { var out MoveResultResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/resign"), userID, "", struct{}{}, &out) return out, err } // Hint reveals the top-ranked move and spends a hint. func (c *Client) Hint(ctx context.Context, userID, gameID string) (HintResultResp, error) { var out HintResultResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/hint"), userID, "", struct{}{}, &out) return out, err } // Evaluate previews a tentative play's legality and score. func (c *Client) Evaluate(ctx context.Context, userID, gameID, dir string, tiles []TileJSON) (EvalResultResp, error) { var out EvalResultResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/evaluate"), userID, "", map[string]any{"dir": dir, "tiles": tiles}, &out) return out, err } // CheckWord looks a word up in the game's pinned dictionary. func (c *Client) CheckWord(ctx context.Context, userID, gameID, word string) (WordCheckResp, error) { var out WordCheckResp err := c.do(ctx, http.MethodGet, c.gamePath(gameID, "/check_word")+"?word="+url.QueryEscape(word), userID, "", nil, &out) return out, err } // Complaint disputes a word-check result. func (c *Client) Complaint(ctx context.Context, userID, gameID, word, note string) error { return c.do(ctx, http.MethodPost, c.gamePath(gameID, "/complaint"), userID, "", map[string]string{"word": word, "note": note}, nil) } // History returns a game's decoded move journal. func (c *Client) History(ctx context.Context, userID, gameID string) (HistoryResp, error) { var out HistoryResp err := c.do(ctx, http.MethodGet, c.gamePath(gameID, "/history"), userID, "", nil, &out) return out, err } // ChatList returns a game's chat history. func (c *Client) ChatList(ctx context.Context, userID, gameID string) (ChatListResp, error) { var out ChatListResp err := c.do(ctx, http.MethodGet, c.gamePath(gameID, "/chat"), userID, "", nil, &out) return out, err } // Nudge posts a nudge to the player whose turn is awaited. func (c *Client) Nudge(ctx context.Context, userID, gameID string) (ChatResp, error) { var out ChatResp err := c.do(ctx, http.MethodPost, c.gamePath(gameID, "/nudge"), userID, "", struct{}{}, &out) return out, err } // GamesList returns the caller's active and finished games. func (c *Client) GamesList(ctx context.Context, userID string) (GameListResp, error) { var out GameListResp err := c.do(ctx, http.MethodGet, "/api/v1/user/games", userID, "", nil, &out) return out, err }