package server import ( "net/http" "github.com/gin-gonic/gin" "scrabble/backend/internal/engine" ) // The /api/v1/user/* endpoints require X-User-ID (RequireUserID middleware). The // backend treats that header as the sole identity input. // handleProfile returns the authenticated account's own profile. func (s *Server) handleProfile(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } acc, err := s.accounts.GetByID(c.Request.Context(), uid) if err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, profileResponseFor(acc)) } // submitPlayRequest places tiles in a direction on the player's turn. type submitPlayRequest struct { Dir string `json:"dir"` Tiles []struct { Row int `json:"row"` Col int `json:"col"` Letter string `json:"letter"` Blank bool `json:"blank"` } `json:"tiles"` } // handleSubmitPlay validates, scores and commits a placement. func (s *Server) handleSubmitPlay(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } gameID, ok := gameIDParam(c) if !ok { abortBadRequest(c, "invalid game id") return } var req submitPlayRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid request body") return } dir, ok := parseDirection(req.Dir) if !ok { abortBadRequest(c, "dir must be H or V") return } tiles := make([]engine.TileRecord, 0, len(req.Tiles)) for _, t := range req.Tiles { tiles = append(tiles, engine.TileRecord{Row: t.Row, Col: t.Col, Letter: t.Letter, Blank: t.Blank}) } res, err := s.games.SubmitPlay(c.Request.Context(), gameID, uid, dir, tiles) if err != nil { s.abortErr(c, err) return } s.writeMoveResult(c, res) } // handleGameState returns the player's view of a game. func (s *Server) handleGameState(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } gameID, ok := gameIDParam(c) if !ok { abortBadRequest(c, "invalid game id") return } view, err := s.games.GameState(c.Request.Context(), gameID, uid) if err != nil { s.abortErr(c, err) return } dto := stateDTOFrom(view) s.fillSeatNames(c.Request.Context(), &dto.Game, map[string]string{}) c.JSON(http.StatusOK, dto) } // enqueueRequest joins the per-variant auto-match pool. type enqueueRequest struct { Variant string `json:"variant"` } // handleEnqueue joins the auto-match pool for a variant. func (s *Server) handleEnqueue(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } var req enqueueRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid request body") return } variant, err := engine.ParseVariant(req.Variant) if err != nil { abortBadRequest(c, "unknown variant") return } res, err := s.matchmaker.Enqueue(c.Request.Context(), uid, variant) if err != nil { s.abortErr(c, err) return } dto := matchDTOFrom(res) if dto.Game != nil { s.fillSeatNames(c.Request.Context(), dto.Game, map[string]string{}) } c.JSON(http.StatusOK, dto) } // handlePoll reports whether the caller has been paired since queueing. func (s *Server) handlePoll(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } res, err := s.matchmaker.Poll(c.Request.Context(), uid) if err != nil { s.abortErr(c, err) return } dto := matchDTOFrom(res) if dto.Game != nil { s.fillSeatNames(c.Request.Context(), dto.Game, map[string]string{}) } c.JSON(http.StatusOK, dto) } // chatPostRequest posts a per-game chat message. type chatPostRequest struct { Body string `json:"body"` } // handleChatPost stores a chat message from the authenticated player. The sender // IP is taken from the gateway-forwarded X-Forwarded-For header. func (s *Server) handleChatPost(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } gameID, ok := gameIDParam(c) if !ok { abortBadRequest(c, "invalid game id") return } var req chatPostRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid request body") return } msg, err := s.social.PostMessage(c.Request.Context(), gameID, uid, req.Body, clientIP(c)) if err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, chatDTOFrom(msg)) }