using Govor.API.Common.SignalR.Helpers; using Govor.Application.Friends; using Govor.Application.Medias; using Govor.Application.Profiles; using Govor.Application.Synching; using Govor.Contracts.DTOs; using Govor.Contracts.Responses.SignalR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; namespace Govor.API.Hubs; [Authorize] public class ProfileHub : Hub { private readonly IFriendshipService _friendsService; private readonly IProfileService _profileService; private readonly IHubUserAccessor _userAccessor; private readonly ISynchingService _synchingService; private readonly ILogger _logger; private readonly IMediaService _mediaService; public ProfileHub( IFriendshipService friendsService, IProfileService profileService, IHubUserAccessor userAccessor, ISynchingService synchingService, IMediaService mediaService, ILogger logger) { _friendsService = friendsService; _profileService = profileService; _userAccessor = userAccessor; _synchingService = synchingService; _mediaService = mediaService; _logger = logger; } public override async Task OnConnectedAsync() { var userId = _userAccessor.GetUserId(Context); await Groups.AddToGroupAsync(Context.ConnectionId, $"user:{userId}"); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { var userId = _userAccessor.GetUserId(Context); await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user-{userId}"); await base.OnDisconnectedAsync(exception); } public async Task> SetDescription(string description) { if (description.Length > 500) return HubResult.Error("Description length exceeded."); var userId = _userAccessor.GetUserId(Context); try { description = _synchingService.NormalizeNewlines(description); await _profileService.SetDescription(description, userId); var payload = new DescriptionUpdatePayload { UserId = userId, Description = description }; await NotifyProfileUpdatedAsync(userId, "DescriptionUpdated", payload); return HubResult.Ok(true); } catch (Exception ex) { _logger.LogError(ex, "Failed to update description for user {UserId}", userId); return HubResult.Error("Server error."); } } public async Task> SetAvatar(Guid iconId) { var userId = _userAccessor.GetUserId(Context); try { if (iconId == Guid.Empty || !await _mediaService.HasMediaAsync(iconId)) return HubResult.BadRequest("Invalid icon id."); await _profileService.SetNewIcon(userId, iconId); var payload = new AvatarUpdatePayload(){UserId = userId, IconId = iconId}; await NotifyProfileUpdatedAsync(userId, "AvatarUpdated", payload); return HubResult.Ok(true); } catch (Exception ex) { _logger.LogError(ex, "An error occurred while updating the user's {userId} avatar {iconId}", userId, iconId); return HubResult.Error("An unaccounted error on the server!"); } } private async Task NotifyProfileUpdatedAsync( Guid UserId, string eventName, object payload) { try { var recipients = new HashSet(); // 1. Friends try { var friends = await _friendsService.GetFriendsAsync(UserId); recipients.UnionWith(friends.Select(f => f.Id)); } catch (Exception ex) { _logger.LogWarning(ex, "User has no friends for user {userId}", UserId); } // 2. (pending) try { var potentialFriends = await _friendsService.GetPotentialFriendsAsync(UserId); recipients.UnionWith(potentialFriends.Select(p => p.Id)); } catch (Exception ex) { _logger.LogWarning(ex, "User has potential friends for user {userId}", UserId); } // 3. groups /*var groups = await _groupsRepository.GetByUserIdAsync(authorUserId); foreach (var group in groups) { var members = await _groupsRepository.Get(group.Id); recipients.UnionWith(members.Select(m => m.Id)); }*/ recipients.Add(UserId); // 5. // recipients.RemoveWhere(id => // !_profileVisibilityService.CanViewProfile(id, authorUserId)); // 6. 1 user = 1 event var recipientsStrings = recipients.Select(r => $"user:{r}").ToList(); foreach (var recipientId in recipients) { await Clients.Groups(recipientsStrings) .SendAsync(eventName, payload); } } catch (Exception ex) { _logger.LogError(ex, "Failed to notify profile update for user {UserId}", UserId); } } }