♻️ Move the chat part of the Sphere service to the Messager service
This commit is contained in:
239
DysonNetwork.Messager/Chat/ChatRoomService.cs
Normal file
239
DysonNetwork.Messager/Chat/ChatRoomService.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Messager.Chat;
|
||||
|
||||
public class ChatRoomService(
|
||||
AppDatabase db,
|
||||
ICacheService cache,
|
||||
RemoteAccountService remoteAccounts,
|
||||
RemoteRealmService remoteRealms
|
||||
)
|
||||
{
|
||||
private const string ChatRoomGroupPrefix = "chatroom:";
|
||||
private const string RoomMembersCacheKeyPrefix = "chatroom:members:";
|
||||
private const string ChatMemberCacheKey = "chatroom:{0}:member:{1}";
|
||||
|
||||
public async Task<List<SnChatMember>> ListRoomMembers(Guid roomId)
|
||||
{
|
||||
var cacheKey = RoomMembersCacheKeyPrefix + roomId;
|
||||
var cachedMembers = await cache.GetAsync<List<SnChatMember>>(cacheKey);
|
||||
if (cachedMembers != null)
|
||||
return cachedMembers;
|
||||
|
||||
var members = await db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == roomId)
|
||||
.Where(m => m.JoinedAt != null)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.ToListAsync();
|
||||
members = await LoadMemberAccounts(members);
|
||||
|
||||
var chatRoomGroup = ChatRoomGroupPrefix + roomId;
|
||||
await cache.SetWithGroupsAsync(cacheKey, members,
|
||||
[chatRoomGroup],
|
||||
TimeSpan.FromMinutes(5));
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
public async Task<SnChatMember?> GetRoomMember(Guid accountId, Guid chatRoomId)
|
||||
{
|
||||
var cacheKey = string.Format(ChatMemberCacheKey, accountId, chatRoomId);
|
||||
var member = await cache.GetAsync<SnChatMember?>(cacheKey);
|
||||
if (member is not null) return member;
|
||||
|
||||
member = await db.ChatMembers
|
||||
.Where(m => m.AccountId == accountId && m.ChatRoomId == chatRoomId && m.JoinedAt != null &&
|
||||
m.LeaveAt == null)
|
||||
.Include(m => m.ChatRoom)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (member == null) return member;
|
||||
|
||||
member = await LoadMemberAccount(member);
|
||||
var chatRoomGroup = ChatRoomGroupPrefix + chatRoomId;
|
||||
await cache.SetWithGroupsAsync(cacheKey, member,
|
||||
[chatRoomGroup],
|
||||
TimeSpan.FromMinutes(5));
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
public async Task PurgeRoomMembersCache(Guid roomId)
|
||||
{
|
||||
var chatRoomGroup = ChatRoomGroupPrefix + roomId;
|
||||
await cache.RemoveGroupAsync(chatRoomGroup);
|
||||
}
|
||||
|
||||
public async Task<List<SnChatRoom>> SortChatRoomByLastMessage(List<SnChatRoom> rooms)
|
||||
{
|
||||
var roomIds = rooms.Select(r => r.Id).ToList();
|
||||
var lastMessages = await db.ChatMessages
|
||||
.Where(m => roomIds.Contains(m.ChatRoomId))
|
||||
.GroupBy(m => m.ChatRoomId)
|
||||
.Select(g => new { RoomId = g.Key, CreatedAt = g.Max(m => m.CreatedAt) })
|
||||
.ToDictionaryAsync(g => g.RoomId, m => m.CreatedAt);
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var sortedRooms = rooms
|
||||
.OrderByDescending(r => lastMessages.TryGetValue(r.Id, out var time) ? time : now)
|
||||
.ToList();
|
||||
|
||||
return sortedRooms;
|
||||
}
|
||||
|
||||
public async Task<List<SnChatRoom>> LoadChatRealms(List<SnChatRoom> rooms)
|
||||
{
|
||||
var realmIds = rooms.Where(r => r.RealmId.HasValue).Select(r => r.RealmId!.Value.ToString()).Distinct().ToList();
|
||||
|
||||
var realms = await remoteRealms.GetRealmBatch(realmIds);
|
||||
var realmDict = realms.ToDictionary(r => r.Id, r => r);
|
||||
|
||||
foreach (var room in rooms)
|
||||
if (room.RealmId.HasValue && realmDict.TryGetValue(room.RealmId.Value, out var realm))
|
||||
room.Realm = realm;
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public async Task<SnChatRoom> LoadChatRealms(SnChatRoom room)
|
||||
{
|
||||
var result = await LoadChatRealms(new List<SnChatRoom> { room });
|
||||
return result[0];
|
||||
}
|
||||
|
||||
public async Task<List<SnChatRoom>> LoadDirectMessageMembers(List<SnChatRoom> rooms, Guid userId)
|
||||
{
|
||||
var directRoomsId = rooms
|
||||
.Where(r => r.Type == ChatRoomType.DirectMessage)
|
||||
.Select(r => r.Id)
|
||||
.ToList();
|
||||
if (directRoomsId.Count == 0) return rooms;
|
||||
|
||||
var members = directRoomsId.Count != 0
|
||||
? await db.ChatMembers
|
||||
.Where(m => directRoomsId.Contains(m.ChatRoomId))
|
||||
.Where(m => m.AccountId != userId)
|
||||
.ToListAsync()
|
||||
: [];
|
||||
members = await LoadMemberAccounts(members);
|
||||
|
||||
Dictionary<Guid, List<SnChatMember>> directMembers = new();
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!directMembers.ContainsKey(member.ChatRoomId))
|
||||
directMembers[member.ChatRoomId] = [];
|
||||
directMembers[member.ChatRoomId].Add(member);
|
||||
}
|
||||
|
||||
return rooms.Select(r =>
|
||||
{
|
||||
if (r.Type == ChatRoomType.DirectMessage && directMembers.TryGetValue(r.Id, out var otherMembers))
|
||||
r.DirectMembers = otherMembers.Select(ChatMemberTransmissionObject.FromEntity).ToList();
|
||||
return r;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<SnChatRoom> LoadDirectMessageMembers(SnChatRoom room, Guid userId)
|
||||
{
|
||||
if (room.Type != ChatRoomType.DirectMessage) return room;
|
||||
var members = await db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == room.Id && m.AccountId != userId)
|
||||
.ToListAsync();
|
||||
|
||||
if (members.Count <= 0) return room;
|
||||
|
||||
members = await LoadMemberAccounts(members);
|
||||
room.DirectMembers = members.Select(ChatMemberTransmissionObject.FromEntity).ToList();
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public async Task<bool> IsChatMember(Guid roomId, Guid accountId)
|
||||
{
|
||||
return await db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == roomId && m.AccountId == accountId && m.JoinedAt != null && m.LeaveAt == null)
|
||||
.AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<SnChatMember> LoadMemberAccount(SnChatMember member)
|
||||
{
|
||||
var account = await remoteAccounts.GetAccount(member.AccountId);
|
||||
member.Account = SnAccount.FromProtoValue(account);
|
||||
return member;
|
||||
}
|
||||
|
||||
public async Task<List<SnChatMember>> LoadMemberAccounts(ICollection<SnChatMember> members)
|
||||
{
|
||||
var accountIds = members.Select(m => m.AccountId).ToList();
|
||||
var accounts = (await remoteAccounts.GetAccountBatch(accountIds)).ToDictionary(a => Guid.Parse(a.Id), a => a);
|
||||
|
||||
return
|
||||
[
|
||||
.. members.Select(m =>
|
||||
{
|
||||
if (accounts.TryGetValue(m.AccountId, out var account))
|
||||
m.Account = SnAccount.FromProtoValue(account);
|
||||
return m;
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
private const string ChatRoomSubscribeKeyPrefix = "chatroom:subscribe:";
|
||||
|
||||
public async Task SubscribeChatRoom(SnChatMember member)
|
||||
{
|
||||
var cacheKey = $"{ChatRoomSubscribeKeyPrefix}{member.ChatRoomId}:{member.Id}";
|
||||
await cache.SetAsync(cacheKey, true, TimeSpan.FromHours(1));
|
||||
await cache.AddToGroupAsync(cacheKey, $"chatroom:subscribers:{member.ChatRoomId}");
|
||||
}
|
||||
|
||||
public async Task UnsubscribeChatRoom(SnChatMember member)
|
||||
{
|
||||
var cacheKey = $"{ChatRoomSubscribeKeyPrefix}{member.ChatRoomId}:{member.Id}";
|
||||
await cache.RemoveAsync(cacheKey);
|
||||
}
|
||||
|
||||
public async Task<bool> IsSubscribedChatRoom(Guid roomId, Guid memberId)
|
||||
{
|
||||
var cacheKey = $"{ChatRoomSubscribeKeyPrefix}{roomId}:{memberId}";
|
||||
var result = await cache.GetAsync<bool?>(cacheKey);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetSubscribedMembers(Guid roomId)
|
||||
{
|
||||
var group = $"chatroom:subscribers:{roomId}";
|
||||
var keys = (await cache.GetGroupKeysAsync(group)).ToList();
|
||||
|
||||
var memberIds = new List<Guid>(keys.Count);
|
||||
foreach (var key in keys)
|
||||
{
|
||||
var lastColonIndex = key.LastIndexOf(':');
|
||||
if (lastColonIndex >= 0 && Guid.TryParse(key.AsSpan(lastColonIndex + 1), out var memberId))
|
||||
{
|
||||
memberIds.Add(memberId);
|
||||
}
|
||||
}
|
||||
|
||||
return memberIds;
|
||||
}
|
||||
|
||||
public async Task<List<SnAccount>> GetTopActiveMembers(Guid roomId, Instant startDate, Instant endDate)
|
||||
{
|
||||
var topMembers = await db.ChatMessages
|
||||
.Where(m => m.ChatRoomId == roomId && m.CreatedAt >= startDate && m.CreatedAt < endDate)
|
||||
.GroupBy(m => m.Sender.AccountId)
|
||||
.Select(g => new { AccountId = g.Key, MessageCount = g.Count() })
|
||||
.OrderByDescending(g => g.MessageCount)
|
||||
.Take(3)
|
||||
.ToListAsync();
|
||||
|
||||
var accountIds = topMembers.Select(t => t.AccountId).ToList();
|
||||
var accounts = await remoteAccounts.GetAccountBatch(accountIds);
|
||||
return accounts.Select(SnAccount.FromProtoValue).ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user