feat: use postgres
This commit is contained in:
@@ -2,178 +2,25 @@ package redisstate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"galaxy/lobby/internal/domain/common"
|
||||
"galaxy/lobby/internal/domain/game"
|
||||
"galaxy/lobby/internal/domain/racename"
|
||||
)
|
||||
|
||||
// defaultPrefix is the mandatory `lobby:` namespace prefix shared by every
|
||||
// Game Lobby Redis key.
|
||||
const defaultPrefix = "lobby:"
|
||||
|
||||
// GameRecordTTL is the Redis retention applied to game records. The
|
||||
// value is zero (no expiry); a future stage will revisit this
|
||||
// choice when the platform locks in archival/GDPR policy.
|
||||
const GameRecordTTL time.Duration = 0
|
||||
|
||||
// ApplicationRecordTTL is the Redis retention applied to application
|
||||
// records. uses zero (no expiry) to match game records; the
|
||||
// archival policy will be revisited when the platform locks it in.
|
||||
const ApplicationRecordTTL time.Duration = 0
|
||||
|
||||
// InviteRecordTTL is the Redis retention applied to invite records.
|
||||
// uses zero (no expiry); the `expires_at` field is a business
|
||||
// deadline enforced by the service layer, not a Redis TTL.
|
||||
const InviteRecordTTL time.Duration = 0
|
||||
|
||||
// MembershipRecordTTL is the Redis retention applied to membership
|
||||
// records. uses zero (no expiry) to match the other participant
|
||||
// entities.
|
||||
const MembershipRecordTTL time.Duration = 0
|
||||
|
||||
// Keyspace builds the frozen Game Lobby Redis keys. All dynamic key
|
||||
// segments are encoded with base64url so raw key structure does not
|
||||
// depend on user-provided or caller-provided characters.
|
||||
// Keyspace builds the Game Lobby Redis keys that survive the PG_PLAN.md
|
||||
// §6A and §6B migrations: per-game ephemeral runtime aggregates,
|
||||
// capability-evaluation guards, gap activation timestamps, and stream
|
||||
// consumer offsets. The four core enrollment entities (game, application,
|
||||
// invite, membership) and the Race Name Directory live in PostgreSQL —
|
||||
// their previous keyspace methods are gone.
|
||||
//
|
||||
// All dynamic key segments are encoded with base64url so raw key structure
|
||||
// does not depend on user-provided or caller-provided characters.
|
||||
type Keyspace struct{}
|
||||
|
||||
// Game returns the primary Redis key for one game record.
|
||||
func (Keyspace) Game(gameID common.GameID) string {
|
||||
return defaultPrefix + "games:" + encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// GamesByStatus returns the sorted-set key that stores game identifiers
|
||||
// indexed by their current status.
|
||||
func (Keyspace) GamesByStatus(status game.Status) string {
|
||||
return defaultPrefix + "games_by_status:" + encodeKeyComponent(string(status))
|
||||
}
|
||||
|
||||
// GamesByOwner returns the set key that stores game identifiers owned
|
||||
// by one user. The set is maintained for private games whose
|
||||
// OwnerUserID is non-empty (public games are admin-owned and carry an
|
||||
// empty OwnerUserID, so they never enter the index).
|
||||
func (Keyspace) GamesByOwner(userID string) string {
|
||||
return defaultPrefix + "games_by_owner:" + encodeKeyComponent(userID)
|
||||
}
|
||||
|
||||
// Application returns the primary Redis key for one application record.
|
||||
func (Keyspace) Application(applicationID common.ApplicationID) string {
|
||||
return defaultPrefix + "applications:" + encodeKeyComponent(applicationID.String())
|
||||
}
|
||||
|
||||
// ApplicationsByGame returns the set key that stores application
|
||||
// identifiers attached to one game.
|
||||
func (Keyspace) ApplicationsByGame(gameID common.GameID) string {
|
||||
return defaultPrefix + "game_applications:" + encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// ApplicationsByUser returns the set key that stores application
|
||||
// identifiers submitted by one applicant.
|
||||
func (Keyspace) ApplicationsByUser(applicantUserID string) string {
|
||||
return defaultPrefix + "user_applications:" + encodeKeyComponent(applicantUserID)
|
||||
}
|
||||
|
||||
// UserGameApplication returns the lookup key that stores the single
|
||||
// non-rejected application identifier for one (user, game) pair. Presence
|
||||
// of this key blocks a second submitted/approved application for the
|
||||
// same user and game.
|
||||
func (Keyspace) UserGameApplication(applicantUserID string, gameID common.GameID) string {
|
||||
return defaultPrefix + "user_game_application:" +
|
||||
encodeKeyComponent(applicantUserID) + ":" +
|
||||
encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// Invite returns the primary Redis key for one invite record.
|
||||
func (Keyspace) Invite(inviteID common.InviteID) string {
|
||||
return defaultPrefix + "invites:" + encodeKeyComponent(inviteID.String())
|
||||
}
|
||||
|
||||
// InvitesByGame returns the set key that stores invite identifiers
|
||||
// attached to one game.
|
||||
func (Keyspace) InvitesByGame(gameID common.GameID) string {
|
||||
return defaultPrefix + "game_invites:" + encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// InvitesByUser returns the set key that stores invite identifiers
|
||||
// addressed to one invitee.
|
||||
func (Keyspace) InvitesByUser(inviteeUserID string) string {
|
||||
return defaultPrefix + "user_invites:" + encodeKeyComponent(inviteeUserID)
|
||||
}
|
||||
|
||||
// InvitesByInviter returns the set key that stores invite identifiers
|
||||
// created by one inviter (private-game owner). The set retains
|
||||
// invite_ids regardless of subsequent status transitions; callers
|
||||
// filter by status when needed.
|
||||
func (Keyspace) InvitesByInviter(inviterUserID string) string {
|
||||
return defaultPrefix + "user_inviter_invites:" + encodeKeyComponent(inviterUserID)
|
||||
}
|
||||
|
||||
// Membership returns the primary Redis key for one membership record.
|
||||
func (Keyspace) Membership(membershipID common.MembershipID) string {
|
||||
return defaultPrefix + "memberships:" + encodeKeyComponent(membershipID.String())
|
||||
}
|
||||
|
||||
// MembershipsByGame returns the set key that stores membership
|
||||
// identifiers attached to one game.
|
||||
func (Keyspace) MembershipsByGame(gameID common.GameID) string {
|
||||
return defaultPrefix + "game_memberships:" + encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// MembershipsByUser returns the set key that stores membership
|
||||
// identifiers held by one user.
|
||||
func (Keyspace) MembershipsByUser(userID string) string {
|
||||
return defaultPrefix + "user_memberships:" + encodeKeyComponent(userID)
|
||||
}
|
||||
|
||||
// RegisteredRaceName returns the Redis key that stores the registered
|
||||
// race name bound to canonical.
|
||||
func (Keyspace) RegisteredRaceName(canonical racename.CanonicalKey) string {
|
||||
return defaultPrefix + "race_names:registered:" + encodeKeyComponent(canonical.String())
|
||||
}
|
||||
|
||||
// UserRegisteredRaceNames returns the set key that stores canonical keys
|
||||
// of every registered race name owned by userID.
|
||||
func (Keyspace) UserRegisteredRaceNames(userID string) string {
|
||||
return defaultPrefix + "race_names:user_registered:" + encodeKeyComponent(userID)
|
||||
}
|
||||
|
||||
// RaceNameReservation returns the Redis key that stores the per-game race
|
||||
// name reservation bound to (gameID, canonical).
|
||||
func (Keyspace) RaceNameReservation(gameID common.GameID, canonical racename.CanonicalKey) string {
|
||||
return defaultPrefix + "race_names:reservations:" +
|
||||
encodeKeyComponent(gameID.String()) + ":" +
|
||||
encodeKeyComponent(canonical.String())
|
||||
}
|
||||
|
||||
// UserRaceNameReservations returns the set key that stores
|
||||
// `<encodedGameID>:<encodedCanonical>` tuples of every active reservation
|
||||
// (including pending_registration) owned by userID.
|
||||
func (Keyspace) UserRaceNameReservations(userID string) string {
|
||||
return defaultPrefix + "race_names:user_reservations:" + encodeKeyComponent(userID)
|
||||
}
|
||||
|
||||
// RaceNameCanonicalLookup returns the Redis key that stores the eager
|
||||
// canonical-lookup cache entry for canonical. The cache surfaces the
|
||||
// strongest existing binding (registered > pending_registration >
|
||||
// reservation) so Check remains an O(1) read.
|
||||
func (Keyspace) RaceNameCanonicalLookup(canonical racename.CanonicalKey) string {
|
||||
return defaultPrefix + "race_names:canonical_lookup:" + encodeKeyComponent(canonical.String())
|
||||
}
|
||||
|
||||
// PendingRaceNameIndex returns the singleton sorted-set key that indexes
|
||||
// pending registrations by eligible_until_ms for the expiration worker.
|
||||
func (Keyspace) PendingRaceNameIndex() string {
|
||||
return defaultPrefix + "race_names:pending_index"
|
||||
}
|
||||
|
||||
// RaceNameReservationMember returns the canonical member representation
|
||||
// stored inside UserRaceNameReservations and PendingRaceNameIndex for
|
||||
// (gameID, canonical).
|
||||
func (Keyspace) RaceNameReservationMember(gameID common.GameID, canonical racename.CanonicalKey) string {
|
||||
return encodeKeyComponent(gameID.String()) + ":" + encodeKeyComponent(canonical.String())
|
||||
}
|
||||
|
||||
// GapActivatedAt returns the Redis key that stores the gap-window
|
||||
// activation timestamp for one game.
|
||||
func (Keyspace) GapActivatedAt(gameID common.GameID) string {
|
||||
@@ -216,12 +63,6 @@ func (Keyspace) CapabilityEvaluationGuard(gameID common.GameID) string {
|
||||
encodeKeyComponent(gameID.String())
|
||||
}
|
||||
|
||||
// CreatedAtScore returns the frozen sorted-set score representation for
|
||||
// game creation timestamps stored in the status index.
|
||||
func CreatedAtScore(createdAt time.Time) float64 {
|
||||
return float64(createdAt.UTC().UnixMilli())
|
||||
}
|
||||
|
||||
func encodeKeyComponent(value string) string {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user