♻️ Finish centerlizing the data models

This commit is contained in:
2025-09-27 15:14:05 +08:00
parent e70d8371f8
commit 9ce31c4dd8
167 changed files with 780 additions and 42880 deletions

View File

@@ -6,11 +6,9 @@ using DysonNetwork.Pass.Wallet.PaymentHandlers;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using NodaTime;
using AccountService = DysonNetwork.Pass.Account.AccountService;
using Duration = NodaTime.Duration;
namespace DysonNetwork.Pass.Wallet;
@@ -18,7 +16,7 @@ namespace DysonNetwork.Pass.Wallet;
public class SubscriptionService(
AppDatabase db,
PaymentService payment,
AccountService accounts,
Account.AccountService accounts,
RingService.RingServiceClient pusher,
IStringLocalizer<NotificationResource> localizer,
IConfiguration configuration,
@@ -26,11 +24,11 @@ public class SubscriptionService(
ILogger<SubscriptionService> logger
)
{
public async Task<Subscription> CreateSubscriptionAsync(
Account.Account account,
public async Task<SnWalletSubscription> CreateSubscriptionAsync(
SnAccount account,
string identifier,
string paymentMethod,
PaymentDetails paymentDetails,
SnPaymentDetails paymentDetails,
Duration? cycleDuration = null,
string? coupon = null,
bool isFreeTrial = false,
@@ -80,7 +78,7 @@ public class SubscriptionService(
throw new InvalidOperationException("Free trial already exists.");
}
Coupon? couponData = null;
SnWalletCoupon? couponData = null;
if (coupon is not null)
{
var inputCouponId = Guid.TryParse(coupon, out var parsedCouponId) ? parsedCouponId : Guid.Empty;
@@ -91,14 +89,14 @@ public class SubscriptionService(
}
var now = SystemClock.Instance.GetCurrentInstant();
var subscription = new Subscription
var subscription = new SnWalletSubscription
{
BegunAt = now,
EndedAt = now.Plus(cycleDuration.Value),
Identifier = identifier,
IsActive = true,
IsFreeTrial = isFreeTrial,
Status = SubscriptionStatus.Unpaid,
Status = Shared.Models.SubscriptionStatus.Unpaid,
PaymentMethod = paymentMethod,
PaymentDetails = paymentDetails,
BasePrice = subscriptionInfo.BasePrice,
@@ -114,7 +112,7 @@ public class SubscriptionService(
return subscription;
}
public async Task<Subscription> CreateSubscriptionFromOrder(ISubscriptionOrder order)
public async Task<SnWalletSubscription> CreateSubscriptionFromOrder(ISubscriptionOrder order)
{
var cfgSection = configuration.GetSection("Payment:Subscriptions");
var provider = order.Provider;
@@ -141,7 +139,7 @@ public class SubscriptionService(
throw new ArgumentOutOfRangeException(nameof(subscriptionIdentifier),
$@"Subscription {subscriptionIdentifier} was not found.");
Account.Account? account = null;
SnAccount? account = null;
if (!string.IsNullOrEmpty(provider))
account = await accounts.LookupAccountByConnection(order.AccountId, provider);
else if (Guid.TryParse(order.AccountId, out var accountId))
@@ -164,7 +162,7 @@ public class SubscriptionService(
existingSubscription.PaymentDetails.OrderId = order.Id;
existingSubscription.EndedAt = order.BegunAt.Plus(cycleDuration);
existingSubscription.RenewalAt = order.BegunAt.Plus(cycleDuration);
existingSubscription.Status = SubscriptionStatus.Active;
existingSubscription.Status = Shared.Models.SubscriptionStatus.Active;
db.Update(existingSubscription);
await db.SaveChangesAsync();
@@ -172,15 +170,15 @@ public class SubscriptionService(
return existingSubscription;
}
var subscription = new Subscription
var subscription = new SnWalletSubscription
{
BegunAt = order.BegunAt,
EndedAt = order.BegunAt.Plus(cycleDuration),
IsActive = true,
Status = SubscriptionStatus.Active,
Status = Shared.Models.SubscriptionStatus.Active,
Identifier = subscriptionIdentifier,
PaymentMethod = provider,
PaymentDetails = new PaymentDetails
PaymentDetails = new Shared.Models.SnPaymentDetails
{
Currency = currency,
OrderId = order.Id,
@@ -205,12 +203,12 @@ public class SubscriptionService(
/// <param name="identifier">The subscription identifier</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">The active subscription was not found</exception>
public async Task<Subscription> CancelSubscriptionAsync(Guid accountId, string identifier)
public async Task<SnWalletSubscription> CancelSubscriptionAsync(Guid accountId, string identifier)
{
var subscription = await GetSubscriptionAsync(accountId, identifier);
if (subscription is null)
throw new InvalidOperationException($"Subscription with identifier {identifier} was not found.");
if (subscription.Status != SubscriptionStatus.Active)
if (subscription.Status != Shared.Models.SubscriptionStatus.Active)
throw new InvalidOperationException("Subscription is already cancelled.");
if (subscription.RenewalAt is null)
throw new InvalidOperationException("Subscription is no need to be cancelled.");
@@ -238,11 +236,11 @@ public class SubscriptionService(
/// <param name="identifier">The unique subscription identifier.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the created subscription order.</returns>
/// <exception cref="InvalidOperationException">Thrown when no matching unpaid or expired subscription is found.</exception>
public async Task<Order> CreateSubscriptionOrder(Guid accountId, string identifier)
public async Task<SnWalletOrder> CreateSubscriptionOrder(Guid accountId, string identifier)
{
var subscription = await db.WalletSubscriptions
.Where(s => s.AccountId == accountId && s.Identifier == identifier)
.Where(s => s.Status != SubscriptionStatus.Expired)
.Where(s => s.Status != Shared.Models.SubscriptionStatus.Expired)
.Include(s => s.Coupon)
.OrderByDescending(s => s.BegunAt)
.FirstOrDefaultAsync();
@@ -268,9 +266,9 @@ public class SubscriptionService(
);
}
public async Task<Subscription> HandleSubscriptionOrder(Order order)
public async Task<SnWalletSubscription> HandleSubscriptionOrder(SnWalletOrder order)
{
if (order.Status != OrderStatus.Paid || order.Meta?["subscription_id"] is not JsonElement subscriptionIdJson)
if (order.Status != Shared.Models.OrderStatus.Paid || order.Meta?["subscription_id"] is not JsonElement subscriptionIdJson)
throw new InvalidOperationException("Invalid order.");
var subscriptionId = Guid.TryParse(subscriptionIdJson.ToString(), out var parsedSubscriptionId)
@@ -285,7 +283,7 @@ public class SubscriptionService(
if (subscription is null)
throw new InvalidOperationException("Invalid order.");
if (subscription.Status == SubscriptionStatus.Expired)
if (subscription.Status == Shared.Models.SubscriptionStatus.Expired)
{
var now = SystemClock.Instance.GetCurrentInstant();
var cycle = subscription.BegunAt.Minus(subscription.RenewalAt ?? subscription.EndedAt ?? now);
@@ -297,7 +295,7 @@ public class SubscriptionService(
subscription.EndedAt = nextEndedAt;
}
subscription.Status = SubscriptionStatus.Active;
subscription.Status = Shared.Models.SubscriptionStatus.Active;
db.Update(subscription);
await db.SaveChangesAsync();
@@ -320,7 +318,7 @@ public class SubscriptionService(
// Find active subscriptions that have passed their end date
var expiredSubscriptions = await db.WalletSubscriptions
.Where(s => s.IsActive)
.Where(s => s.Status == SubscriptionStatus.Active)
.Where(s => s.Status == Shared.Models.SubscriptionStatus.Active)
.Where(s => s.EndedAt.HasValue && s.EndedAt.Value < now)
.Take(batchSize)
.ToListAsync();
@@ -330,7 +328,7 @@ public class SubscriptionService(
foreach (var subscription in expiredSubscriptions)
{
subscription.Status = SubscriptionStatus.Expired;
subscription.Status = Shared.Models.SubscriptionStatus.Expired;
// Clear the cache for this subscription
var cacheKey = $"{SubscriptionCacheKeyPrefix}{subscription.AccountId}:{subscription.Identifier}";
@@ -341,12 +339,12 @@ public class SubscriptionService(
return expiredSubscriptions.Count;
}
private async Task NotifySubscriptionBegun(Subscription subscription)
private async Task NotifySubscriptionBegun(SnWalletSubscription subscription)
{
var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId);
if (account is null) return;
AccountService.SetCultureInfo(account);
Account.AccountService.SetCultureInfo(account);
var humanReadableName =
SubscriptionTypeData.SubscriptionHumanReadable.TryGetValue(subscription.Identifier, out var humanReadable)
@@ -378,7 +376,7 @@ public class SubscriptionService(
private const string SubscriptionCacheKeyPrefix = "subscription:";
public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, params string[] identifiers)
public async Task<SnWalletSubscription?> GetSubscriptionAsync(Guid accountId, params string[] identifiers)
{
// Create a unique cache key for this subscription
var hashBytes = MD5.HashData(Encoding.UTF8.GetBytes(string.Join(",", identifiers)));
@@ -387,7 +385,7 @@ public class SubscriptionService(
var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{hashIdentifier}";
// Try to get the subscription from cache first
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<SnWalletSubscription>(cacheKey);
if (found && cachedSubscription != null)
{
return cachedSubscription;
@@ -411,12 +409,12 @@ public class SubscriptionService(
private static readonly List<string> PerkIdentifiers =
[SubscriptionType.Stellar, SubscriptionType.Nova, SubscriptionType.Supernova];
public async Task<Subscription?> GetPerkSubscriptionAsync(Guid accountId)
public async Task<SnWalletSubscription?> GetPerkSubscriptionAsync(Guid accountId)
{
var cacheKey = $"{SubscriptionPerkCacheKeyPrefix}{accountId}";
// Try to get the subscription from cache first
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<SnWalletSubscription>(cacheKey);
if (found && cachedSubscription != null)
{
return cachedSubscription;
@@ -426,7 +424,7 @@ public class SubscriptionService(
var now = SystemClock.Instance.GetCurrentInstant();
var subscription = await db.WalletSubscriptions
.Where(s => s.AccountId == accountId && PerkIdentifiers.Contains(s.Identifier))
.Where(s => s.Status == SubscriptionStatus.Active)
.Where(s => s.Status == Shared.Models.SubscriptionStatus.Active)
.Where(s => s.EndedAt == null || s.EndedAt > now)
.OrderByDescending(s => s.BegunAt)
.FirstOrDefaultAsync();
@@ -439,16 +437,16 @@ public class SubscriptionService(
}
public async Task<Dictionary<Guid, Subscription?>> GetPerkSubscriptionsAsync(List<Guid> accountIds)
public async Task<Dictionary<Guid, SnWalletSubscription?>> GetPerkSubscriptionsAsync(List<Guid> accountIds)
{
var result = new Dictionary<Guid, Subscription?>();
var result = new Dictionary<Guid, SnWalletSubscription?>();
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);
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<SnWalletSubscription>(cacheKey);
if (found && cachedSubscription != null)
result[accountId] = cachedSubscription;
else
@@ -462,7 +460,7 @@ public class SubscriptionService(
var subscriptions = await db.WalletSubscriptions
.Where(s => missingAccountIds.Contains(s.AccountId))
.Where(s => PerkIdentifiers.Contains(s.Identifier))
.Where(s => s.Status == SubscriptionStatus.Active)
.Where(s => s.Status == Shared.Models.SubscriptionStatus.Active)
.Where(s => s.EndedAt == null || s.EndedAt > now)
.OrderByDescending(s => s.BegunAt)
.ToListAsync();