Compare commits
	
		
			2 Commits
		
	
	
		
			f177377fe3
			...
			fb885e138d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fb885e138d | |||
| 5bdc21ebc5 | 
| @@ -42,17 +42,31 @@ public class ActivityService( | ||||
|  | ||||
|         if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2) | ||||
|         { | ||||
|             var recentArticlesQuery = db.WebArticles | ||||
|                 .Include(a => a.Feed) | ||||
|                 .Take(20); // Get a larger pool for randomization | ||||
|             var recentFeedIds = await db.WebArticles | ||||
|                 .GroupBy(a => a.FeedId) | ||||
|                 .OrderByDescending(g => g.Max(a => a.PublishedAt)) | ||||
|                 .Take(10) // Get recent 10 distinct feeds | ||||
|                 .Select(g => g.Key) | ||||
|                 .ToListAsync(); | ||||
|  | ||||
|             // Apply random ordering 50% of the time | ||||
|             if (Random.Shared.NextDouble() < 0.5) | ||||
|                 recentArticlesQuery = recentArticlesQuery.OrderBy(_ => EF.Functions.Random()); | ||||
|             else | ||||
|                 recentArticlesQuery = recentArticlesQuery.OrderByDescending(a => a.PublishedAt); | ||||
|  | ||||
|             var recentArticles = await recentArticlesQuery.Take(5).ToListAsync(); | ||||
|             // For each feed, get one random article | ||||
|             var recentArticles = new List<WebArticle>(); | ||||
|             var random = new Random(); | ||||
|              | ||||
|             foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next())) | ||||
|             { | ||||
|                 var article = await db.WebArticles | ||||
|                     .Include(a => a.Feed) | ||||
|                     .Where(a => a.FeedId == feedId) | ||||
|                     .OrderBy(_ => EF.Functions.Random()) | ||||
|                     .FirstOrDefaultAsync(); | ||||
|                      | ||||
|                 if (article != null) | ||||
|                 { | ||||
|                     recentArticles.Add(article); | ||||
|                     if (recentArticles.Count >= 5) break; // Limit to 5 articles | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (recentArticles.Count > 0) | ||||
|             { | ||||
|   | ||||
| @@ -93,17 +93,13 @@ public class WebFeedService( | ||||
|         { | ||||
|             var itemUrl = item.Links.FirstOrDefault()?.Uri.ToString(); | ||||
|             if (string.IsNullOrEmpty(itemUrl)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             var articleExists = await database.Set<WebArticle>() | ||||
|                 .AnyAsync(a => a.FeedId == feed.Id && a.Url == itemUrl, cancellationToken); | ||||
|  | ||||
|             if (articleExists) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             var content = (item.Content as TextSyndicationContent)?.Text ?? item.Summary.Text; | ||||
|             LinkEmbed preview; | ||||
| @@ -127,11 +123,11 @@ public class WebFeedService( | ||||
|                 Url = itemUrl, | ||||
|                 Author = item.Authors.FirstOrDefault()?.Name, | ||||
|                 Content = content, | ||||
|                 PublishedAt = item.PublishDate.UtcDateTime, | ||||
|                 PublishedAt = item.LastUpdatedTime.UtcDateTime, | ||||
|                 Preview = preview, | ||||
|             }; | ||||
|  | ||||
|             database.Set<WebArticle>().Add(newArticle); | ||||
|             database.WebArticles.Add(newArticle); | ||||
|         } | ||||
|  | ||||
|         await database.SaveChangesAsync(cancellationToken); | ||||
|   | ||||
| @@ -7,16 +7,43 @@ namespace DysonNetwork.Sphere.Wallet; | ||||
|  | ||||
| public record class SubscriptionTypeData( | ||||
|     string Identifier, | ||||
|     decimal BasePrice | ||||
|     string? GroupIdentifier, | ||||
|     string Currency, | ||||
|     decimal BasePrice, | ||||
|     int? RequiredLevel = null | ||||
| ) | ||||
| { | ||||
|     public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict = | ||||
|         new() | ||||
|         { | ||||
|             [SubscriptionType.Twinkle] = new SubscriptionTypeData(SubscriptionType.Twinkle, 0), | ||||
|             [SubscriptionType.Stellar] = new SubscriptionTypeData(SubscriptionType.Stellar, 10), | ||||
|             [SubscriptionType.Nova] = new SubscriptionTypeData(SubscriptionType.Nova, 20), | ||||
|             [SubscriptionType.Supernova] = new SubscriptionTypeData(SubscriptionType.Supernova, 30) | ||||
|             [SubscriptionType.Twinkle] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Twinkle, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 0, | ||||
|                 1 | ||||
|             ), | ||||
|             [SubscriptionType.Stellar] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Stellar, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 1200, | ||||
|                 3 | ||||
|             ), | ||||
|             [SubscriptionType.Nova] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Nova, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 2400, | ||||
|                 6 | ||||
|             ), | ||||
|             [SubscriptionType.Supernova] = new SubscriptionTypeData( | ||||
|                 SubscriptionType.Supernova, | ||||
|                 SubscriptionType.StellarProgram, | ||||
|                 WalletCurrency.SourcePoint, | ||||
|                 3600, | ||||
|                 9 | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|     public static readonly Dictionary<string, string> SubscriptionHumanReadable = | ||||
|   | ||||
| @@ -32,16 +32,22 @@ public class SubscriptionService( | ||||
|         bool noop = false | ||||
|     ) | ||||
|     { | ||||
|         var subscriptionTemplate = SubscriptionTypeData | ||||
|         var subscriptionInfo = SubscriptionTypeData | ||||
|             .SubscriptionDict.TryGetValue(identifier, out var template) | ||||
|             ? template | ||||
|             : null; | ||||
|         if (subscriptionTemplate is null) | ||||
|         if (subscriptionInfo is null) | ||||
|             throw new ArgumentOutOfRangeException(nameof(identifier), $@"Subscription {identifier} was not found."); | ||||
|         var subscriptionsInGroup = subscriptionInfo.GroupIdentifier is not null | ||||
|             ? SubscriptionTypeData.SubscriptionDict | ||||
|                 .Where(s => s.Value.GroupIdentifier == subscriptionInfo.GroupIdentifier) | ||||
|                 .Select(s => s.Value.Identifier) | ||||
|                 .ToArray() | ||||
|             : [identifier]; | ||||
|  | ||||
|         cycleDuration ??= Duration.FromDays(30); | ||||
|  | ||||
|         var existingSubscription = await GetSubscriptionAsync(account.Id, identifier); | ||||
|         var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionsInGroup); | ||||
|         if (existingSubscription is not null && !noop) | ||||
|             throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists."); | ||||
|         if (existingSubscription is not null) | ||||
| @@ -77,7 +83,7 @@ public class SubscriptionService( | ||||
|             Status = SubscriptionStatus.Unpaid, | ||||
|             PaymentMethod = paymentMethod, | ||||
|             PaymentDetails = paymentDetails, | ||||
|             BasePrice = subscriptionTemplate.BasePrice, | ||||
|             BasePrice = subscriptionInfo.BasePrice, | ||||
|             CouponId = couponData?.Id, | ||||
|             Coupon = couponData, | ||||
|             RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value), | ||||
| @@ -220,10 +226,16 @@ public class SubscriptionService( | ||||
|             .OrderByDescending(s => s.BegunAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (subscription is null) throw new InvalidOperationException("No matching subscription found."); | ||||
|          | ||||
|         var subscriptionInfo = SubscriptionTypeData.SubscriptionDict | ||||
|             .TryGetValue(subscription.Identifier, out var template) | ||||
|             ? template | ||||
|             : null; | ||||
|         if (subscriptionInfo is null) throw new InvalidOperationException("No matching subscription found."); | ||||
|  | ||||
|         return await payment.CreateOrderAsync( | ||||
|             null, | ||||
|             WalletCurrency.GoldenPoint, | ||||
|             subscriptionInfo.Currency, | ||||
|             subscription.FinalPrice, | ||||
|             appIdentifier: SubscriptionOrderIdentifier, | ||||
|             meta: new Dictionary<string, object>() | ||||
| @@ -319,7 +331,7 @@ public class SubscriptionService( | ||||
|     { | ||||
|         var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId); | ||||
|         if (account is null) return; | ||||
|          | ||||
|  | ||||
|         AccountService.SetCultureInfo(account); | ||||
|  | ||||
|         var humanReadableName = | ||||
| @@ -345,10 +357,10 @@ public class SubscriptionService( | ||||
|  | ||||
|     private const string SubscriptionCacheKeyPrefix = "subscription:"; | ||||
|  | ||||
|     public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, string identifier) | ||||
|     public async Task<Subscription?> GetSubscriptionAsync(Guid accountId, params string[] identifiers) | ||||
|     { | ||||
|         // Create a unique cache key for this subscription | ||||
|         var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{identifier}"; | ||||
|         var cacheKey = $"{SubscriptionCacheKeyPrefix}{accountId}:{string.Join(",", identifiers)}"; | ||||
|  | ||||
|         // Try to get the subscription from cache first | ||||
|         var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey); | ||||
| @@ -359,15 +371,13 @@ public class SubscriptionService( | ||||
|  | ||||
|         // If not in cache, get from database | ||||
|         var subscription = await db.WalletSubscriptions | ||||
|             .Where(s => s.AccountId == accountId && s.Identifier == identifier) | ||||
|             .Where(s => s.AccountId == accountId && identifiers.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; | ||||
|     } | ||||
|   | ||||
| @@ -83,6 +83,7 @@ | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationFeed_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fad_003Fd26b4d73_003FSyndicationFeed_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fe1_003Fb136d7be_003FSyndicationItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F12_003Fe0a28ad6_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
| 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATotp_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F48c9d2a1b3c84b32b36ebc6f20a927ea4600_003F7b_003Ff98e5727_003FTotp_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user