feat: expose authoritative ranked profile

This commit is contained in:
Josh Creek
2026-08-31 21:27:21 +01:00
parent 846663e320
commit 236cca30ba
4 changed files with 82 additions and 8 deletions
+41 -6
View File
@@ -21,12 +21,14 @@ const maxBodyBytes = 8 << 10
type CandidateProvider func(playerID, ticketID string) (domain.Candidate, error)
type Service struct {
Sessions *domain.SessionStore
Queue *domain.Queue
Candidate CandidateProvider
Now func() time.Time
Proposals map[string]*domain.Proposal
proposalMu sync.Mutex
Sessions *domain.SessionStore
Queue *domain.Queue
Candidate CandidateProvider
Now func() time.Time
Proposals map[string]*domain.Proposal
RankedProfiles map[string]domain.RankedProfile
TierPolicy domain.TierPolicy
proposalMu sync.Mutex
}
func (s *Service) Handler() http.Handler {
@@ -35,6 +37,7 @@ func (s *Service) Handler() http.Handler {
mux.HandleFunc("/v1/queue", s.queueCreate)
mux.HandleFunc("/v1/queue/", s.queueMutation)
mux.HandleFunc("/v1/proposals/", s.proposalMutation)
mux.HandleFunc("/v1/profile/ranked", s.rankedProfile)
return mux
}
@@ -184,6 +187,38 @@ func (s *Service) proposalMutation(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, toProposalResponse(updated))
}
type rankedProfileResponse struct {
Rating float64 `json:"rating"`
RD float64 `json:"rd"`
Volatility float64 `json:"volatility"`
RankedGames int `json:"ranked_games"`
Tier string `json:"tier"`
Provisional bool `json:"provisional"`
SeasonID string `json:"season_id,omitempty"`
}
func (s *Service) rankedProfile(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed")
return
}
playerID, ok := s.authenticate(w, r)
if !ok {
return
}
profile, exists := s.RankedProfiles[playerID]
if !exists {
writeError(w, http.StatusNotFound, "not_found")
return
}
tier, err := domain.RankedTier(profile, s.TierPolicy)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "ranked_profile_unavailable")
return
}
writeJSON(w, http.StatusOK, rankedProfileResponse{Rating: profile.Value, RD: profile.RD, Volatility: profile.Volatility, RankedGames: profile.RankedGames, Tier: string(tier), Provisional: domain.RankedIsProvisional(profile), SeasonID: profile.LastSeasonID})
}
func (s *Service) authenticate(w http.ResponseWriter, r *http.Request) (string, bool) {
if s.Sessions == nil {
writeError(w, http.StatusServiceUnavailable, "auth_unavailable")
+38
View File
@@ -151,3 +151,41 @@ func TestAuthenticatedProposalAPIUsesRevisionAndIdempotencyPolicy(t *testing.T)
}
_ = response.Body.Close()
}
func TestRankedProfileAPIReturnsBackendTierAndHidesCasualData(t *testing.T) {
now := time.Unix(1000, 0).UTC()
sessions := domain.NewSessionStore()
session, token, err := sessions.Issue("player-a", time.Hour, now)
if err != nil {
t.Fatal(err)
}
policy, err := domain.NewTierPolicy([]domain.TierBand{{Tier: domain.RankTierBronze, MinRating: 0}, {Tier: domain.RankTierGold, MinRating: 1500}})
if err != nil {
t.Fatal(err)
}
service := &Service{
Sessions: sessions,
RankedProfiles: map[string]domain.RankedProfile{"player-a": {Rating: domain.Rating{Value: 1600, RD: 200, Volatility: 0.06}, RankedGames: 10, LastSeasonID: "season-1"}},
TierPolicy: policy,
Now: func() time.Time { return now },
}
server := httptest.NewServer(service.Handler())
defer server.Close()
req, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/profile/ranked", nil)
req.Header.Set("Authorization", "Bearer "+session.SessionID+":"+token)
response, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
t.Fatalf("ranked profile status = %d", response.StatusCode)
}
var body rankedProfileResponse
if err := json.NewDecoder(response.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if body.Tier != string(domain.RankTierGold) || body.Provisional || body.RankedGames != 10 || body.SeasonID != "season-1" {
t.Fatalf("ranked profile response = %+v", body)
}
}