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)
|
if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2)
|
||||||
{
|
{
|
||||||
var recentArticlesQuery = db.WebArticles
|
var recentFeedIds = await db.WebArticles
|
||||||
.Include(a => a.Feed)
|
.GroupBy(a => a.FeedId)
|
||||||
.Take(20); // Get a larger pool for randomization
|
.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
|
// For each feed, get one random article
|
||||||
if (Random.Shared.NextDouble() < 0.5)
|
var recentArticles = new List<WebArticle>();
|
||||||
recentArticlesQuery = recentArticlesQuery.OrderBy(_ => EF.Functions.Random());
|
var random = new Random();
|
||||||
else
|
|
||||||
recentArticlesQuery = recentArticlesQuery.OrderByDescending(a => a.PublishedAt);
|
foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next()))
|
||||||
|
{
|
||||||
var recentArticles = await recentArticlesQuery.Take(5).ToListAsync();
|
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)
|
if (recentArticles.Count > 0)
|
||||||
{
|
{
|
||||||
|
@ -93,17 +93,13 @@ public class WebFeedService(
|
|||||||
{
|
{
|
||||||
var itemUrl = item.Links.FirstOrDefault()?.Uri.ToString();
|
var itemUrl = item.Links.FirstOrDefault()?.Uri.ToString();
|
||||||
if (string.IsNullOrEmpty(itemUrl))
|
if (string.IsNullOrEmpty(itemUrl))
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
var articleExists = await database.Set<WebArticle>()
|
var articleExists = await database.Set<WebArticle>()
|
||||||
.AnyAsync(a => a.FeedId == feed.Id && a.Url == itemUrl, cancellationToken);
|
.AnyAsync(a => a.FeedId == feed.Id && a.Url == itemUrl, cancellationToken);
|
||||||
|
|
||||||
if (articleExists)
|
if (articleExists)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
var content = (item.Content as TextSyndicationContent)?.Text ?? item.Summary.Text;
|
var content = (item.Content as TextSyndicationContent)?.Text ?? item.Summary.Text;
|
||||||
LinkEmbed preview;
|
LinkEmbed preview;
|
||||||
@ -127,11 +123,11 @@ public class WebFeedService(
|
|||||||
Url = itemUrl,
|
Url = itemUrl,
|
||||||
Author = item.Authors.FirstOrDefault()?.Name,
|
Author = item.Authors.FirstOrDefault()?.Name,
|
||||||
Content = content,
|
Content = content,
|
||||||
PublishedAt = item.PublishDate.UtcDateTime,
|
PublishedAt = item.LastUpdatedTime.UtcDateTime,
|
||||||
Preview = preview,
|
Preview = preview,
|
||||||
};
|
};
|
||||||
|
|
||||||
database.Set<WebArticle>().Add(newArticle);
|
database.WebArticles.Add(newArticle);
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.SaveChangesAsync(cancellationToken);
|
await database.SaveChangesAsync(cancellationToken);
|
||||||
|
@ -7,16 +7,43 @@ namespace DysonNetwork.Sphere.Wallet;
|
|||||||
|
|
||||||
public record class SubscriptionTypeData(
|
public record class SubscriptionTypeData(
|
||||||
string Identifier,
|
string Identifier,
|
||||||
decimal BasePrice
|
string? GroupIdentifier,
|
||||||
|
string Currency,
|
||||||
|
decimal BasePrice,
|
||||||
|
int? RequiredLevel = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict =
|
public static readonly Dictionary<string, SubscriptionTypeData> SubscriptionDict =
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
[SubscriptionType.Twinkle] = new SubscriptionTypeData(SubscriptionType.Twinkle, 0),
|
[SubscriptionType.Twinkle] = new SubscriptionTypeData(
|
||||||
[SubscriptionType.Stellar] = new SubscriptionTypeData(SubscriptionType.Stellar, 10),
|
SubscriptionType.Twinkle,
|
||||||
[SubscriptionType.Nova] = new SubscriptionTypeData(SubscriptionType.Nova, 20),
|
SubscriptionType.StellarProgram,
|
||||||
[SubscriptionType.Supernova] = new SubscriptionTypeData(SubscriptionType.Supernova, 30)
|
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 =
|
public static readonly Dictionary<string, string> SubscriptionHumanReadable =
|
||||||
|
@ -32,16 +32,22 @@ public class SubscriptionService(
|
|||||||
bool noop = false
|
bool noop = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var subscriptionTemplate = SubscriptionTypeData
|
var subscriptionInfo = SubscriptionTypeData
|
||||||
.SubscriptionDict.TryGetValue(identifier, out var template)
|
.SubscriptionDict.TryGetValue(identifier, out var template)
|
||||||
? template
|
? template
|
||||||
: null;
|
: null;
|
||||||
if (subscriptionTemplate is null)
|
if (subscriptionInfo is null)
|
||||||
throw new ArgumentOutOfRangeException(nameof(identifier), $@"Subscription {identifier} was not found.");
|
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);
|
cycleDuration ??= Duration.FromDays(30);
|
||||||
|
|
||||||
var existingSubscription = await GetSubscriptionAsync(account.Id, identifier);
|
var existingSubscription = await GetSubscriptionAsync(account.Id, subscriptionsInGroup);
|
||||||
if (existingSubscription is not null && !noop)
|
if (existingSubscription is not null && !noop)
|
||||||
throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists.");
|
throw new InvalidOperationException($"Active subscription with identifier {identifier} already exists.");
|
||||||
if (existingSubscription is not null)
|
if (existingSubscription is not null)
|
||||||
@ -77,7 +83,7 @@ public class SubscriptionService(
|
|||||||
Status = SubscriptionStatus.Unpaid,
|
Status = SubscriptionStatus.Unpaid,
|
||||||
PaymentMethod = paymentMethod,
|
PaymentMethod = paymentMethod,
|
||||||
PaymentDetails = paymentDetails,
|
PaymentDetails = paymentDetails,
|
||||||
BasePrice = subscriptionTemplate.BasePrice,
|
BasePrice = subscriptionInfo.BasePrice,
|
||||||
CouponId = couponData?.Id,
|
CouponId = couponData?.Id,
|
||||||
Coupon = couponData,
|
Coupon = couponData,
|
||||||
RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value),
|
RenewalAt = (isFreeTrial || !isAutoRenewal) ? null : now.Plus(cycleDuration.Value),
|
||||||
@ -220,10 +226,16 @@ public class SubscriptionService(
|
|||||||
.OrderByDescending(s => s.BegunAt)
|
.OrderByDescending(s => s.BegunAt)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (subscription is null) throw new InvalidOperationException("No matching subscription found.");
|
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(
|
return await payment.CreateOrderAsync(
|
||||||
null,
|
null,
|
||||||
WalletCurrency.GoldenPoint,
|
subscriptionInfo.Currency,
|
||||||
subscription.FinalPrice,
|
subscription.FinalPrice,
|
||||||
appIdentifier: SubscriptionOrderIdentifier,
|
appIdentifier: SubscriptionOrderIdentifier,
|
||||||
meta: new Dictionary<string, object>()
|
meta: new Dictionary<string, object>()
|
||||||
@ -319,7 +331,7 @@ public class SubscriptionService(
|
|||||||
{
|
{
|
||||||
var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId);
|
var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == subscription.AccountId);
|
||||||
if (account is null) return;
|
if (account is null) return;
|
||||||
|
|
||||||
AccountService.SetCultureInfo(account);
|
AccountService.SetCultureInfo(account);
|
||||||
|
|
||||||
var humanReadableName =
|
var humanReadableName =
|
||||||
@ -345,10 +357,10 @@ public class SubscriptionService(
|
|||||||
|
|
||||||
private const string SubscriptionCacheKeyPrefix = "subscription:";
|
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
|
// 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
|
// Try to get the subscription from cache first
|
||||||
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
|
var (found, cachedSubscription) = await cache.GetAsyncWithStatus<Subscription>(cacheKey);
|
||||||
@ -359,15 +371,13 @@ public class SubscriptionService(
|
|||||||
|
|
||||||
// If not in cache, get from database
|
// If not in cache, get from database
|
||||||
var subscription = await db.WalletSubscriptions
|
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)
|
.OrderByDescending(s => s.BegunAt)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
// Cache the result if found (with 30 minutes expiry)
|
// Cache the result if found (with 30 minutes expiry)
|
||||||
if (subscription != null)
|
if (subscription != null)
|
||||||
{
|
|
||||||
await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30));
|
await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30));
|
||||||
}
|
|
||||||
|
|
||||||
return subscription;
|
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_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_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_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_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_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>
|
<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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user