package redisstate import ( "encoding/json" "fmt" "time" "galaxy/lobby/internal/domain/common" "galaxy/lobby/internal/domain/membership" ) // membershipRecord stores the strict Redis JSON shape used for one // membership record. type membershipRecord struct { MembershipID string `json:"membership_id"` GameID string `json:"game_id"` UserID string `json:"user_id"` RaceName string `json:"race_name"` CanonicalKey string `json:"canonical_key"` Status membership.Status `json:"status"` JoinedAtMS int64 `json:"joined_at_ms"` RemovedAtMS *int64 `json:"removed_at_ms,omitempty"` } // MarshalMembership encodes record into the strict Redis JSON shape used // for membership records. The record is re-validated before marshalling. func MarshalMembership(record membership.Membership) ([]byte, error) { if err := record.Validate(); err != nil { return nil, fmt.Errorf("marshal redis membership record: %w", err) } stored := membershipRecord{ MembershipID: record.MembershipID.String(), GameID: record.GameID.String(), UserID: record.UserID, RaceName: record.RaceName, CanonicalKey: record.CanonicalKey, Status: record.Status, JoinedAtMS: record.JoinedAt.UTC().UnixMilli(), RemovedAtMS: optionalUnixMilli(record.RemovedAt), } payload, err := json.Marshal(stored) if err != nil { return nil, fmt.Errorf("marshal redis membership record: %w", err) } return payload, nil } // UnmarshalMembership decodes payload from the strict Redis JSON shape // used for membership records. The decoded record is validated before // returning. func UnmarshalMembership(payload []byte) (membership.Membership, error) { var stored membershipRecord if err := decodeStrictJSON("decode redis membership record", payload, &stored); err != nil { return membership.Membership{}, err } record := membership.Membership{ MembershipID: common.MembershipID(stored.MembershipID), GameID: common.GameID(stored.GameID), UserID: stored.UserID, RaceName: stored.RaceName, CanonicalKey: stored.CanonicalKey, Status: stored.Status, JoinedAt: time.UnixMilli(stored.JoinedAtMS).UTC(), RemovedAt: inflateOptionalTime(stored.RemovedAtMS), } if err := record.Validate(); err != nil { return membership.Membership{}, fmt.Errorf("decode redis membership record: %w", err) } return record, nil }