Stage 15: dual Telegram bots & language-gated variants
Tests · Go / test (push) Successful in 9s
Tests · Integration / integration (push) Successful in 10s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 8s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Tests · Go / test (push) Successful in 9s
Tests · Integration / integration (push) Successful in 10s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 8s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Service-agnostic refinement of the owner's idea: the sign-in service returns a set of supported game languages with the user identity, and the lobby gates the New Game variant choice by it (en -> English; ru -> Russian + Эрудит). - Connector hosts two bots in one container (one per service language, each its own token + game channel; the same telegram_id spans both). ValidateInitData tries each token and returns the validating bot's service_language + supported_languages. Per-language config (TELEGRAM_BOT_TOKEN_EN/_RU, channels). - supported_languages rides the Session (fbs, session-scoped, not persisted); the UI offers only the matching variants on New Game — gating only the START of a new game (auto-match + friend invite), not accept/open/play; backend does not enforce. - service_language persisted (accounts.service_language, migration 00010, written every login, last-login-wins) and routes the user-facing Notify push back through the right bot (push-target coalesces with preferred_language). - Admin SendToUser/SendToGameChannel gain an operator-chosen language selector in the console (unrelated to ValidateInitData). - Non-Telegram logins carry the gateway default set (GATEWAY_DEFAULT_SUPPORTED_LANGUAGES, all variants). Wire (committed regen): ValidateInitDataResponse +service_language +supported_languages; Session +supported_languages; SendToUser/SendToGameChannel +language. Docs (ARCHITECTURE/FUNCTIONAL/_ru/READMEs) + PLAN updated; stage marked done.
This commit is contained in:
@@ -56,6 +56,12 @@ type Account struct {
|
||||
HintBalance int
|
||||
BlockChat bool
|
||||
BlockFriendRequests bool
|
||||
// ServiceLanguage is the language tag (en/ru) of the bot the account last
|
||||
// authenticated through (its last Telegram ValidateInitData); it routes the
|
||||
// account's out-of-app push back through the right bot. Empty when the account
|
||||
// has never signed in through a tagged bot. Distinct from PreferredLanguage (the
|
||||
// interface language) and from a game's variant language.
|
||||
ServiceLanguage string
|
||||
// IsGuest marks an ephemeral guest account: a durable row with no identity,
|
||||
// excluded from statistics, friends and history.
|
||||
IsGuest bool
|
||||
@@ -374,16 +380,41 @@ func (s *Store) SpendHint(ctx context.Context, id uuid.UUID) (bool, error) {
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
// SetServiceLanguage records the service language (en/ru) of the bot a Telegram
|
||||
// user authenticated through. It is called on every Telegram login — new and
|
||||
// existing accounts — so it tracks the bot the user last came through (last-login-
|
||||
// wins), and the out-of-app push routes by it. It is a no-op for an empty language
|
||||
// (a non-Telegram login carries none) and does not bump updated_at (an infra
|
||||
// routing field, not a user profile edit).
|
||||
func (s *Store) SetServiceLanguage(ctx context.Context, id uuid.UUID, language string) error {
|
||||
if language == "" {
|
||||
return nil
|
||||
}
|
||||
stmt := table.Accounts.
|
||||
UPDATE(table.Accounts.ServiceLanguage).
|
||||
SET(postgres.String(language)).
|
||||
WHERE(table.Accounts.AccountID.EQ(postgres.UUID(id)))
|
||||
if _, err := stmt.ExecContext(ctx, s.db); err != nil {
|
||||
return fmt.Errorf("account: set service language %s: %w", id, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// modelToAccount projects a generated model row into the public Account struct.
|
||||
func modelToAccount(row model.Accounts) Account {
|
||||
var mergedInto uuid.UUID
|
||||
if row.MergedInto != nil {
|
||||
mergedInto = *row.MergedInto
|
||||
}
|
||||
var serviceLanguage string
|
||||
if row.ServiceLanguage != nil {
|
||||
serviceLanguage = *row.ServiceLanguage
|
||||
}
|
||||
return Account{
|
||||
ID: row.AccountID,
|
||||
DisplayName: row.DisplayName,
|
||||
PreferredLanguage: row.PreferredLanguage,
|
||||
ServiceLanguage: serviceLanguage,
|
||||
TimeZone: row.TimeZone,
|
||||
AwayStart: row.AwayStart,
|
||||
AwayEnd: row.AwayEnd,
|
||||
|
||||
Reference in New Issue
Block a user