diff --git a/DysonNetwork.Pass/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs index 1bab0e35..f8a47c60 100644 --- a/DysonNetwork.Pass/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -4,6 +4,7 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Networking; using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Registry; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -22,7 +23,8 @@ public class AccountCurrentController( AccountEventService events, AuthService auth, FileService.FileServiceClient files, - Credit.SocialCreditService creditService + Credit.SocialCreditService creditService, + RemoteSubscriptionService remoteSubscription ) : ControllerBase { [HttpGet] @@ -39,6 +41,24 @@ public class AccountCurrentController( .Where(e => e.Id == userId) .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); } @@ -321,7 +341,10 @@ public class AccountCurrentController( } 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( message: "You need to have a subscription to check-in backdated.", forbidden: true, diff --git a/DysonNetwork.Pass/Account/AccountPublicController.cs b/DysonNetwork.Pass/Account/AccountPublicController.cs index 283a58eb..f8ad77a2 100644 --- a/DysonNetwork.Pass/Account/AccountPublicController.cs +++ b/DysonNetwork.Pass/Account/AccountPublicController.cs @@ -1,6 +1,7 @@ using DysonNetwork.Pass.Credit; using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Networking; +using DysonNetwork.Shared.Registry; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -10,7 +11,8 @@ namespace DysonNetwork.Pass.Account; [Route("/api/accounts")] public class AccountPublicController( AppDatabase db, - SocialCreditService socialCreditService + SocialCreditService socialCreditService, + RemoteSubscriptionService remoteSubscription ) : ControllerBase { [HttpGet("{name}")] @@ -26,6 +28,21 @@ public class AccountPublicController( .FirstOrDefaultAsync(); 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; } @@ -67,11 +84,44 @@ public class AccountPublicController( { if (string.IsNullOrWhiteSpace(query)) return []; - return await db.Accounts + + var accounts = await db.Accounts .Include(e => e.Profile) .Where(a => EF.Functions.ILike(a.Name, $"%{query}%") || EF.Functions.ILike(a.Nick, $"%{query}%")) .Take(take) .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; } } diff --git a/DysonNetwork.Pass/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs index 495aa89d..879615dc 100644 --- a/DysonNetwork.Pass/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -16,6 +16,7 @@ using NATS.Net; using NodaTime; using OtpNet; using AuthService = DysonNetwork.Pass.Auth.AuthService; +using DysonNetwork.Shared.Registry; namespace DysonNetwork.Pass.Account; @@ -31,6 +32,7 @@ public class AccountService( IStringLocalizer emailLocalizer, ICacheService cache, ILogger logger, + RemoteSubscriptionService remoteSubscription, INatsConnection nats ) { @@ -757,4 +759,56 @@ public class AccountService( }).ToByteArray() ); } -} \ No newline at end of file + + /// + /// Populates the PerkSubscription property for a single account by calling the Wallet service via gRPC. + /// + 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); + } + } + + /// + /// Populates the PerkSubscription property for multiple accounts by calling the Wallet service via gRPC. + /// + public async Task PopulatePerkSubscriptionsAsync(List 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); + } + } +} diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 4a23c923..96a79fef 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -1,4 +1,6 @@ +using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Registry; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using Microsoft.EntityFrameworkCore; @@ -10,6 +12,7 @@ public class AccountServiceGrpc( AppDatabase db, AccountEventService accountEvents, RelationshipService relationships, + RemoteSubscriptionService remoteSubscription, ILogger logger ) : Shared.Proto.AccountService.AccountServiceBase @@ -33,6 +36,9 @@ public class AccountServiceGrpc( if (account == null) 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(); } @@ -51,6 +57,9 @@ public class AccountServiceGrpc( throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account with automated ID {request.AutomatedId} not found")); + // Populate PerkSubscription from Wallet service via gRPC + await PopulatePerkSubscriptionAsync(account); + return account.ToProtoValue(); } @@ -69,6 +78,9 @@ public class AccountServiceGrpc( .Include(a => a.Profile) .ToListAsync(); + // Populate PerkSubscriptions from Wallet service via gRPC + await PopulatePerkSubscriptionsAsync(accounts); + var response = new GetAccountBatchResponse(); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); return response; @@ -90,6 +102,9 @@ public class AccountServiceGrpc( .Include(a => a.Profile) .ToListAsync(); + // Populate PerkSubscriptions from Wallet service via gRPC + await PopulatePerkSubscriptionsAsync(accounts); + var response = new GetAccountBatchResponse(); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); return response; @@ -126,6 +141,9 @@ public class AccountServiceGrpc( .Include(a => a.Profile) .ToListAsync(); + // Populate PerkSubscriptions from Wallet service via gRPC + await PopulatePerkSubscriptionsAsync(accounts); + var response = new GetAccountBatchResponse(); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); return response; @@ -140,6 +158,9 @@ public class AccountServiceGrpc( .Include(a => a.Profile) .ToListAsync(); + // Populate PerkSubscriptions from Wallet service via gRPC + await PopulatePerkSubscriptionsAsync(accounts); + var response = new GetAccountBatchResponse(); response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); return response; @@ -176,6 +197,9 @@ public class AccountServiceGrpc( .Include(a => a.Profile) .ToListAsync(); + // Populate PerkSubscriptions from Wallet service via gRPC + await PopulatePerkSubscriptionsAsync(accounts); + var response = new ListAccountsResponse { TotalSize = totalCount, @@ -264,4 +288,56 @@ public class AccountServiceGrpc( ); return new BoolValue { Value = hasRelationship }; } -} \ No newline at end of file + + /// + /// Populates the PerkSubscription property for a single account by calling the Wallet service via gRPC. + /// + 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); + } + } + + /// + /// Populates the PerkSubscription property for multiple accounts by calling the Wallet service via gRPC. + /// + private async Task PopulatePerkSubscriptionsAsync(List 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); + } + } +}