using System.Text.Json; using StackExchange.Redis; namespace Meezi.API.Services; public record RefreshTokenPayload( string UserId, string CafeId, string Role, string PlanTier, string Language, string Actor = Meezi.Core.Constants.MeeziActorKinds.Merchant, string? ActiveBranchId = null); public interface IRefreshTokenStore { Task StoreAsync(string refreshToken, RefreshTokenPayload payload, TimeSpan ttl, CancellationToken cancellationToken = default); Task GetAsync(string refreshToken, CancellationToken cancellationToken = default); Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default); } public class RedisRefreshTokenStore : IRefreshTokenStore { private readonly IConnectionMultiplexer _redis; public RedisRefreshTokenStore(IConnectionMultiplexer redis) { _redis = redis; } private static string Key(string token) => $"refresh:{token}"; public async Task StoreAsync(string refreshToken, RefreshTokenPayload payload, TimeSpan ttl, CancellationToken cancellationToken = default) { var db = _redis.GetDatabase(); var json = JsonSerializer.Serialize(payload); await db.StringSetAsync(Key(refreshToken), json, ttl); } public async Task GetAsync(string refreshToken, CancellationToken cancellationToken = default) { var db = _redis.GetDatabase(); var value = await db.StringGetAsync(Key(refreshToken)); if (value.IsNullOrEmpty) return null; return JsonSerializer.Deserialize(value.ToString()); } public async Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default) { var db = _redis.GetDatabase(); await db.KeyDeleteAsync(Key(refreshToken)); } }