Ajoute les amis et les invitations temps reel
This commit is contained in:
168
ChessCubing.Server/Social/SocialHub.cs
Normal file
168
ChessCubing.Server/Social/SocialHub.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using ChessCubing.Server.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ChessCubing.Server.Social;
|
||||
|
||||
[Authorize]
|
||||
public sealed class SocialHub(
|
||||
ConnectedUserTracker tracker,
|
||||
MySqlSocialStore socialStore,
|
||||
MySqlUserProfileStore profileStore,
|
||||
PlayInviteCoordinator playInviteCoordinator) : Hub
|
||||
{
|
||||
private readonly ConnectedUserTracker _tracker = tracker;
|
||||
private readonly MySqlSocialStore _socialStore = socialStore;
|
||||
private readonly MySqlUserProfileStore _profileStore = profileStore;
|
||||
private readonly PlayInviteCoordinator _playInviteCoordinator = playInviteCoordinator;
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var siteUser = RequireCurrentUser();
|
||||
var becameOnline = _tracker.TrackConnection(Context.ConnectionId, siteUser.Subject);
|
||||
|
||||
await SendPresenceSnapshotAsync(siteUser.Subject, Context.ConnectionAborted);
|
||||
|
||||
if (becameOnline)
|
||||
{
|
||||
var relevantSubjects = await _socialStore.GetRelevantPresenceSubjectsAsync(siteUser.Subject, Context.ConnectionAborted);
|
||||
await NotifyPresenceChangedAsync(relevantSubjects, siteUser.Subject, isOnline: true);
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var (subject, becameOffline) = _tracker.RemoveConnection(Context.ConnectionId);
|
||||
if (becameOffline && !string.IsNullOrWhiteSpace(subject))
|
||||
{
|
||||
var relevantSubjects = await _socialStore.GetRelevantPresenceSubjectsAsync(subject, CancellationToken.None);
|
||||
await NotifyPresenceChangedAsync(relevantSubjects, subject, isOnline: false);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public async Task RequestPresenceSnapshot()
|
||||
{
|
||||
var siteUser = RequireCurrentUser();
|
||||
await SendPresenceSnapshotAsync(siteUser.Subject, Context.ConnectionAborted);
|
||||
}
|
||||
|
||||
public async Task<PlayInviteMessage> SendPlayInvite(string recipientSubject, string recipientColor)
|
||||
{
|
||||
var sender = RequireCurrentUser();
|
||||
var normalizedRecipientSubject = recipientSubject.Trim();
|
||||
|
||||
if (!await _socialStore.AreFriendsAsync(sender.Subject, normalizedRecipientSubject, Context.ConnectionAborted))
|
||||
{
|
||||
throw new HubException("Seuls tes amis peuvent recevoir une invitation de partie.");
|
||||
}
|
||||
|
||||
if (!_tracker.IsOnline(normalizedRecipientSubject))
|
||||
{
|
||||
throw new HubException("Cet ami n'est plus connecte pour le moment.");
|
||||
}
|
||||
|
||||
var senderProfile = await _profileStore.GetOrCreateAsync(sender, Context.ConnectionAborted);
|
||||
var recipientProfile = await _profileStore.FindBySubjectAsync(normalizedRecipientSubject, Context.ConnectionAborted);
|
||||
if (recipientProfile is null)
|
||||
{
|
||||
throw new HubException("Le profil de cet ami est introuvable.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var message = _playInviteCoordinator.CreateInvite(
|
||||
new PlayInviteParticipant(sender.Subject, sender.Username, senderProfile.DisplayName),
|
||||
new PlayInviteParticipant(recipientProfile.Subject, recipientProfile.Username, recipientProfile.DisplayName),
|
||||
recipientColor);
|
||||
|
||||
await Clients.Users([sender.Subject, recipientProfile.Subject])
|
||||
.SendAsync("PlayInviteUpdated", message, Context.ConnectionAborted);
|
||||
|
||||
return message;
|
||||
}
|
||||
catch (SocialValidationException exception)
|
||||
{
|
||||
throw new HubException(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RespondToPlayInvite(string inviteId, bool accept)
|
||||
{
|
||||
var recipient = RequireCurrentUser();
|
||||
|
||||
try
|
||||
{
|
||||
if (accept)
|
||||
{
|
||||
var accepted = _playInviteCoordinator.AcceptInvite(inviteId, recipient.Subject);
|
||||
await Clients.Users([accepted.SenderSubject, accepted.RecipientSubject])
|
||||
.SendAsync("PlayInviteAccepted", accepted.Session, Context.ConnectionAborted);
|
||||
return;
|
||||
}
|
||||
|
||||
var declined = _playInviteCoordinator.DeclineInvite(inviteId, recipient.Subject);
|
||||
await Clients.Users([declined.SenderSubject, declined.RecipientSubject])
|
||||
.SendAsync("PlayInviteClosed", declined.ClosedMessage, Context.ConnectionAborted);
|
||||
}
|
||||
catch (SocialValidationException exception)
|
||||
{
|
||||
throw new HubException(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CancelPlayInvite(string inviteId)
|
||||
{
|
||||
var sender = RequireCurrentUser();
|
||||
|
||||
try
|
||||
{
|
||||
var cancelled = _playInviteCoordinator.CancelInvite(inviteId, sender.Subject);
|
||||
await Clients.Users([cancelled.SenderSubject, cancelled.RecipientSubject])
|
||||
.SendAsync("PlayInviteClosed", cancelled.ClosedMessage, Context.ConnectionAborted);
|
||||
}
|
||||
catch (SocialValidationException exception)
|
||||
{
|
||||
throw new HubException(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticatedSiteUser RequireCurrentUser()
|
||||
=> AuthenticatedSiteUserFactory.FromClaimsPrincipal(Context.User ?? new System.Security.Claims.ClaimsPrincipal())
|
||||
?? throw new HubException("La session utilisateur est incomplete.");
|
||||
|
||||
private async Task SendPresenceSnapshotAsync(string subject, CancellationToken cancellationToken)
|
||||
{
|
||||
var relevantSubjects = await _socialStore.GetRelevantPresenceSubjectsAsync(subject, cancellationToken);
|
||||
var onlineSubjects = relevantSubjects
|
||||
.Where(_tracker.IsOnline)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
await Clients.Caller.SendAsync(
|
||||
"PresenceSnapshot",
|
||||
new PresenceSnapshotMessage { OnlineSubjects = onlineSubjects },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private Task NotifyPresenceChangedAsync(IReadOnlyList<string> subjects, string changedSubject, bool isOnline)
|
||||
{
|
||||
var distinctSubjects = subjects
|
||||
.Where(subject => !string.IsNullOrWhiteSpace(subject))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
return distinctSubjects.Length == 0
|
||||
? Task.CompletedTask
|
||||
: Clients.Users(distinctSubjects).SendAsync(
|
||||
"PresenceChanged",
|
||||
new PresenceChangedMessage
|
||||
{
|
||||
Subject = changedSubject,
|
||||
IsOnline = isOnline,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user