✨ Pass rewind service and account rewind service
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
namespace DysonNetwork.Pass.Account.Rewind;
|
||||
|
||||
public class AccountRewindService(AppDatabase db)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace DysonNetwork.Pass.Account.Rewind;
|
||||
namespace DysonNetwork.Pass.Rewind;
|
||||
|
||||
public class AccountRewindController
|
||||
{
|
||||
75
DysonNetwork.Pass/Rewind/AccountRewindService.cs
Normal file
75
DysonNetwork.Pass/Rewind/AccountRewindService.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Pass.Rewind;
|
||||
|
||||
public class AccountRewindService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
AppDatabase db,
|
||||
PassRewindService passRewindSrv
|
||||
)
|
||||
{
|
||||
private static string CapitalizeFirstLetter(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return str;
|
||||
|
||||
// Capitalize the first character and append the rest of the string in lowercase
|
||||
return char.ToUpper(str[0]) + str[1..].ToLower();
|
||||
}
|
||||
|
||||
private RewindService.RewindServiceClient CreateRewindServiceClient(string serviceId)
|
||||
{
|
||||
var httpClient = httpClientFactory.CreateClient(
|
||||
$"{nameof(AccountRewindService)}+{CapitalizeFirstLetter(serviceId)}"
|
||||
);
|
||||
var channel = GrpcChannel.ForAddress($"http://{serviceId}", new GrpcChannelOptions { HttpClient = httpClient });
|
||||
return new RewindService.RewindServiceClient(channel);
|
||||
}
|
||||
|
||||
private async Task<SnRewindPoint> CreateRewindPoint(Guid accountId)
|
||||
{
|
||||
var currentYear = DateTime.UtcNow.Year;
|
||||
var rewindRequest = new RequestRewindEvent { AccountId = accountId.ToString(), Year = currentYear};
|
||||
|
||||
var rewindEventTasks = new List<Task<RewindEvent>>
|
||||
{
|
||||
passRewindSrv.CreateRewindEvent(accountId, currentYear),
|
||||
CreateRewindServiceClient("sphere").GetRewindEventAsync(rewindRequest).ResponseAsync
|
||||
};
|
||||
var rewindEvents = await Task.WhenAll(rewindEventTasks);
|
||||
|
||||
var rewindData = rewindEvents.ToDictionary<RewindEvent, string, Dictionary<string, object?>>(
|
||||
rewindEvent => rewindEvent.ServiceId,
|
||||
rewindEvent => GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object?>>(rewindEvent.Data) ??
|
||||
new Dictionary<string, object?>()
|
||||
);
|
||||
|
||||
var point = new SnRewindPoint
|
||||
{
|
||||
SchemaVersion = 1,
|
||||
AccountId = accountId,
|
||||
Data = rewindData.ToDictionary(kvp => kvp.Key, object? (kvp) => kvp.Value)
|
||||
};
|
||||
|
||||
db.RewindPoints.Add(point);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
public async Task<SnRewindPoint> GetOrCreateRewindPoint(Guid accountId)
|
||||
{
|
||||
var currentYear = DateTime.UtcNow.Year;
|
||||
|
||||
var existingRewind = await db.RewindPoints
|
||||
.Where(p => p.AccountId == accountId && p.Year == currentYear)
|
||||
.OrderBy(p => p.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
if (existingRewind is not null) return existingRewind;
|
||||
|
||||
return await CreateRewindPoint(accountId);
|
||||
}
|
||||
}
|
||||
49
DysonNetwork.Pass/Rewind/PassRewindService.cs
Normal file
49
DysonNetwork.Pass/Rewind/PassRewindService.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Rewind;
|
||||
|
||||
/// <summary>
|
||||
/// Although the pass uses the rewind service call internally, no need for grpc.
|
||||
/// But we created a service that produce the grpc type for consistency.
|
||||
/// </summary>
|
||||
public class PassRewindService(AppDatabase db)
|
||||
{
|
||||
public async Task<RewindEvent> CreateRewindEvent(Guid accountId, int year)
|
||||
{
|
||||
var startDate = Instant.FromDateTimeUtc(new DateTime(year - 1, 12, 26));
|
||||
var endDate = Instant.FromDateTimeUtc(new DateTime(year, 12, 26));
|
||||
|
||||
var checkInDates = await db.AccountCheckInResults
|
||||
.Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate)
|
||||
.Where(a => a.AccountId == accountId)
|
||||
.Select(a => a.CreatedAt.ToDateTimeUtc().Date)
|
||||
.Distinct()
|
||||
.OrderBy(d => d)
|
||||
.ToListAsync();
|
||||
|
||||
var maxCheckInStrike = 0;
|
||||
if (checkInDates.Count != 0)
|
||||
{
|
||||
maxCheckInStrike = checkInDates
|
||||
.Select((d, i) => new { Date = d, Index = i })
|
||||
.GroupBy(x => x.Date.Subtract(new TimeSpan(x.Index, 0, 0, 0)))
|
||||
.Select(g => g.Count())
|
||||
.Max();
|
||||
}
|
||||
|
||||
var data = new Dictionary<string, object?>
|
||||
{
|
||||
["max_check_in_strike"] = maxCheckInStrike,
|
||||
};
|
||||
|
||||
return new RewindEvent
|
||||
{
|
||||
ServiceId = "pass",
|
||||
AccountId = accountId.ToString(),
|
||||
Data = GrpcTypeHelper.ConvertObjectToByteString(data)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ using DysonNetwork.Pass.Leveling;
|
||||
using DysonNetwork.Pass.Lotteries;
|
||||
using DysonNetwork.Pass.Mailer;
|
||||
using DysonNetwork.Pass.Realm;
|
||||
using DysonNetwork.Pass.Rewind;
|
||||
using DysonNetwork.Pass.Safety;
|
||||
using DysonNetwork.Pass.Wallet.PaymentHandlers;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
@@ -167,6 +168,9 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
||||
services.AddScoped<OidcProviderService>();
|
||||
|
||||
services.AddScoped<PassRewindService>();
|
||||
services.AddScoped<AccountRewindService>();
|
||||
|
||||
services.AddHostedService<BroadcastEventHandler>();
|
||||
|
||||
|
||||
21
DysonNetwork.Shared/Models/RewindPoint.cs
Normal file
21
DysonNetwork.Shared/Models/RewindPoint.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnRewindPoint : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public int Year { get; set; } = DateTime.UtcNow.Year;
|
||||
|
||||
/// <summary>
|
||||
/// Due to every year the Solar Network upgrade become better and better.
|
||||
/// The rewind data might be incompatible at that time,
|
||||
/// this field provide the clues for the client to parsing the data correctly.
|
||||
/// </summary>
|
||||
public int SchemaVersion { get; set; } = 1;
|
||||
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object?> Data { get; set; } = new();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace DysonNetwork.Shared.Models;
|
||||
|
||||
public class SnRewindPoint
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public int Year { get; set; } = DateTime.UtcNow.Year;
|
||||
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, string> Data { get; set; } = new();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ message RewindEvent {
|
||||
|
||||
message RequestRewindEvent {
|
||||
string account_id = 1;
|
||||
int32 year = 2;
|
||||
}
|
||||
|
||||
service RewindService {
|
||||
|
||||
@@ -3,6 +3,7 @@ using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Rewind;
|
||||
|
||||
@@ -15,10 +16,15 @@ public class SphereRewindServiceGrpc(
|
||||
public override async Task<RewindEvent> GetRewindEvent(RequestRewindEvent request, ServerCallContext context)
|
||||
{
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var year = request.Year;
|
||||
|
||||
var startDate = Instant.FromDateTimeUtc(new DateTime(year - 1, 12, 26));
|
||||
var endDate = Instant.FromDateTimeUtc(new DateTime(year, 12, 26));
|
||||
|
||||
// Audience data
|
||||
var mostLovedPublisherClue =
|
||||
await db.PostReactions
|
||||
.Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate)
|
||||
.Where(p => p.AccountId == accountId && p.Attitude == Shared.Models.PostReactionAttitude.Positive)
|
||||
.GroupBy(p => p.Post.PublisherId)
|
||||
.OrderByDescending(g => g.Count())
|
||||
@@ -36,6 +42,7 @@ public class SphereRewindServiceGrpc(
|
||||
|
||||
var mostLovedAudienceClue =
|
||||
await db.PostReactions
|
||||
.Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate)
|
||||
.Where(pr =>
|
||||
pr.Attitude == Shared.Models.PostReactionAttitude.Positive &&
|
||||
publishers.Contains(pr.Post.PublisherId))
|
||||
@@ -47,14 +54,15 @@ public class SphereRewindServiceGrpc(
|
||||
? await remoteAccounts.GetAccount(mostLovedAudienceClue.AccountId)
|
||||
: null;
|
||||
|
||||
var posts = await db.Posts
|
||||
var posts = db.Posts
|
||||
.Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate)
|
||||
.Where(p => publishers.Contains(p.PublisherId))
|
||||
.ToListAsync();
|
||||
var postTotalCount = posts.Count;
|
||||
var mostPopularPost = posts
|
||||
.AsQueryable();
|
||||
var postTotalCount = await posts.CountAsync();
|
||||
var mostPopularPost = await posts
|
||||
.OrderByDescending(p => p.Upvotes - p.Downvotes)
|
||||
.FirstOrDefault();
|
||||
var mostProductiveDay = posts
|
||||
.FirstOrDefaultAsync();
|
||||
var mostProductiveDay = (await posts.Select(p => new { p.CreatedAt }).ToListAsync())
|
||||
.GroupBy(p => p.CreatedAt.ToDateTimeUtc().Date)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Select(g => new { Date = g.Key, PostCount = g.Count() })
|
||||
@@ -94,4 +102,4 @@ public class SphereRewindServiceGrpc(
|
||||
Data = GrpcTypeHelper.ConvertObjectToByteString(data)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbFunctionsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc1c46ed28c61e1caa79185e4375a8ae7cd11cd5ba8853dcb37577f93f2ca8d5_003FDbFunctionsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultInterpolatedStringHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3e6f03b324974d80b20d1999c4a43881f83400_003F87_003F5967abaf_003FDefaultInterpolatedStringHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADiagnosticServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F47e01f36dea14a23aaea6e0391c1347ace00_003F3c_003F140e6d8b_003FDiagnosticServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19674998a88c4c42be3692b32f3379d21046510_003F40_003F495b0a36_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADirectory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fde_003F94973e27_003FDirectory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADockerComposeServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4768773ea5864bf8b6fc7a1a3c6f6f311fc38_003F2d_003F5f83b17e_003FDockerComposeServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpointConventionBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F8a_003F101938e3_003FEndpointConventionBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
@@ -66,6 +67,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFirebaseSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003F5c_003F1f5bca3f_003FFirebaseSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedTransformExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F03_003F36e779df_003FForwardedTransformExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGrpcChannel_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F33b697967214455ca048862a59bf98a457c60_003Fc0_003Fd99cb5be_003FGrpcChannel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc181aff8c6ec418494a7efcfec578fc154e00_003Fd0_003Fcc905531_003FHttpContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpRequestHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb904f9896c4049fabd596decf1be9c381dc400_003F32_003F906beb77_003FHttpRequestHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpStatusCode_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3f2e07d4b3f4b42a41fbcf3137e534f3be00_003Fe2_003F215f9441_003FHttpStatusCode_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
Reference in New Issue
Block a user