Putting the stellar perks back

This commit is contained in:
2025-07-23 18:20:47 +08:00
parent b4c8096c41
commit 8e61a8b43d
6 changed files with 169 additions and 4 deletions

View File

@@ -176,6 +176,57 @@ public class Subscription : ModelBase
return BasePrice;
}
}
/// <summary>
/// Returns a reference object that contains a subset of subscription data
/// suitable for client-side use, with sensitive information removed.
/// </summary>
public SubscriptionReferenceObject ToReference()
{
return new SubscriptionReferenceObject
{
Id = Id,
Identifier = Identifier,
BegunAt = BegunAt,
EndedAt = EndedAt,
IsActive = IsActive,
IsAvailable = IsAvailable,
IsFreeTrial = IsFreeTrial,
Status = Status,
BasePrice = BasePrice,
FinalPrice = FinalPrice,
RenewalAt = RenewalAt,
AccountId = AccountId
};
}
}
/// <summary>
/// A reference object for Subscription that contains only non-sensitive information
/// suitable for client-side use.
/// </summary>
public class SubscriptionReferenceObject : ModelBase
{
public Guid Id { get; set; }
public string Identifier { get; set; } = null!;
public Instant BegunAt { get; set; }
public Instant? EndedAt { get; set; }
public bool IsActive { get; set; }
public bool IsAvailable { get; set; }
public bool IsFreeTrial { get; set; }
public SubscriptionStatus Status { get; set; }
public decimal BasePrice { get; set; }
public decimal FinalPrice { get; set; }
public Instant? RenewalAt { get; set; }
public Guid AccountId { get; set; }
/// <summary>
/// Gets the human-readable name of the subscription type if available.
/// </summary>
[NotMapped]
public string? DisplayName => SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(Identifier, out var name)
? name
: null;
}
public class PaymentDetails

View File

@@ -1,3 +1,5 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Pass.Wallet.PaymentHandlers;
@@ -377,7 +379,10 @@ public class SubscriptionService(
public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, params string[] identifiers)
{
// Create a unique cache key for this subscription
var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{string.Join(",", identifiers)}";
var hashBytes = MD5.HashData(Encoding.UTF8.GetBytes(string.Join(",", identifiers)));
var hashIdentifier = Convert.ToHexStringLower(hashBytes);
var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{hashIdentifier}";
// Try to get the subscription from cache first
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
@@ -398,4 +403,71 @@ public class SubscriptionService(
return subscription;
}
private const string SubscriptionPerkCacheKeyPrefix = "subscription:perk:";
private static readonly List<string> PerkIdentifiers =
[SubscriptionType.Stellar, SubscriptionType.Nova, SubscriptionType.Supernova];
public async Task<Subscription?> GetPerkSubscriptionAsync(Guid accountId)
{
var cacheKey = $"{SubscriptionPerkCacheKeyPrefix}{accountId}";
// Try to get the subscription from cache first
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
if (found && cachedSubscription != null)
{
return cachedSubscription;
}
// If not in cache, get from database
var subscription = await db.WalletSubscriptions
.Where(s => s.AccountId == accountId && PerkIdentifiers.Contains(s.Identifier))
.OrderByDescending(s => s.BegunAt)
.FirstOrDefaultAsync();
// Cache the result if found (with 30 minutes expiry)
if (subscription != null)
await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30));
return subscription;
}
public async Task<Dictionary<Guid, Subscription?>> GetPerkSubscriptionsAsync(List<Guid> accountIds)
{
var result = new Dictionary<Guid, Subscription?>();
var missingAccountIds = new List<Guid>();
// Try to get the subscription from cache first
foreach (var accountId in accountIds)
{
var cacheKey = $"{SubscriptionPerkCacheKeyPrefix}{accountId}";
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
if (found && cachedSubscription != null)
result[accountId] = cachedSubscription;
else
missingAccountIds.Add(accountId);
}
if (missingAccountIds.Count <= 0) return result;
// If not in cache, get from database
var subscriptions = await db.WalletSubscriptions
.Where(s => missingAccountIds.Contains(s.AccountId))
.Where(s => PerkIdentifiers.Contains(s.Identifier))
.ToListAsync();
// Group the subscriptions by account id
foreach (var subscription in subscriptions)
{
result[subscription.AccountId] = subscription;
// Cache the result if found (with 30 minutes expiry)
var cacheKey = $"{SubscriptionPerkCacheKeyPrefix}{subscription.AccountId}";
await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30));
}
return result;
}
}