diff --git a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs index e399eb4..d497c9d 100644 --- a/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs +++ b/DysonNetwork.Gateway/RegistryProxyConfigProvider.cs @@ -6,11 +6,13 @@ namespace DysonNetwork.Gateway; public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable { + private readonly object _lock = new(); private readonly IEtcdClient _etcdClient; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly CancellationTokenSource _watchCts = new(); - private CancellationTokenSource _cts = new(); + private CancellationTokenSource _cts; + private IProxyConfig _config; public RegistryProxyConfigProvider(IEtcdClient etcdClient, IConfiguration configuration, ILogger logger) @@ -18,19 +20,33 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable _etcdClient = etcdClient; _configuration = configuration; _logger = logger; + _cts = new CancellationTokenSource(); + _config = LoadConfig(); // Watch for changes in etcd _etcdClient.WatchRange("/services/", _ => { _logger.LogInformation("Etcd configuration changed. Reloading proxy config."); - _cts.Cancel(); - _cts = new CancellationTokenSource(); + ReloadConfig(); }, cancellationToken: _watchCts.Token); } - public IProxyConfig GetConfig() + public IProxyConfig GetConfig() => _config; + + private void ReloadConfig() + { + lock (_lock) + { + var oldCts = _cts; + _cts = new CancellationTokenSource(); + _config = LoadConfig(); + oldCts.Cancel(); + oldCts.Dispose(); + } + } + + private IProxyConfig LoadConfig() { - // This will be called by YARP when it needs a new config _logger.LogInformation("Generating new proxy config."); var response = _etcdClient.GetRange("/services/"); var kvs = response.Kvs; @@ -184,17 +200,15 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable _logger.LogInformation(" Added Path-based Route: {Path}", pathRoute.Match.Path); } - return new CustomProxyConfig(routes, clusters); + return new CustomProxyConfig(routes, clusters, new Microsoft.Extensions.Primitives.CancellationChangeToken(_cts.Token)); } - private class CustomProxyConfig(IReadOnlyList routes, IReadOnlyList clusters) + private class CustomProxyConfig(IReadOnlyList routes, IReadOnlyList clusters, Microsoft.Extensions.Primitives.IChangeToken changeToken) : IProxyConfig { public IReadOnlyList Routes { get; } = routes; public IReadOnlyList Clusters { get; } = clusters; - - public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = - new Microsoft.Extensions.Primitives.CancellationChangeToken(CancellationToken.None); + public Microsoft.Extensions.Primitives.IChangeToken ChangeToken { get; } = changeToken; } public record DirectRouteConfig @@ -211,4 +225,4 @@ public class RegistryProxyConfigProvider : IProxyConfigProvider, IDisposable _watchCts.Cancel(); _watchCts.Dispose(); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Pusher/Notification/PushService.cs b/DysonNetwork.Pusher/Notification/PushService.cs index 5a859e0..0d605e9 100644 --- a/DysonNetwork.Pusher/Notification/PushService.cs +++ b/DysonNetwork.Pusher/Notification/PushService.cs @@ -71,7 +71,7 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto string? title = null, string? subtitle = null, string? content = null, - Dictionary? meta = null, + Dictionary meta = null, string? actionUri = null, bool isSilent = false, bool save = true) @@ -79,7 +79,6 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto if (title is null && subtitle is null && content is null) throw new ArgumentException("Unable to send notification that completely empty."); - meta ??= new Dictionary(); if (actionUri is not null) meta["action_uri"] = actionUri; var accountId = Guid.Parse(account.Id!); @@ -102,7 +101,7 @@ public class PushService(IConfiguration config, AppDatabase db, IHttpClientFacto if (!isSilent) _ = DeliveryNotification(notification); } - public async Task DeliveryNotification(Pusher.Notification.Notification notification) + public async Task DeliveryNotification(Notification notification) { // Pushing the notification var subscribers = await db.PushSubscriptions diff --git a/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs index 0ff7d05..e83d418 100644 --- a/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs +++ b/DysonNetwork.Pusher/Services/PusherServiceGrpc.cs @@ -2,6 +2,7 @@ using DysonNetwork.Pusher.Connection; using DysonNetwork.Pusher.Email; using DysonNetwork.Pusher.Notification; using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Registry; using Google.Protobuf.WellKnownTypes; using Grpc.Core; @@ -10,7 +11,8 @@ namespace DysonNetwork.Pusher.Services; public class PusherServiceGrpc( EmailService emailService, WebSocketService websocket, - PushService pushService + PushService pushService, + AccountClientHelper accountsHelper ) : PusherService.PusherServiceBase { public override async Task SendEmail(SendEmailRequest request, ServerCallContext context) @@ -79,56 +81,10 @@ public class PusherServiceGrpc( return Task.FromResult(new Empty()); } - public override async Task SendPushNotification(SendPushNotificationRequest request, - ServerCallContext context) - { - // This is a placeholder implementation. In a real-world scenario, you would - // need to retrieve the account from the database based on the device token. - var account = new Account(); - await pushService.SendNotification( - account, - request.Notification.Topic, - request.Notification.Title, - request.Notification.Subtitle, - request.Notification.Body, - GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), - request.Notification.ActionUri, - request.Notification.IsSilent, - request.Notification.IsSavable - ); - return new Empty(); - } - - public override async Task SendPushNotificationToDevices(SendPushNotificationToDevicesRequest request, - ServerCallContext context) - { - // This is a placeholder implementation. In a real-world scenario, you would - // need to retrieve the accounts from the database based on the device tokens. - var account = new Account(); - foreach (var deviceId in request.DeviceIds) - { - await pushService.SendNotification( - account, - request.Notification.Topic, - request.Notification.Title, - request.Notification.Subtitle, - request.Notification.Body, - GrpcTypeHelper.ConvertFromValueMap(request.Notification.Meta), - request.Notification.ActionUri, - request.Notification.IsSilent, - request.Notification.IsSavable - ); - } - - return new Empty(); - } - public override async Task SendPushNotificationToUser(SendPushNotificationToUserRequest request, ServerCallContext context) { - // This is a placeholder implementation. In a real-world scenario, you would - // need to retrieve the account from the database based on the user ID. - var account = new Account(); + var account = await accountsHelper.GetAccount(Guid.Parse(request.UserId)); await pushService.SendNotification( account, request.Notification.Topic, diff --git a/DysonNetwork.Shared/Cache/CacheService.cs b/DysonNetwork.Shared/Cache/CacheService.cs index 42cb21a..3ab8fcc 100644 --- a/DysonNetwork.Shared/Cache/CacheService.cs +++ b/DysonNetwork.Shared/Cache/CacheService.cs @@ -206,7 +206,8 @@ public class CacheServiceRedis : ICacheService ContractResolver = new CamelCasePropertyNamesContractResolver(), PreserveReferencesHandling = PreserveReferencesHandling.None, NullValueHandling = NullValueHandling.Include, - DateParseHandling = DateParseHandling.None + DateParseHandling = DateParseHandling.None, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; // Configure NodaTime serializers diff --git a/DysonNetwork.Shared/Proto/pusher.proto b/DysonNetwork.Shared/Proto/pusher.proto index bed1e02..52111cc 100644 --- a/DysonNetwork.Shared/Proto/pusher.proto +++ b/DysonNetwork.Shared/Proto/pusher.proto @@ -27,12 +27,6 @@ service PusherService { // Pushes a packet to a list of devices via WebSocket. rpc PushWebSocketPacketToDevices(PushWebSocketPacketToDevicesRequest) returns (google.protobuf.Empty) {} - // Sends a push notification to a device. - rpc SendPushNotification(SendPushNotificationRequest) returns (google.protobuf.Empty) {} - - // Sends a push notification to a list of devices. - rpc SendPushNotificationToDevices(SendPushNotificationToDevicesRequest) returns (google.protobuf.Empty) {} - // Sends a push notification to a user. rpc SendPushNotificationToUser(SendPushNotificationToUserRequest) returns (google.protobuf.Empty) {} @@ -60,9 +54,9 @@ message SendEmailRequest { // Represents a WebSocket packet. message WebSocketPacket { - string type = 1; - google.protobuf.Value data = 2; - google.protobuf.StringValue error_message = 3; + string type = 1; + google.protobuf.Value data = 2; + google.protobuf.StringValue error_message = 3; } message PushWebSocketPacketRequest { @@ -87,49 +81,39 @@ message PushWebSocketPacketToDevicesRequest { // Represents a push notification. message PushNotification { - string topic = 1; - string title = 2; - string subtitle = 3; - string body = 4; - map meta = 5; - optional string action_uri = 6; - bool is_silent = 7; - bool is_savable = 8; -} - -message SendPushNotificationRequest { - string device_id = 1; - PushNotification notification = 2; -} - -message SendPushNotificationToDevicesRequest { - repeated string device_ids = 1; - PushNotification notification = 2; + string topic = 1; + string title = 2; + string subtitle = 3; + string body = 4; + map meta = 5; + optional string action_uri = 6; + bool is_silent = 7; + bool is_savable = 8; } message SendPushNotificationToUserRequest { - string user_id = 1; - PushNotification notification = 2; + string user_id = 1; + PushNotification notification = 2; } message SendPushNotificationToUsersRequest { - repeated string user_ids = 1; - PushNotification notification = 2; + repeated string user_ids = 1; + PushNotification notification = 2; } message UnsubscribePushNotificationsRequest { - string device_id = 1; + string device_id = 1; } message GetWebsocketConnectionStatusRequest { - oneof id { - string device_id = 1; - string user_id = 2; - } + oneof id { + string device_id = 1; + string user_id = 2; + } } message GetWebsocketConnectionStatusResponse { - bool is_connected = 1; + bool is_connected = 1; } diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index 49cc663..d0ab923 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -100,6 +100,12 @@ public partial class ChatController( .Skip(offset) .Take(take) .ToListAsync(); + + var members = messages.Select(m => m.Sender).DistinctBy(x => x.Id).ToList(); + members = await crs.LoadMemberAccounts(members); + + foreach (var message in messages) + message.Sender = members.First(x => x.Id == message.SenderId); Response.Headers["X-Total"] = totalCount.ToString(); diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index aec8e5b..0a4af47 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -48,9 +48,9 @@ public class ChatRoomService( .Where(m => m.AccountId == accountId && m.ChatRoomId == chatRoomId) .Include(m => m.ChatRoom) .FirstOrDefaultAsync(); - + if (member == null) return member; - + member = await LoadMemberAccount(member); var chatRoomGroup = ChatRoomGroupPrefix + chatRoomId; await cache.SetWithGroupsAsync(cacheKey, member, @@ -91,14 +91,22 @@ public class ChatRoomService( .ToList(); if (directRoomsId.Count == 0) return rooms; - var directMembers = directRoomsId.Count != 0 + List members = directRoomsId.Count != 0 ? await db.ChatMembers .Where(m => directRoomsId.Contains(m.ChatRoomId)) .Where(m => m.AccountId != userId) .Where(m => m.LeaveAt == null) - .GroupBy(m => m.ChatRoomId) - .ToDictionaryAsync(g => g.Key, g => g.ToList()) - : new Dictionary>(); + .ToListAsync() + : []; + members = await LoadMemberAccounts(members); + + Dictionary> directMembers = new(); + foreach (var member in members) + { + if (!directMembers.ContainsKey(member.ChatRoomId)) + directMembers[member.ChatRoomId] = []; + directMembers[member.ChatRoomId].Add(member); + } return rooms.Select(r => { diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index 3139182..df0157c 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -58,7 +58,7 @@ public class RealmController( var members = await db.RealmMembers .Where(m => m.AccountId == accountId) - .Where(m => m.JoinedAt == null) + .Where(m => m.JoinedAt == null && m.LeaveAt == null) .Include(e => e.Realm) .ToListAsync();