diff --git a/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessage.cs b/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessage.cs new file mode 100644 index 0000000..7ca3eb0 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessage.cs @@ -0,0 +1,6 @@ +using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; + +namespace AipsCore.Application.Common.Message.UserCanceledRequestToJoin; + +public record UserCanceledRequestToJoinMessage(UserCanceledRequestToJoinCommand Command): IMessage; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessageHandler.cs b/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessageHandler.cs new file mode 100644 index 0000000..9a35815 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/UserCanceledRequestToJoin/UserCanceledRequestToJoinMessageHandler.cs @@ -0,0 +1,19 @@ +using AipsCore.Application.Abstract; +using AipsCore.Application.Abstract.MessageBroking; + +namespace AipsCore.Application.Common.Message.UserCanceledRequestToJoin; + +public class UserCanceledRequestToJoinMessageHandler : IMessageHandler +{ + private readonly IDispatcher _dispatcher; + + public UserCanceledRequestToJoinMessageHandler(IDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task Handle(UserCanceledRequestToJoinMessage message, CancellationToken cancellationToken) + { + await _dispatcher.Execute(message.Command, cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommand.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommand.cs new file mode 100644 index 0000000..b169f89 --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommand.cs @@ -0,0 +1,5 @@ +using AipsCore.Application.Abstract.Command; + +namespace AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; + +public record UserCanceledRequestToJoinCommand(string WhiteboardId, string UserId): ICommand; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommandHandler.cs new file mode 100644 index 0000000..b307915 --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UserCanceledRequestToJoin/UserCanceledRequestToJoinCommandHandler.cs @@ -0,0 +1,40 @@ +using AipsCore.Application.Abstract.Command; +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Models.User.ValueObjects; +using AipsCore.Domain.Models.Whiteboard.ValueObjects; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; +using AipsCore.Domain.Models.WhiteboardMembership.External; +using AipsCore.Domain.Models.WhiteboardMembership.Validation; + +namespace AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; + +public class CancelJoinRequestCommandHandler : ICommandHandler +{ + private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; + private readonly IUnitOfWork _unitOfWork; + + public CancelJoinRequestCommandHandler(IWhiteboardMembershipRepository whiteboardMembershipRepository, IUnitOfWork unitOfWork) + { + _whiteboardMembershipRepository = whiteboardMembershipRepository; + _unitOfWork = unitOfWork; + } + + public async Task Handle(UserCanceledRequestToJoinCommand command, CancellationToken cancellationToken = default) + { + var whiteboardId = new WhiteboardId(command.WhiteboardId); + var userId = new UserId(command.UserId); + + var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboardId, userId, cancellationToken); + + if (membership is null) + { + throw new ValidationException(WhiteboardMembershipErrors.NotFound(whiteboardId, userId)); + } + + membership.UpdateStatus(WhiteboardMembershipStatus.Cancelled); + + await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsRT/Hubs/WhiteboardHub.cs b/dotnet/AipsRT/Hubs/WhiteboardHub.cs index 519c915..af07b91 100644 --- a/dotnet/AipsRT/Hubs/WhiteboardHub.cs +++ b/dotnet/AipsRT/Hubs/WhiteboardHub.cs @@ -2,6 +2,7 @@ using AipsCore.Application.Abstract; using AipsCore.Application.Models.Shape.Command.MoveShape; using AipsCore.Application.Models.Whiteboard.Command.AcceptUserRequestToJoin; using AipsCore.Application.Models.Whiteboard.Command.RejectUserRequestToJoin; +using AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; using AipsCore.Application.Models.Whiteboard.Query.GetMembershipStatus; using AipsCore.Domain.Models.WhiteboardMembership.Enums; using AipsRT.Model.Memberships; @@ -21,14 +22,14 @@ public class WhiteboardHub : Hub private readonly WhiteboardManager _whiteboardManager; private readonly IMessagingService _messagingService; private readonly MembershipService _membershipService; - private readonly GetUserService _getUserService; + private readonly UserService _userService; - public WhiteboardHub(WhiteboardManager whiteboardManager, IMessagingService messagingService, MembershipService membershipService, GetUserService getUserService) + public WhiteboardHub(WhiteboardManager whiteboardManager, IMessagingService messagingService, MembershipService membershipService, UserService userService) { _whiteboardManager = whiteboardManager; _messagingService = messagingService; _membershipService = membershipService; - _getUserService = getUserService; + _userService = userService; } public override async Task OnDisconnectedAsync(Exception? exception) @@ -107,7 +108,7 @@ public class WhiteboardHub : Hub if (user == null) { - user = await _getUserService.GetUser(userId); + user = await _userService.GetUser(userId); } await Clients.User(ownerId.ToString()).SendAsync("UserWaitingForApproval", user); @@ -147,6 +148,7 @@ public class WhiteboardHub : Hub if (whiteboard != null) { + await _messagingService.CancelJoinRequest(new UserCanceledRequestToJoinCommand(whiteboard.WhiteboardId.ToString(), userId.ToString())); await Clients.User(whiteboard.OwnerId.ToString()).SendAsync("UserCanceledJoinRequest", userId.ToString()); } } diff --git a/dotnet/AipsRT/Model/Users/GetUserService.cs b/dotnet/AipsRT/Model/Users/UserService.cs similarity index 77% rename from dotnet/AipsRT/Model/Users/GetUserService.cs rename to dotnet/AipsRT/Model/Users/UserService.cs index 988939e..bdd7d8d 100644 --- a/dotnet/AipsRT/Model/Users/GetUserService.cs +++ b/dotnet/AipsRT/Model/Users/UserService.cs @@ -1,13 +1,14 @@ using AipsCore.Application.Abstract; using AipsCore.Application.Models.User.Query.GetUser; +using AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; namespace AipsRT.Model.Users; -public class GetUserService +public class UserService { private readonly IDispatcher _dispatcher; - public GetUserService(IDispatcher dispatcher) + public UserService(IDispatcher dispatcher) { _dispatcher = dispatcher; } diff --git a/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs b/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs index 68f72ed..593ae1b 100644 --- a/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs +++ b/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs @@ -28,7 +28,8 @@ public class GetWhiteboardService { WhiteboardId = entity.Id, OwnerId = entity.OwnerId, - Owner = new User(entity.Owner.Id, entity.Owner.UserName!, entity.Owner.Email!) + Owner = new User(entity.Owner.Id, entity.Owner.UserName!, entity.Owner.Email!), + Code = entity.Code, }; foreach (var membership in entity.Memberships) diff --git a/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs b/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs index 87a728e..711ff49 100644 --- a/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs +++ b/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs @@ -9,13 +9,14 @@ public class Whiteboard public Guid OwnerId { get; set; } public User Owner { get; set; } = null!; + + public string Code {get; set;} public List Users { get; } = []; public List ActiveUsers { get; } = []; public void AddActiveUser(User user) => ActiveUsers.Add(user); - public void RemoveActiveUser(Guid userId) - => ActiveUsers.RemoveAll(u => u.UserId == userId); + public void RemoveActiveUser(Guid userId) => ActiveUsers.RemoveAll(u => u.UserId == userId); public List Shapes { get; } = []; @@ -48,5 +49,11 @@ public class Whiteboard TextShapes.Add(shape); } - public void AddUser(User user) => Users.Add(user); + public void AddUser(User user) + { + if (!Users.Contains(user)) + { + Users.Add(user); + } + } } \ No newline at end of file diff --git a/dotnet/AipsRT/Program.cs b/dotnet/AipsRT/Program.cs index 2f7711f..8949cb6 100644 --- a/dotnet/AipsRT/Program.cs +++ b/dotnet/AipsRT/Program.cs @@ -27,7 +27,7 @@ builder.Services.AddSingleton(); builder.Services.AddTransient(); -builder.Services.AddTransient(); +builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs index ca8b3cb..bbf42d7 100644 --- a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs +++ b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs @@ -2,6 +2,7 @@ using AipsCore.Application.Models.Shape.Command.CreateTextShape; using AipsCore.Application.Models.Shape.Command.MoveShape; using AipsCore.Application.Models.Whiteboard.Command.AcceptUserRequestToJoin; using AipsCore.Application.Models.Whiteboard.Command.RejectUserRequestToJoin; +using AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; using AipsRT.Model.Whiteboard.Shapes; namespace AipsRT.Services.Interfaces; @@ -17,4 +18,5 @@ public interface IMessagingService Task AcceptedUser(AcceptUserRequestToJoinCommand command); Task RejectedUser(RejectUserRequestToJoinCommand command); + Task CancelJoinRequest(UserCanceledRequestToJoinCommand command); } \ No newline at end of file diff --git a/dotnet/AipsRT/Services/MessagingService.cs b/dotnet/AipsRT/Services/MessagingService.cs index 7789852..ecd08c0 100644 --- a/dotnet/AipsRT/Services/MessagingService.cs +++ b/dotnet/AipsRT/Services/MessagingService.cs @@ -6,6 +6,7 @@ using AipsCore.Application.Common.Message.AddRectangle; using AipsCore.Application.Common.Message.AddTextShape; using AipsCore.Application.Common.Message.MoveShape; using AipsCore.Application.Common.Message.RejectUserRequestToJoin; +using AipsCore.Application.Common.Message.UserCanceledRequestToJoin; using AipsCore.Application.Models.Shape.Command.CreateArrow; using AipsCore.Application.Models.Shape.Command.CreateLine; using AipsCore.Application.Models.Shape.Command.CreateRectangle; @@ -13,6 +14,7 @@ using AipsCore.Application.Models.Shape.Command.CreateTextShape; using AipsCore.Application.Models.Shape.Command.MoveShape; using AipsCore.Application.Models.Whiteboard.Command.AcceptUserRequestToJoin; using AipsCore.Application.Models.Whiteboard.Command.RejectUserRequestToJoin; +using AipsCore.Application.Models.Whiteboard.Command.UserCanceledRequestToJoin; using AipsRT.Model.Whiteboard.Shapes; using AipsRT.Services.Interfaces; @@ -116,4 +118,10 @@ public class MessagingService : IMessagingService var message = new RejectUserRequestToJoinMessage(command); await _messagePublisher.PublishAsync(message); } + + public async Task CancelJoinRequest(UserCanceledRequestToJoinCommand command) + { + var message = new UserCanceledRequestToJoinMessage(command); + await _messagePublisher.PublishAsync(message); + } } \ No newline at end of file diff --git a/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs b/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs index 3f14796..2669f2f 100644 --- a/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs +++ b/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs @@ -18,9 +18,16 @@ public class RtErrorHandleStrategy : IErrorMessageHandleStrategy public async Task Handle(ErrorMessage message, CancellationToken cancellationToken) { + var activeUsers = _whiteboardManager.GetWhiteboard(message.WhiteboardId)!.ActiveUsers; + await _whiteboardManager.LoadWhiteboard(message.WhiteboardId); var whiteboard = _whiteboardManager.GetWhiteboard(message.WhiteboardId)!; + + foreach (var user in activeUsers) + { + whiteboard.AddActiveUser(user); + } await _hubContext.Clients .Group(whiteboard.WhiteboardId.ToString()) diff --git a/dotnet/AipsWorker/Messages/MessageTypesProvider.cs b/dotnet/AipsWorker/Messages/MessageTypesProvider.cs index a2c62b4..d8089e4 100644 --- a/dotnet/AipsWorker/Messages/MessageTypesProvider.cs +++ b/dotnet/AipsWorker/Messages/MessageTypesProvider.cs @@ -6,6 +6,7 @@ using AipsCore.Application.Common.Message.AddRectangle; using AipsCore.Application.Common.Message.AddTextShape; using AipsCore.Application.Common.Message.MoveShape; using AipsCore.Application.Common.Message.RejectUserRequestToJoin; +using AipsCore.Application.Common.Message.UserCanceledRequestToJoin; namespace AipsWorker.Messages; @@ -21,7 +22,8 @@ public class MessageTypesProvider : IMessageTypesProvider typeof(AddTextShapeMessage), typeof(MoveShapeMessage), typeof(AcceptUserRequestToJoinMessage), - typeof(RejectUserRequestToJoinMessage) + typeof(RejectUserRequestToJoinMessage), + typeof(UserCanceledRequestToJoinMessage) ]; } } \ No newline at end of file diff --git a/front/src/components/whiteboard/WhiteboardToolbar.vue b/front/src/components/whiteboard/WhiteboardToolbar.vue index 23739ed..492c9ac 100644 --- a/front/src/components/whiteboard/WhiteboardToolbar.vue +++ b/front/src/components/whiteboard/WhiteboardToolbar.vue @@ -23,6 +23,12 @@ const colors = ['#4f9dff', '#ff4f4f', '#4fff4f', '#ffff4f', '#ff4fff', '#ffffff' const isReadOnly = computed(() => sessionStore.selectedTool === 'hand' && !!sessionStore.selectedShape) +const isOwner = computed(() => { + const wb = sessionStore.whiteboard + if (!wb || !auth.user) return false + return wb.ownerId === auth.user.userId +}) + const showProperties = computed(() => { if (['rectangle', 'arrow', 'line', 'text'].includes(sessionStore.selectedTool)) return true if (sessionStore.selectedTool === 'hand' && sessionStore.selectedShape) return true @@ -61,7 +67,7 @@ const displayTextSize = computed(() => { const showCopiedTooltip = ref(false) -const whiteboardCode = computed(() => infoStore.getCurrentWhiteboard()?.code || '') +const whiteboardCode = computed(() => sessionStore.whiteboard!.code) const copyCodeToClipboard = async () => { console.info(whiteboardCode.value) @@ -166,7 +172,7 @@ const copyCodeToClipboard = async () => { -
+