🐛 Fix Pass service missing perks subscription

This commit is contained in:
2026-02-04 02:10:31 +08:00
parent 0bc4ea68e1
commit 07c9c907f4
4 changed files with 209 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Networking; using DysonNetwork.Shared.Networking;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -22,7 +23,8 @@ public class AccountCurrentController(
AccountEventService events, AccountEventService events,
AuthService auth, AuthService auth,
FileService.FileServiceClient files, FileService.FileServiceClient files,
Credit.SocialCreditService creditService Credit.SocialCreditService creditService,
RemoteSubscriptionService remoteSubscription
) : ControllerBase ) : ControllerBase
{ {
[HttpGet] [HttpGet]
@@ -39,6 +41,24 @@ public class AccountCurrentController(
.Where(e => e.Id == userId) .Where(e => e.Id == userId)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (account != null)
{
// Populate PerkSubscription from Wallet service via gRPC
try
{
var subscription = await remoteSubscription.GetPerkSubscription(account.Id);
if (subscription != null)
{
account.PerkSubscription = SnWalletSubscription.FromProtoValue(subscription).ToReference();
}
}
catch (Exception ex)
{
// Log error but don't fail the request - PerkSubscription is optional
Console.WriteLine($"Failed to populate PerkSubscription for account {account.Id}: {ex.Message}");
}
}
return Ok(account); return Ok(account);
} }
@@ -321,7 +341,10 @@ public class AccountCurrentController(
} }
else else
{ {
if (currentUser.PerkSubscription is null) // Check PerkSubscription via RemoteSubscriptionService instead of relying on currentUser.PerkSubscription
// which is not populated when currentUser comes from HttpContext.Items
var perkSubscription = await remoteSubscription.GetPerkSubscription(currentUser.Id);
if (perkSubscription == null)
return StatusCode(403, ApiError.Unauthorized( return StatusCode(403, ApiError.Unauthorized(
message: "You need to have a subscription to check-in backdated.", message: "You need to have a subscription to check-in backdated.",
forbidden: true, forbidden: true,

View File

@@ -1,6 +1,7 @@
using DysonNetwork.Pass.Credit; using DysonNetwork.Pass.Credit;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Networking; using DysonNetwork.Shared.Networking;
using DysonNetwork.Shared.Registry;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -10,7 +11,8 @@ namespace DysonNetwork.Pass.Account;
[Route("/api/accounts")] [Route("/api/accounts")]
public class AccountPublicController( public class AccountPublicController(
AppDatabase db, AppDatabase db,
SocialCreditService socialCreditService SocialCreditService socialCreditService,
RemoteSubscriptionService remoteSubscription
) : ControllerBase ) : ControllerBase
{ {
[HttpGet("{name}")] [HttpGet("{name}")]
@@ -26,6 +28,21 @@ public class AccountPublicController(
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (account is null) return NotFound(ApiError.NotFound(name, traceId: HttpContext.TraceIdentifier)); if (account is null) return NotFound(ApiError.NotFound(name, traceId: HttpContext.TraceIdentifier));
// Populate PerkSubscription from Wallet service via gRPC
try
{
var subscription = await remoteSubscription.GetPerkSubscription(account.Id);
if (subscription != null)
{
account.PerkSubscription = SnWalletSubscription.FromProtoValue(subscription).ToReference();
}
}
catch (Exception ex)
{
// Log error but don't fail the request - PerkSubscription is optional
Console.WriteLine($"Failed to populate PerkSubscription for account {account.Id}: {ex.Message}");
}
return account; return account;
} }
@@ -67,11 +84,44 @@ public class AccountPublicController(
{ {
if (string.IsNullOrWhiteSpace(query)) if (string.IsNullOrWhiteSpace(query))
return []; return [];
return await db.Accounts
var accounts = await db.Accounts
.Include(e => e.Profile) .Include(e => e.Profile)
.Where(a => EF.Functions.ILike(a.Name, $"%{query}%") || .Where(a => EF.Functions.ILike(a.Name, $"%{query}%") ||
EF.Functions.ILike(a.Nick, $"%{query}%")) EF.Functions.ILike(a.Nick, $"%{query}%"))
.Take(take) .Take(take)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
if (accounts.Count > 0)
{
try
{
var accountIds = accounts.Select(a => a.Id).ToList();
var subscriptions = await remoteSubscription.GetPerkSubscriptions(accountIds);
var subscriptionDict = subscriptions
.Where(s => s != null)
.ToDictionary(
s => Guid.Parse(s.AccountId),
s => SnWalletSubscription.FromProtoValue(s).ToReference()
);
foreach (var account in accounts)
{
if (subscriptionDict.TryGetValue(account.Id, out var subscription))
{
account.PerkSubscription = subscription;
}
}
}
catch (Exception ex)
{
// Log error but don't fail the request - PerkSubscription is optional
Console.WriteLine($"Failed to populate PerkSubscriptions for search results: {ex.Message}");
}
}
return accounts;
} }
} }

View File

@@ -16,6 +16,7 @@ using NATS.Net;
using NodaTime; using NodaTime;
using OtpNet; using OtpNet;
using AuthService = DysonNetwork.Pass.Auth.AuthService; using AuthService = DysonNetwork.Pass.Auth.AuthService;
using DysonNetwork.Shared.Registry;
namespace DysonNetwork.Pass.Account; namespace DysonNetwork.Pass.Account;
@@ -31,6 +32,7 @@ public class AccountService(
IStringLocalizer<EmailResource> emailLocalizer, IStringLocalizer<EmailResource> emailLocalizer,
ICacheService cache, ICacheService cache,
ILogger<AccountService> logger, ILogger<AccountService> logger,
RemoteSubscriptionService remoteSubscription,
INatsConnection nats INatsConnection nats
) )
{ {
@@ -757,4 +759,56 @@ public class AccountService(
}).ToByteArray() }).ToByteArray()
); );
} }
}
/// <summary>
/// Populates the PerkSubscription property for a single account by calling the Wallet service via gRPC.
/// </summary>
public async Task PopulatePerkSubscriptionAsync(SnAccount account)
{
try
{
var subscription = await remoteSubscription.GetPerkSubscription(account.Id);
if (subscription != null)
{
account.PerkSubscription = SnWalletSubscription.FromProtoValue(subscription).ToReference();
}
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to populate PerkSubscription for account {AccountId}", account.Id);
}
}
/// <summary>
/// Populates the PerkSubscription property for multiple accounts by calling the Wallet service via gRPC.
/// </summary>
public async Task PopulatePerkSubscriptionsAsync(List<SnAccount> accounts)
{
if (accounts.Count == 0) return;
try
{
var accountIds = accounts.Select(a => a.Id).ToList();
var subscriptions = await remoteSubscription.GetPerkSubscriptions(accountIds);
var subscriptionDict = subscriptions
.Where(s => s != null)
.ToDictionary(
s => Guid.Parse(s.AccountId),
s => SnWalletSubscription.FromProtoValue(s).ToReference()
);
foreach (var account in accounts)
{
if (subscriptionDict.TryGetValue(account.Id, out var subscription))
{
account.PerkSubscription = subscription;
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to populate PerkSubscriptions for {Count} accounts", accounts.Count);
}
}
}

View File

@@ -1,4 +1,6 @@
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using Grpc.Core; using Grpc.Core;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -10,6 +12,7 @@ public class AccountServiceGrpc(
AppDatabase db, AppDatabase db,
AccountEventService accountEvents, AccountEventService accountEvents,
RelationshipService relationships, RelationshipService relationships,
RemoteSubscriptionService remoteSubscription,
ILogger<AccountServiceGrpc> logger ILogger<AccountServiceGrpc> logger
) )
: Shared.Proto.AccountService.AccountServiceBase : Shared.Proto.AccountService.AccountServiceBase
@@ -33,6 +36,9 @@ public class AccountServiceGrpc(
if (account == null) if (account == null)
throw new RpcException(new Status(StatusCode.NotFound, $"Account {request.Id} not found")); throw new RpcException(new Status(StatusCode.NotFound, $"Account {request.Id} not found"));
// Populate PerkSubscription from Wallet service via gRPC
await PopulatePerkSubscriptionAsync(account);
return account.ToProtoValue(); return account.ToProtoValue();
} }
@@ -51,6 +57,9 @@ public class AccountServiceGrpc(
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
$"Account with automated ID {request.AutomatedId} not found")); $"Account with automated ID {request.AutomatedId} not found"));
// Populate PerkSubscription from Wallet service via gRPC
await PopulatePerkSubscriptionAsync(account);
return account.ToProtoValue(); return account.ToProtoValue();
} }
@@ -69,6 +78,9 @@ public class AccountServiceGrpc(
.Include(a => a.Profile) .Include(a => a.Profile)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
await PopulatePerkSubscriptionsAsync(accounts);
var response = new GetAccountBatchResponse(); var response = new GetAccountBatchResponse();
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
return response; return response;
@@ -90,6 +102,9 @@ public class AccountServiceGrpc(
.Include(a => a.Profile) .Include(a => a.Profile)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
await PopulatePerkSubscriptionsAsync(accounts);
var response = new GetAccountBatchResponse(); var response = new GetAccountBatchResponse();
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
return response; return response;
@@ -126,6 +141,9 @@ public class AccountServiceGrpc(
.Include(a => a.Profile) .Include(a => a.Profile)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
await PopulatePerkSubscriptionsAsync(accounts);
var response = new GetAccountBatchResponse(); var response = new GetAccountBatchResponse();
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
return response; return response;
@@ -140,6 +158,9 @@ public class AccountServiceGrpc(
.Include(a => a.Profile) .Include(a => a.Profile)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
await PopulatePerkSubscriptionsAsync(accounts);
var response = new GetAccountBatchResponse(); var response = new GetAccountBatchResponse();
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
return response; return response;
@@ -176,6 +197,9 @@ public class AccountServiceGrpc(
.Include(a => a.Profile) .Include(a => a.Profile)
.ToListAsync(); .ToListAsync();
// Populate PerkSubscriptions from Wallet service via gRPC
await PopulatePerkSubscriptionsAsync(accounts);
var response = new ListAccountsResponse var response = new ListAccountsResponse
{ {
TotalSize = totalCount, TotalSize = totalCount,
@@ -264,4 +288,56 @@ public class AccountServiceGrpc(
); );
return new BoolValue { Value = hasRelationship }; return new BoolValue { Value = hasRelationship };
} }
}
/// <summary>
/// Populates the PerkSubscription property for a single account by calling the Wallet service via gRPC.
/// </summary>
private async Task PopulatePerkSubscriptionAsync(SnAccount account)
{
try
{
var subscription = await remoteSubscription.GetPerkSubscription(account.Id);
if (subscription != null)
{
account.PerkSubscription = SnWalletSubscription.FromProtoValue(subscription).ToReference();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to populate PerkSubscription for account {AccountId} in gRPC service", account.Id);
}
}
/// <summary>
/// Populates the PerkSubscription property for multiple accounts by calling the Wallet service via gRPC.
/// </summary>
private async Task PopulatePerkSubscriptionsAsync(List<SnAccount> accounts)
{
if (accounts.Count == 0) return;
try
{
var accountIds = accounts.Select(a => a.Id).ToList();
var subscriptions = await remoteSubscription.GetPerkSubscriptions(accountIds);
var subscriptionDict = subscriptions
.Where(s => s != null)
.ToDictionary(
s => Guid.Parse(s.AccountId),
s => SnWalletSubscription.FromProtoValue(s).ToReference()
);
foreach (var account in accounts)
{
if (subscriptionDict.TryGetValue(account.Id, out var subscription))
{
account.PerkSubscription = subscription;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to populate PerkSubscriptions for {Count} accounts in gRPC service", accounts.Count);
}
}
}