package server import ( "net/http" "strings" "time" "github.com/gin-gonic/gin" "scrabble/backend/internal/account" ) // The /api/v1/user account handlers wire profile editing, email binding and the // 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. // updateProfileRequest is the full editable profile. away_start/away_end are // "HH:MM" local-time bounds of the daily away window. type updateProfileRequest struct { 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"` BlockChat bool `json:"block_chat"` BlockFriendRequests bool `json:"block_friend_requests"` NotificationsInAppOnly bool `json:"notifications_in_app_only"` } // statsDTO is a durable account's lifetime statistics (the derived games-played and // win-rate are computed client-side). type statsDTO struct { Wins int `json:"wins"` Losses int `json:"losses"` Draws int `json:"draws"` MaxGamePoints int `json:"max_game_points"` MaxWordPoints int `json:"max_word_points"` } // parseAwayTime parses an "HH:MM" away-window bound. func parseAwayTime(s string) (time.Time, bool) { t, err := time.Parse(awayTimeLayout, strings.TrimSpace(s)) if err != nil { return time.Time{}, false } return t, true } // handleUpdateProfile overwrites the caller's editable profile fields. func (s *Server) handleUpdateProfile(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } var req updateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { abortBadRequest(c, "invalid request body") return } awayStart, ok := parseAwayTime(req.AwayStart) if !ok { abortBadRequest(c, "away_start must be HH:MM") return } awayEnd, ok := parseAwayTime(req.AwayEnd) if !ok { abortBadRequest(c, "away_end must be HH:MM") return } acc, err := s.accounts.UpdateProfile(c.Request.Context(), uid, account.ProfileUpdate{ DisplayName: req.DisplayName, PreferredLanguage: req.PreferredLanguage, TimeZone: req.TimeZone, AwayStart: awayStart, AwayEnd: awayEnd, BlockChat: req.BlockChat, BlockFriendRequests: req.BlockFriendRequests, NotificationsInAppOnly: req.NotificationsInAppOnly, }) if err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, profileResponseFor(acc)) } // handleStats returns the caller's lifetime statistics. func (s *Server) handleStats(c *gin.Context) { uid, ok := userID(c) if !ok { abortBadRequest(c, "missing identity") return } st, err := s.accounts.GetStats(c.Request.Context(), uid) if err != nil { s.abortErr(c, err) return } c.JSON(http.StatusOK, statsDTO{ Wins: st.Wins, Losses: st.Losses, Draws: st.Draws, MaxGamePoints: st.MaxGamePoints, MaxWordPoints: st.MaxWordPoints, }) }