using System.Text.RegularExpressions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Nuuru.Server.Extensions; using Nuuru.Server.Services; namespace Nuuru.Server.Hubs; public static partial class LiveUpdateGroups { public static string PostComments(int postId) => $"post-comments:{postId}"; public static string Thread(int threadId) => $"thread:{threadId}"; public static string Conversation(Guid conversationId) => $"conversation:{conversationId}"; [GeneratedRegex(@"^(post-comments|thread):\d+$|^conversation:[0-9a-fA-F\-]{36}$")] public static partial Regex ValidGroupPattern(); [GeneratedRegex(@"^conversation:([0-9a-fA-F\-]{36})$")] public static partial Regex ConversationGroupPattern(); } public static class LiveUpdateEvents { public const string Revalidate = "Revalidate"; } public static class LiveUpdateHubExtensions { public static Task NotifyGroupAsync(this IHubContext hub, string group) => hub.Clients.Group(group).SendAsync(LiveUpdateEvents.Revalidate); } [Authorize] public class LiveUpdateHub : Hub { private readonly IConversationService _conversationService; public LiveUpdateHub(IConversationService conversationService) { _conversationService = conversationService; } public async Task JoinGroup(string group) { if (!LiveUpdateGroups.ValidGroupPattern().IsMatch(group)) throw new HubException("Invalid group name."); // Conversation groups require participant check var conversationMatch = LiveUpdateGroups.ConversationGroupPattern().Match(group); if (conversationMatch.Success) { var userId = Context.User?.GetUserId(); if (userId == null) throw new HubException("Not authenticated."); var conversationId = Guid.Parse(conversationMatch.Groups[1].Value); if (!await _conversationService.IsParticipantAsync(conversationId, userId.Value)) throw new HubException("Not a participant."); } await Groups.AddToGroupAsync(Context.ConnectionId, group); } public Task LeaveGroup(string group) { if (!LiveUpdateGroups.ValidGroupPattern().IsMatch(group)) throw new HubException("Invalid group name."); return Groups.RemoveFromGroupAsync(Context.ConnectionId, group); } }