feat(profile): advertise per-variant dictionary versions for offline preload
The Profile now carries dict_versions (game variant -> current dictionary version), populated from the dictionary registry at the profileResponse choke point, so an installed PWA can preload the matching dawg per enabled variant off the existing cold-start profile request instead of adding a round-trip for a rare feature. Wire path: FBS DictVersion table + Profile.dict_versions (additive, backward-compatible trailing field) -> backend dto/registry -> gateway ProfileResp + FBS encoder -> client codec decode into a per-variant map on model.Profile. Empty in a degenerate no-dictionary deployment; the mock serves v1.3.0 for all three variants. Codec decode covered by a bite-tested round-trip unit test.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"scrabble/backend/internal/account"
|
||||
"scrabble/backend/internal/ads"
|
||||
"scrabble/backend/internal/engine"
|
||||
"scrabble/backend/internal/notify"
|
||||
)
|
||||
|
||||
@@ -56,9 +57,34 @@ func (s *Server) profileResponse(ctx context.Context, acc account.Account) profi
|
||||
r := profileResponseFor(acc)
|
||||
r.Banner = s.bannerFor(ctx, acc)
|
||||
s.fillLinkedIdentities(ctx, &r, acc.ID)
|
||||
r.DictVersions = s.currentDictVersions()
|
||||
return r
|
||||
}
|
||||
|
||||
// currentDictVersions reports the current dictionary version of every game variant with a
|
||||
// resident dictionary, as engine.Variant stable labels. It backs profileResponse.DictVersions
|
||||
// so an offline-capable client preloads the matching dawg per variant off the cold-start
|
||||
// profile. A variant without a loaded dictionary is omitted (Registry.Latest reports
|
||||
// ErrUnknownVariant); the returned slice is nil only when no variant is resident.
|
||||
func (s *Server) currentDictVersions() []dictVersion {
|
||||
if s.registry == nil {
|
||||
return nil // no dictionary registry wired (e.g. a minimal test server): advertise none
|
||||
}
|
||||
variants := []engine.Variant{engine.VariantEnglish, engine.VariantRussianScrabble, engine.VariantErudit}
|
||||
out := make([]dictVersion, 0, len(variants))
|
||||
for _, v := range variants {
|
||||
version, _, err := s.registry.Latest(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, dictVersion{Variant: v.String(), Version: version})
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// fillLinkedIdentities sets the profile's confirmed email address and platform-linked
|
||||
// flags from the account's identities, so the client offers the right link / unlink /
|
||||
// change-email controls. A read failure leaves them zero (no controls), logged as a
|
||||
|
||||
@@ -63,6 +63,20 @@ type profileResponse struct {
|
||||
Email string `json:"email"`
|
||||
TelegramLinked bool `json:"telegram_linked"`
|
||||
VkLinked bool `json:"vk_linked"`
|
||||
// DictVersions lists the current dictionary version per game variant (engine.Variant
|
||||
// stable labels). An installed PWA preparing for offline play reads it to preload the
|
||||
// right dawg per enabled variant off this cold-start response, without an extra request.
|
||||
// Filled outside the pure projection (it reads the dictionary registry), so it is empty
|
||||
// for callers that build the DTO without a Server. See Server.profileResponse.
|
||||
DictVersions []dictVersion `json:"dict_versions,omitempty"`
|
||||
}
|
||||
|
||||
// dictVersion pairs a game variant's stable label (engine.Variant.String) with its current
|
||||
// dictionary version. profileResponse.DictVersions carries the set so an offline-capable
|
||||
// client preloads the matching dawg per variant.
|
||||
type dictVersion struct {
|
||||
Variant string `json:"variant"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// tileDTO is one placed (or to-place) tile.
|
||||
|
||||
Reference in New Issue
Block a user