package redisstate import ( "encoding/json" "fmt" "time" "galaxy/lobby/internal/domain/common" "galaxy/lobby/internal/domain/invite" ) // inviteRecord stores the strict Redis JSON shape used for one invite // record. type inviteRecord struct { InviteID string `json:"invite_id"` GameID string `json:"game_id"` InviterUserID string `json:"inviter_user_id"` InviteeUserID string `json:"invitee_user_id"` RaceName string `json:"race_name,omitempty"` Status invite.Status `json:"status"` CreatedAtMS int64 `json:"created_at_ms"` ExpiresAtMS int64 `json:"expires_at_ms"` DecidedAtMS *int64 `json:"decided_at_ms,omitempty"` } // MarshalInvite encodes record into the strict Redis JSON shape used for // invite records. The record is re-validated before marshalling. func MarshalInvite(record invite.Invite) ([]byte, error) { if err := record.Validate(); err != nil { return nil, fmt.Errorf("marshal redis invite record: %w", err) } stored := inviteRecord{ InviteID: record.InviteID.String(), GameID: record.GameID.String(), InviterUserID: record.InviterUserID, InviteeUserID: record.InviteeUserID, RaceName: record.RaceName, Status: record.Status, CreatedAtMS: record.CreatedAt.UTC().UnixMilli(), ExpiresAtMS: record.ExpiresAt.UTC().UnixMilli(), DecidedAtMS: optionalUnixMilli(record.DecidedAt), } payload, err := json.Marshal(stored) if err != nil { return nil, fmt.Errorf("marshal redis invite record: %w", err) } return payload, nil } // UnmarshalInvite decodes payload from the strict Redis JSON shape used // for invite records. The decoded record is validated before returning. func UnmarshalInvite(payload []byte) (invite.Invite, error) { var stored inviteRecord if err := decodeStrictJSON("decode redis invite record", payload, &stored); err != nil { return invite.Invite{}, err } record := invite.Invite{ InviteID: common.InviteID(stored.InviteID), GameID: common.GameID(stored.GameID), InviterUserID: stored.InviterUserID, InviteeUserID: stored.InviteeUserID, RaceName: stored.RaceName, Status: stored.Status, CreatedAt: time.UnixMilli(stored.CreatedAtMS).UTC(), ExpiresAt: time.UnixMilli(stored.ExpiresAtMS).UTC(), DecidedAt: inflateOptionalTime(stored.DecidedAtMS), } if err := record.Validate(); err != nil { return invite.Invite{}, fmt.Errorf("decode redis invite record: %w", err) } return record, nil }