package userstore import ( "context" "errors" "galaxy/user/internal/adapters/redisstate" "galaxy/user/internal/domain/account" "galaxy/user/internal/domain/common" "galaxy/user/internal/domain/entitlement" "galaxy/user/internal/domain/policy" "galaxy/user/internal/ports" "github.com/redis/go-redis/v9" ) var knownSanctionCodes = []policy.SanctionCode{ policy.SanctionCodeLoginBlock, policy.SanctionCodePrivateGameCreateBlock, policy.SanctionCodePrivateGameManageBlock, policy.SanctionCodeGameJoinBlock, policy.SanctionCodeProfileUpdateBlock, } var knownLimitCodes = []policy.LimitCode{ policy.LimitCodeMaxOwnedPrivateGames, policy.LimitCodeMaxPendingPublicApplications, policy.LimitCodeMaxActiveGameMemberships, } var knownEligibilityMarkers = []policy.EligibilityMarker{ policy.EligibilityMarkerCanLogin, policy.EligibilityMarkerCanCreatePrivateGame, policy.EligibilityMarkerCanManagePrivateGame, policy.EligibilityMarkerCanJoinGame, policy.EligibilityMarkerCanUpdateProfile, } func (store *Store) addCreatedAtIndex( pipe redis.Pipeliner, ctx context.Context, record account.UserAccount, ) { pipe.ZAdd(ctx, store.keyspace.CreatedAtIndex(), redis.Z{ Score: redisstate.CreatedAtScore(record.CreatedAt), Member: record.UserID.String(), }) } func (store *Store) syncDeclaredCountryIndex( pipe redis.Pipeliner, ctx context.Context, previous account.UserAccount, current account.UserAccount, ) { if !previous.DeclaredCountry.IsZero() { pipe.SRem(ctx, store.keyspace.DeclaredCountryIndex(previous.DeclaredCountry), current.UserID.String()) } if !current.DeclaredCountry.IsZero() { pipe.SAdd(ctx, store.keyspace.DeclaredCountryIndex(current.DeclaredCountry), current.UserID.String()) } } func (store *Store) syncEntitlementIndexes( pipe redis.Pipeliner, ctx context.Context, snapshot entitlement.CurrentSnapshot, ) { pipe.SRem(ctx, store.keyspace.PaidStateIndex(entitlement.PaidStateFree), snapshot.UserID.String()) pipe.SRem(ctx, store.keyspace.PaidStateIndex(entitlement.PaidStatePaid), snapshot.UserID.String()) pipe.SAdd(ctx, store.keyspace.PaidStateIndex(paidStateFromSnapshot(snapshot)), snapshot.UserID.String()) pipe.ZRem(ctx, store.keyspace.FinitePaidExpiryIndex(), snapshot.UserID.String()) if snapshot.HasFiniteExpiry() { pipe.ZAdd(ctx, store.keyspace.FinitePaidExpiryIndex(), redis.Z{ Score: redisstate.ExpiryScore(*snapshot.EndsAt), Member: snapshot.UserID.String(), }) } } func (store *Store) syncActiveSanctionCodeIndexes( pipe redis.Pipeliner, ctx context.Context, userID common.UserID, activeCodes map[policy.SanctionCode]struct{}, ) { for _, code := range knownSanctionCodes { pipe.SRem(ctx, store.keyspace.ActiveSanctionCodeIndex(code), userID.String()) if _, ok := activeCodes[code]; ok { pipe.SAdd(ctx, store.keyspace.ActiveSanctionCodeIndex(code), userID.String()) } } } func (store *Store) syncActiveLimitCodeIndexes( pipe redis.Pipeliner, ctx context.Context, userID common.UserID, activeCodes map[policy.LimitCode]struct{}, ) { for _, code := range knownLimitCodes { pipe.SRem(ctx, store.keyspace.ActiveLimitCodeIndex(code), userID.String()) if _, ok := activeCodes[code]; ok { pipe.SAdd(ctx, store.keyspace.ActiveLimitCodeIndex(code), userID.String()) } } } func (store *Store) syncEligibilityMarkerIndexes( pipe redis.Pipeliner, ctx context.Context, userID common.UserID, isPaid bool, activeSanctionCodes map[policy.SanctionCode]struct{}, ) { values := deriveEligibilityMarkerValues(isPaid, activeSanctionCodes) for _, marker := range knownEligibilityMarkers { pipe.SRem(ctx, store.keyspace.EligibilityMarkerIndex(marker, true), userID.String()) pipe.SRem(ctx, store.keyspace.EligibilityMarkerIndex(marker, false), userID.String()) pipe.SAdd(ctx, store.keyspace.EligibilityMarkerIndex(marker, values[marker]), userID.String()) } } func (store *Store) loadActiveSanctionCodeSet( ctx context.Context, getter bytesGetter, userID common.UserID, ) (map[policy.SanctionCode]struct{}, error) { activeCodes := make(map[policy.SanctionCode]struct{}, len(knownSanctionCodes)) for _, code := range knownSanctionCodes { _, err := store.loadActiveSanctionRecordID(ctx, getter, store.keyspace.ActiveSanction(userID, code)) switch { case err == nil: activeCodes[code] = struct{}{} case errors.Is(err, ports.ErrNotFound): continue default: return nil, err } } return activeCodes, nil } func (store *Store) loadActiveLimitCodeSet( ctx context.Context, getter bytesGetter, userID common.UserID, ) (map[policy.LimitCode]struct{}, error) { activeCodes := make(map[policy.LimitCode]struct{}, len(knownLimitCodes)) for _, code := range knownLimitCodes { _, err := store.loadActiveLimitRecordID(ctx, getter, store.keyspace.ActiveLimit(userID, code)) switch { case err == nil: activeCodes[code] = struct{}{} case errors.Is(err, ports.ErrNotFound): continue default: return nil, err } } return activeCodes, nil } func (store *Store) activeSanctionWatchKeys(userID common.UserID) []string { keys := make([]string, 0, len(knownSanctionCodes)) for _, code := range knownSanctionCodes { keys = append(keys, store.keyspace.ActiveSanction(userID, code)) } return keys } func (store *Store) activeLimitWatchKeys(userID common.UserID) []string { keys := make([]string, 0, len(knownLimitCodes)) for _, code := range knownLimitCodes { keys = append(keys, store.keyspace.ActiveLimit(userID, code)) } return keys } func deriveEligibilityMarkerValues( isPaid bool, activeSanctionCodes map[policy.SanctionCode]struct{}, ) map[policy.EligibilityMarker]bool { _, loginBlocked := activeSanctionCodes[policy.SanctionCodeLoginBlock] _, createBlocked := activeSanctionCodes[policy.SanctionCodePrivateGameCreateBlock] _, manageBlocked := activeSanctionCodes[policy.SanctionCodePrivateGameManageBlock] _, joinBlocked := activeSanctionCodes[policy.SanctionCodeGameJoinBlock] _, profileBlocked := activeSanctionCodes[policy.SanctionCodeProfileUpdateBlock] canLogin := !loginBlocked return map[policy.EligibilityMarker]bool{ policy.EligibilityMarkerCanLogin: canLogin, policy.EligibilityMarkerCanCreatePrivateGame: canLogin && isPaid && !createBlocked, policy.EligibilityMarkerCanManagePrivateGame: canLogin && isPaid && !manageBlocked, policy.EligibilityMarkerCanJoinGame: canLogin && !joinBlocked, policy.EligibilityMarkerCanUpdateProfile: canLogin && !profileBlocked, } } func paidStateFromSnapshot(snapshot entitlement.CurrentSnapshot) entitlement.PaidState { if snapshot.IsPaid { return entitlement.PaidStatePaid } return entitlement.PaidStateFree }