chrly/internal/db/redis/redis.go

233 lines
5.9 KiB
Go
Raw Normal View History

package redis
import (
2023-12-14 06:45:59 +05:30
"context"
"fmt"
"strings"
2023-12-14 06:45:59 +05:30
"github.com/mediocregopher/radix/v4"
"ely.by/chrly/internal/db"
)
const usernameToProfileKey = "hash:username-to-profile"
const userUuidToUsernameKey = "hash:uuid-to-username"
2020-04-21 04:50:45 +05:30
type Redis struct {
client radix.Client
context context.Context
serializer db.ProfileSerializer
}
func New(ctx context.Context, profileSerializer db.ProfileSerializer, addr string, poolSize int) (*Redis, error) {
2023-12-14 06:45:59 +05:30
client, err := (radix.PoolConfig{Size: poolSize}).New(ctx, "tcp", addr)
if err != nil {
return nil, err
}
return &Redis{
client: client,
context: ctx,
serializer: profileSerializer,
}, nil
}
func (r *Redis) FindProfileByUsername(username string) (*db.Profile, error) {
var profile *db.Profile
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
2023-12-14 06:45:59 +05:30
var err error
profile, err = r.findProfileByUsername(ctx, conn, username)
2023-12-14 06:45:59 +05:30
return err
}))
return profile, err
}
func (r *Redis) findProfileByUsername(ctx context.Context, conn radix.Conn, username string) (*db.Profile, error) {
2023-12-14 06:45:59 +05:30
var encodedResult []byte
err := conn.Do(ctx, radix.Cmd(&encodedResult, "HGET", usernameToProfileKey, usernameHashKey(username)))
2023-12-14 06:45:59 +05:30
if err != nil {
return nil, err
}
if len(encodedResult) == 0 {
return nil, nil
}
return r.serializer.Deserialize(encodedResult)
}
func (r *Redis) FindProfileByUuid(uuid string) (*db.Profile, error) {
var skin *db.Profile
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
2023-12-14 06:45:59 +05:30
var err error
skin, err = r.findProfileByUuid(ctx, conn, uuid)
2023-12-14 06:45:59 +05:30
return err
}))
2023-12-14 06:45:59 +05:30
return skin, err
}
func (r *Redis) findProfileByUuid(ctx context.Context, conn radix.Conn, uuid string) (*db.Profile, error) {
username, err := r.findUsernameHashKeyByUuid(ctx, conn, uuid)
2019-05-06 19:42:37 +05:30
if err != nil {
return nil, err
2019-05-06 19:42:37 +05:30
}
2019-04-25 04:53:10 +05:30
2023-12-14 06:45:59 +05:30
if username == "" {
return nil, nil
}
return r.findProfileByUsername(ctx, conn, username)
2019-04-25 04:53:10 +05:30
}
func (r *Redis) findUsernameHashKeyByUuid(ctx context.Context, conn radix.Conn, uuid string) (string, error) {
var username string
return username, conn.Do(ctx, radix.FlatCmd(&username, "HGET", userUuidToUsernameKey, normalizeUuid(uuid)))
}
func (r *Redis) SaveProfile(profile *db.Profile) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return r.saveProfile(ctx, conn, profile)
2023-12-14 06:45:59 +05:30
}))
}
func (r *Redis) saveProfile(ctx context.Context, conn radix.Conn, profile *db.Profile) error {
newUsernameHashKey := usernameHashKey(profile.Username)
existsUsernameHashKey, err := r.findUsernameHashKeyByUuid(ctx, conn, profile.Uuid)
if err != nil {
return err
}
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
2019-05-06 19:42:37 +05:30
if err != nil {
return err
}
// If user has changed username, then we must delete his old username record
if existsUsernameHashKey != "" && existsUsernameHashKey != newUsernameHashKey {
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", usernameToProfileKey, existsUsernameHashKey))
2023-12-14 06:45:59 +05:30
if err != nil {
return err
}
}
err = conn.Do(ctx, radix.FlatCmd(nil, "HSET", userUuidToUsernameKey, normalizeUuid(profile.Uuid), newUsernameHashKey))
2023-12-14 06:45:59 +05:30
if err != nil {
return err
}
serializedProfile, err := r.serializer.Serialize(profile)
2023-12-14 06:45:59 +05:30
if err != nil {
return err
}
err = conn.Do(ctx, radix.FlatCmd(nil, "HSET", usernameToProfileKey, newUsernameHashKey, serializedProfile))
if err != nil {
return err
}
err = conn.Do(ctx, radix.Cmd(nil, "EXEC"))
2023-12-14 06:45:59 +05:30
if err != nil {
return err
}
return nil
}
func (r *Redis) RemoveProfileByUuid(uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return r.removeProfileByUuid(ctx, conn, uuid)
2023-12-14 06:45:59 +05:30
}))
}
func (r *Redis) removeProfileByUuid(ctx context.Context, conn radix.Conn, uuid string) error {
username, err := r.findUsernameHashKeyByUuid(ctx, conn, uuid)
if err != nil {
2019-05-06 19:42:37 +05:30
return err
}
2023-12-14 06:45:59 +05:30
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
if err != nil {
return err
}
err = conn.Do(ctx, radix.FlatCmd(nil, "HDEL", userUuidToUsernameKey, normalizeUuid(uuid)))
2023-12-14 06:45:59 +05:30
if err != nil {
return err
}
if username != "" {
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", usernameToProfileKey, usernameHashKey(username)))
if err != nil {
return err
}
2023-12-14 06:45:59 +05:30
}
2023-12-14 06:45:59 +05:30
return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
}
func (r *Redis) GetUuidForMojangUsername(username string) (string, string, error) {
2023-12-14 06:45:59 +05:30
var uuid string
foundUsername := username
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
2023-12-14 06:45:59 +05:30
var err error
uuid, foundUsername, err = findMojangUuidByUsername(ctx, conn, username)
2023-12-14 06:45:59 +05:30
return err
}))
return uuid, foundUsername, err
}
func findMojangUuidByUsername(ctx context.Context, conn radix.Conn, username string) (string, string, error) {
key := buildMojangUsernameKey(username)
2023-12-14 06:45:59 +05:30
var result string
err := conn.Do(ctx, radix.Cmd(&result, "GET", key))
2023-12-14 06:45:59 +05:30
if err != nil {
return "", "", err
2023-12-14 06:45:59 +05:30
}
if result == "" {
return "", "", nil
2019-04-25 04:53:10 +05:30
}
2023-12-14 06:45:59 +05:30
parts := strings.Split(result, ":")
return parts[1], parts[0], nil
2019-04-25 04:53:10 +05:30
}
func (r *Redis) StoreMojangUuid(username string, uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
2023-12-14 06:45:59 +05:30
return storeMojangUuid(ctx, conn, username, uuid)
}))
}
2023-12-14 06:45:59 +05:30
func storeMojangUuid(ctx context.Context, conn radix.Conn, username string, uuid string) error {
value := fmt.Sprintf("%s:%s", username, uuid)
err := conn.Do(ctx, radix.FlatCmd(nil, "SET", buildMojangUsernameKey(username), value, "EX", 60*60*24*30))
2023-12-14 06:45:59 +05:30
if err != nil {
return err
2019-05-06 19:42:37 +05:30
}
return nil
2019-04-25 04:53:10 +05:30
}
func (r *Redis) Ping() error {
return r.client.Do(r.context, radix.Cmd(nil, "PING"))
}
func normalizeUuid(uuid string) string {
return strings.ToLower(strings.ReplaceAll(uuid, "-", ""))
}
func usernameHashKey(username string) string {
return strings.ToLower(username)
}
func buildMojangUsernameKey(username string) string {
return fmt.Sprintf("mojang:uuid:%s", usernameHashKey(username))
}