using qtc_api.Services.RoomService; using System.Text.Json; namespace qtc_api.Hubs { public class ChatHub : Hub { private IUserService _userService; private IRoomService _roomService; private ILogger _logger; private DataContext _dataContext; private ServerConfig _serverConfig; public static readonly Dictionary> GroupUsers = []; public static readonly Dictionary> ConnectedUsers = []; public static readonly List OnlineUsers = []; private static User ServerUser = new() { Id = "0", Username = "Server", }; public ChatHub(IUserService userService, IRoomService roomService, ILogger logger, DataContext dataContext, ServerConfig serverConfig) { _userService = userService; _roomService = roomService; _logger = logger; _dataContext = dataContext; _serverConfig = serverConfig; } public async override Task OnConnectedAsync() { Log("Client Connected To Hub"); var uid = Context.UserIdentifier; if (uid != null) { var user = await _userService.GetUserById(uid); if (user != null && user.Success && user.Data != null) { var userId = user.Data.Id; lock (ConnectedUsers) { if (!ConnectedUsers.TryGetValue(userId, out List? value)) { value = []; ConnectedUsers[userId] = value; } value.Add(Context.ConnectionId); } if (!OnlineUsers.Any(e => e.Id == userId)) { Log("Setting User Online..."); OnlineUsers.Add(user.Data); if (user.Data.Status == 0) await UpdateStatusAsync(user.Data, 1); await Clients.Client(Context.ConnectionId).SendAsync("RefreshRoomList"); await Clients.Client(Context.ConnectionId).SendAsync("ReceiveServerConfig", _serverConfig); } } } } public override async Task OnDisconnectedAsync(Exception? ex) { Log("Client Disconnected From Hub"); var uid = Context.UserIdentifier; if (uid != null) { var user = await _userService.GetUserById(uid); if (user != null && user.Success && user.Data != null) { var userId = user.Data.Id; lock (ConnectedUsers) { if (ConnectedUsers.TryGetValue(userId, out List? value)) { value.Remove(Context.ConnectionId); } } if (ConnectedUsers[userId].Count == 0) { ConnectedUsers.Remove(userId); if (OnlineUsers.Any(e => e.Id == userId)) { // set user offline if there aren't any more connections var onlineUser = OnlineUsers.FirstOrDefault(e => e.Id == userId); if (onlineUser != null) { Log("User Has No More Connections. Setting Offline..."); OnlineUsers.Remove(onlineUser); if (user.Data.Status >= 1) await UpdateStatusAsync(onlineUser, 0); } // also remove the user from any rooms var room = await _roomService.GetRoom(user.Data.CurrentRoomId); if (room.Data != null) await LeaveRoomAsync(user.Data, room.Data); } } } } } [HubMethodName("UpdateStatus")] [Authorize] public async Task UpdateStatusAsync(User user, int status) { var statusDto = new UserStatusDto { Id = user.Id, Status = status }; Log($"Updating Status\n{JsonSerializer.Serialize(statusDto)}"); var res = await _userService.UpdateStatus(statusDto); if (res != null && res.Success && res.Data != null) { // send UserUpdate to all clients UserInformationDto userInfoDto = new(); userInfoDto.MapToUser(user); await Clients.All.SendAsync("UserUpdate", userInfoDto); Log($"Status Was Set To {res.Data.Status} On User {user.Username}"); await Clients.Client(Context.ConnectionId).SendAsync("UpdateCurrentUser"); } else Log($"Something Went Wrong Setting The Status On User {user.Username}"); } [HubMethodName("JoinRoom")] [Authorize] public async Task JoinRoomAsync(User user, Room room) { await Groups.AddToGroupAsync(Context.ConnectionId, room.Id); await Clients.Group(room.Id).SendAsync("RoomMessage", ServerUser, $"User {user.Username} Has Joined {room.Name}", room); if (!GroupUsers.TryGetValue(room.Id, out _)) { GroupUsers.Add(room.Id, new List()); } GroupUsers[room.Id].Add(user); await Clients.Group(room.Id).SendAsync("RoomUserList", room, GroupUsers[room.Id]); Log($"User {user.Username} Has Joined {room.Name}"); user.CurrentRoomId = room.Id; ServiceResponse dbRoomRes = await _roomService.GetRoom(room.Id); Room? dbRoom = dbRoomRes?.Data; if (dbRoom != null) dbRoom.UserCount += 1; await _dataContext.SaveChangesAsync(); await Clients.All.SendAsync("RefreshRoomList"); } [HubMethodName("JoinRoomGuest")] public async Task JoinRoomGuestAsync(string roomId, string username) { // here we can just add the client to the room group and call it a day since the user isn't authenticated var room = await _roomService.GetRoom(roomId); if (room != null && room.Success && room.Data != null) { await Groups.AddToGroupAsync(Context.ConnectionId, room.Data.Id); await Clients.Group(room.Data.Id).SendAsync("RoomMessage", ServerUser, $"Guest User {username} Has Joined {room.Data.Name}", room); room.Data.UserCount += 1; await _dataContext.SaveChangesAsync(); await Clients.All.SendAsync("RefreshRoomList"); } } [HubMethodName("LeaveRoom")] [Authorize] public async Task LeaveRoomAsync(User user, Room room) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.Id); await Clients.Group(room.Id).SendAsync("RoomMessage", ServerUser, $"User {user.Username} Has Left {room.Name}", room); if (GroupUsers.TryGetValue(room.Id, out _)) GroupUsers[room.Id].Remove(GroupUsers[room.Id].FirstOrDefault(e => e.Id == user.Id)!); await Clients.Group(room.Id).SendAsync("RoomUserList", room, GroupUsers[room.Id]); Log($"User {user.Username} Has Left {room.Name}"); user.CurrentRoomId = string.Empty; ServiceResponse dbRoomRes = await _roomService.GetRoom(room.Id); Room? dbRoom = dbRoomRes?.Data; if (dbRoom != null) dbRoom.UserCount -= 1; await _dataContext.SaveChangesAsync(); await Clients.All.SendAsync("RefreshRoomList"); } [HubMethodName("SendMessage")] [Authorize] public async Task SendMessageAsync(User user, Message message, Room room) { await Clients.Group(room.Id).SendAsync("RoomMessage", user, $"{message.Content}", room); } [HubMethodName("SendDirectMessage")] [Authorize] public async Task SendDirectMessageAsync(User user, UserInformationDto userToMsg, Message message) { // send direct message directly to connected user if (ConnectedUsers.TryGetValue(userToMsg.Id, out List? value)) { var connection = value.FirstOrDefault(); if (connection != null) { UserInformationDto userInformationDto = new UserInformationDto { Id = user.Id, Username = user.Username, Bio = user.Bio, Role = user.Role, Status = user.Status, CreatedAt = user.CreatedAt, DateOfBirth = user.DateOfBirth, ProfilePicture = user.ProfilePicture, ProfileCosmeticId = user.ActiveProfileCosmetic }; await Clients.Client(connection).SendAsync("ReceiveDirectMessage", message, userInformationDto); return; } } } private void Log(string message) => _logger.LogInformation(message); } }