mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
a1ae36a54c
Same discovery pattern as ResultSubmitter/SessionIssuer, one level deeper: api.Service.rankedProfile and .profile both only ever read from an in-memory RankedProfiles map with no durable-store equivalent at all -- not "adapter exists but unwired" this time, there was no adapter. Every real request to GET /v1/profile/ranked or /api/v1/profile always 404'd regardless of a player's actual rating. Add RankedProfileProvider (an interface, not a struct-literal adapter this time) and store.PostgresRankedProfiles reading the ratings table; Service.rankedProfileFor prefers it when set and falls back to the map otherwise, so every existing test/direct Service literal keeps compiling and passing unchanged. A missing ratings row maps to the exact same (zero value, false, nil) the map lookup already produced, preserving existing not-found semantics rather than reinterpreting them. LastSeasonID/SeasonHistory are deliberately left unset -- the ratings table has no season pointer, and reconstructing history needs its own query and display semantics, not bundled in here speculatively. Wired into both cmd/control-plane and cmd/testkit-api. Verified against real PostgreSQL via curl: a fresh identity's ranked profile correctly 404s through the real adapter (same behavior as before, now for a real reason instead of an empty map).
44 lines
1.6 KiB
Go
44 lines
1.6 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/cosmic-clash/cosmic-clash/server/domain"
|
|
)
|
|
|
|
const RankedProfileSelectSQL = `SELECT rating, deviation, volatility, ranked_games, updated_at
|
|
FROM ratings
|
|
WHERE player_id = $1`
|
|
|
|
// PostgresRankedProfiles reads the durable rating row api.Service's
|
|
// RankedProfileProvider needs. A missing row means "this player has no
|
|
// ranked profile yet" (never queued ranked, or their identity predates any
|
|
// result) -- that's a real, expected state, not an error, and is reported
|
|
// the same way the in-memory RankedProfiles map api.Service still falls
|
|
// back to already did: (zero value, false, nil).
|
|
//
|
|
// LastSeasonID and SeasonHistory are deliberately left at their zero values.
|
|
// The ratings table has no "current season" column, and reconstructing
|
|
// season history means a second query against ranked_season_rollovers with
|
|
// its own display semantics to settle -- a real, separate piece of work,
|
|
// not bundled into this read path speculatively.
|
|
type PostgresRankedProfiles struct{ DB *sql.DB }
|
|
|
|
func (p PostgresRankedProfiles) Get(ctx context.Context, playerID string) (domain.RankedProfile, bool, error) {
|
|
if p.DB == nil || playerID == "" {
|
|
return domain.RankedProfile{}, false, fmt.Errorf("invalid ranked profile lookup")
|
|
}
|
|
var profile domain.RankedProfile
|
|
err := p.DB.QueryRowContext(ctx, RankedProfileSelectSQL, playerID).
|
|
Scan(&profile.Value, &profile.RD, &profile.Volatility, &profile.RankedGames, &profile.LastRatedAt)
|
|
if err == sql.ErrNoRows {
|
|
return domain.RankedProfile{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return domain.RankedProfile{}, false, err
|
|
}
|
|
return profile, true, nil
|
|
}
|