package server import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // The /api/v1/user/blocks/* handlers wire the per-user block list. A block is asymmetric: // the blocker stops seeing everything from the blocked user (chat, nudge, requests, // invitations, matchmaking) while the blocked user notices nothing; it overrides — but does // not delete — any friendship (an unblock restores it). They reuse the friend handlers' // targetIDRequest and account-ref resolution. // blockListDTO is the accounts the caller has blocked, plus the per-game disguised-robot // blocks (not real accounts), which the blocked list shows as distinct personalities. type blockListDTO struct { Blocked []accountRefDTO `json:"blocked"` Robots []robotBlockDTO `json:"robots"` } // robotBlockDTO is one per-game disguised-robot block: the row id (used to unblock it), the // game name the player saw, and the game + seat it was blocked in (so the in-game card can // re-mark that seat). type robotBlockDTO struct { ID string `json:"id"` DisplayName string `json:"display_name"` GameID string `json:"game_id"` Seat int `json:"seat"` } // handleBlock blocks the body-supplied account. func (s *Server) handleBlock(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } var req targetIDRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid request body") return } target, ok := parseUUIDField(req.AccountID) if !ok { abortBadRequest(c, "invalid account id") return } // An in-game block carries the game id, so a disguised-robot opponent is recorded as a // per-game block (BlockInGame); a settings-screen block (no game) blocks a human directly. var err error if strings.TrimSpace(req.GameID) == "" { err = s.social.Block(c.Request.Context(), uid, target) } else if gameID, ok := parseUUIDField(req.GameID); !ok { abortBadRequest(c, "invalid game id") return } else { err = s.social.BlockInGame(c.Request.Context(), uid, target, gameID) } if err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, okResponse{OK: true}) } // handleUnblock removes the caller's block on the :id account. func (s *Server) handleUnblock(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } target, err := uuid.Parse(c.Param("id")) if err != nil { abortBadRequest(c, "invalid account id") return } if err := s.social.Unblock(c.Request.Context(), uid, target); err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, okResponse{OK: true}) } // handleListBlocks returns the accounts the caller has blocked. func (s *Server) handleListBlocks(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } ctx := c.Request.Context() ids, err := s.social.ListBlocks(ctx, uid) if err != nil { s.abortErr(c, err) return } robots, err := s.social.ListRobotBlocks(ctx, uid) if err != nil { s.abortErr(c, err) return } dto := blockListDTO{Blocked: s.accountRefs(ctx, ids)} for _, r := range robots { dto.Robots = append(dto.Robots, robotBlockDTO{ID: r.ID.String(), DisplayName: r.DisplayName, GameID: r.GameID.String(), Seat: r.Seat}) } c.JSON(http.StatusOK, dto) }