package redis import ( "context" "encoding/json" "fmt" "time" "github.com/redis/go-redis/v9" ) type Config struct { Addr string Password string DB int } type Client struct { rdb *redis.Client } func NewClient(cfg Config) (*Client, error) { rdb := redis.NewClient(&redis.Options{ Addr: cfg.Addr, Password: cfg.Password, DB: cfg.DB, }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := rdb.Ping(ctx).Err(); err != nil { return nil, fmt.Errorf("failed to connect to redis: %w", err) } return &Client{rdb: rdb}, nil } func (c *Client) Close() error { return c.rdb.Close() } func (c *Client) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal value: %w", err) } return c.rdb.Set(ctx, key, data, ttl).Err() } func (c *Client) Get(ctx context.Context, key string, dest interface{}) error { data, err := c.rdb.Get(ctx, key).Bytes() if err != nil { return err } if err := json.Unmarshal(data, dest); err != nil { return fmt.Errorf("failed to unmarshal value: %w", err) } return nil } func (c *Client) Exists(ctx context.Context, key string) (bool, error) { result, err := c.rdb.Exists(ctx, key).Result() if err != nil { return false, err } return result > 0, nil } func (c *Client) Delete(ctx context.Context, keys ...string) error { return c.rdb.Del(ctx, keys...).Err() } func (c *Client) SetTTL(ctx context.Context, key string, ttl time.Duration) error { return c.rdb.Expire(ctx, key, ttl).Err() } func (c *Client) GetTTL(ctx context.Context, key string) (time.Duration, error) { return c.rdb.TTL(ctx, key).Result() } func (c *Client) LPush(ctx context.Context, key string, values ...interface{}) error { data := make([]interface{}, len(values)) for i, v := range values { b, err := json.Marshal(v) if err != nil { return fmt.Errorf("failed to marshal list value: %w", err) } data[i] = b } return c.rdb.LPush(ctx, key, data...).Err() } func (c *Client) RPush(ctx context.Context, key string, values ...interface{}) error { data := make([]interface{}, len(values)) for i, v := range values { b, err := json.Marshal(v) if err != nil { return fmt.Errorf("failed to marshal list value: %w", err) } data[i] = b } return c.rdb.RPush(ctx, key, data...).Err() } func (c *Client) LRange(ctx context.Context, key string, start, stop int64, dest interface{}) error { results, err := c.rdb.LRange(ctx, key, start, stop).Result() if err != nil { return err } data, err := json.Marshal(results) if err != nil { return fmt.Errorf("failed to marshal results: %w", err) } return json.Unmarshal(data, dest) } func (c *Client) LLen(ctx context.Context, key string) (int64, error) { return c.rdb.LLen(ctx, key).Result() } func (c *Client) LPop(ctx context.Context, key string) (string, error) { return c.rdb.LPop(ctx, key).Result() } func (c *Client) LTrim(ctx context.Context, key string, start, stop int64) error { return c.rdb.LTrim(ctx, key, start, stop).Err() } func (c *Client) LSet(ctx context.Context, key string, index int64, value interface{}) error { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal value: %w", err) } return c.rdb.LSet(ctx, key, index, data).Err() } func (c *Client) SAdd(ctx context.Context, key string, members ...interface{}) error { data := make([]interface{}, len(members)) for i, m := range members { b, err := json.Marshal(m) if err != nil { return fmt.Errorf("failed to marshal set member: %w", err) } data[i] = b } return c.rdb.SAdd(ctx, key, data...).Err() } func (c *Client) SMembers(ctx context.Context, key string) ([]string, error) { return c.rdb.SMembers(ctx, key).Result() } func (c *Client) SRem(ctx context.Context, key string, members ...interface{}) error { data := make([]interface{}, len(members)) for i, m := range members { b, err := json.Marshal(m) if err != nil { return fmt.Errorf("failed to marshal set member: %w", err) } data[i] = b } return c.rdb.SRem(ctx, key, data...).Err() } func (c *Client) HSet(ctx context.Context, key string, field string, value interface{}) error { data, err := json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal hash value: %w", err) } return c.rdb.HSet(ctx, key, field, data).Err() } func (c *Client) HGet(ctx context.Context, key, field string, dest interface{}) error { data, err := c.rdb.HGet(ctx, key, field).Bytes() if err != nil { return err } return json.Unmarshal(data, dest) } func (c *Client) HDel(ctx context.Context, key string, fields ...string) error { return c.rdb.HDel(ctx, key, fields...).Err() } func (c *Client) HGetAll(ctx context.Context, key string) (map[string]string, error) { return c.rdb.HGetAll(ctx, key).Result() } func (c *Client) HExists(ctx context.Context, key, field string) (bool, error) { return c.rdb.HExists(ctx, key, field).Result() } func (c *Client) Keys(ctx context.Context, pattern string) ([]string, error) { return c.rdb.Keys(ctx, pattern).Result() } func (c *Client) ScanKeys(ctx context.Context, pattern string, callback func(key string) error) error { var cursor uint64 for { var keys []string var err error keys, cursor, err = c.rdb.Scan(ctx, cursor, pattern, 100).Result() if err != nil { return err } for _, key := range keys { if err := callback(key); err != nil { return err } } if cursor == 0 { break } } return nil } const ( RoomTTL = 30 * time.Minute PlayerTTL = 10 * time.Minute ) func RoomKey(roomID string) string { return "room:" + roomID } func PlayerKey(playerID string) string { return "player:" + playerID } func RoomPlayersKey(roomID string) string { return "room:" + roomID + ":players" } func RoomDeckKey(roomID string) string { return "room:" + roomID + ":deck" } func RoomDiscardKey(roomID string) string { return "room:" + roomID + ":discard" } func RoomTTLKey(roomID string) string { return "room:" + roomID + ":ttl" } func PlayerTTLKey(playerID string) string { return "player:" + playerID + ":ttl" } func CaptchaKey(captchaID string) string { return "captcha:" + captchaID } func UserKey(username string) string { return "user:" + username } func UserRoomKey(userID string) string { return "user:" + userID + ":room" } func SessionKey(token string) string { return "session:" + token } func UserIDToUsernameKey(userID string) string { return "user_id:" + userID + ":username" }