diff --git a/dotnet/AipsCore/AipsCore.csproj b/dotnet/AipsCore/AipsCore.csproj index 021cc81..55eef2c 100644 --- a/dotnet/AipsCore/AipsCore.csproj +++ b/dotnet/AipsCore/AipsCore.csproj @@ -21,7 +21,6 @@ - diff --git a/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessage.cs b/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessage.cs new file mode 100644 index 0000000..79d4401 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessage.cs @@ -0,0 +1,6 @@ +using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Models.Whiteboard.Command.AcceptUserRequestToJoin; + +namespace AipsCore.Application.Common.Message.AcceptUserRequestToJoin; + +public record AcceptUserRequestToJoinMessage(AcceptUserRequestToJoinCommand Command) : IMessage; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessageHandler.cs b/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessageHandler.cs new file mode 100644 index 0000000..c42f1b8 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/AcceptUserRequestToJoin/AcceptUserRequestToJoinMessageHandler.cs @@ -0,0 +1,19 @@ +using AipsCore.Application.Abstract; +using AipsCore.Application.Abstract.MessageBroking; + +namespace AipsCore.Application.Common.Message.AcceptUserRequestToJoin; + +public class AcceptUserRequestToJoinMessageHandler : IMessageHandler +{ + private readonly IDispatcher _dispatcher; + + public AcceptUserRequestToJoinMessageHandler(IDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task Handle(AcceptUserRequestToJoinMessage message, CancellationToken cancellationToken) + { + await _dispatcher.Execute(message.Command, cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessage.cs b/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessage.cs new file mode 100644 index 0000000..d92dd98 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessage.cs @@ -0,0 +1,6 @@ +using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Models.Whiteboard.Command.RejectUserRequestToJoin; + +namespace AipsCore.Application.Common.Message.RejectUserRequestToJoin; + +public record RejectUserRequestToJoinMessage(RejectUserRequestToJoinCommand Command): IMessage; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessageHandler.cs b/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessageHandler.cs new file mode 100644 index 0000000..a076a32 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/RejectUserRequestToJoin/RejectUserRequestToJoinMessageHandler.cs @@ -0,0 +1,19 @@ +using AipsCore.Application.Abstract; +using AipsCore.Application.Abstract.MessageBroking; + +namespace AipsCore.Application.Common.Message.RejectUserRequestToJoin; + +public class RejectUserRequestToJoinMessageHandler : IMessageHandler +{ + private readonly IDispatcher _dispatcher; + + public RejectUserRequestToJoinMessageHandler(IDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task Handle(RejectUserRequestToJoinMessage message, CancellationToken cancellationToken) + { + await _dispatcher.Execute(message.Command, cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommand.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommand.cs new file mode 100644 index 0000000..51acdeb --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommand.cs @@ -0,0 +1,5 @@ +using AipsCore.Application.Abstract.Command; + +namespace AipsCore.Application.Models.Whiteboard.Command.AcceptUserRequestToJoin; + +public record AcceptUserRequestToJoinCommand(string WhiteboardId, string UserId): ICommand; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommandHandler.cs new file mode 100644 index 0000000..3762d2f --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AcceptUserRequestToJoin/AcceptUserRequestToJoinCommandHandler.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.AcceptUserRequestToJoin; + +public class AcceptUserRequestToJoinCommandHandler : ICommandHandler +{ + private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; + private readonly IUnitOfWork _unitOfWork; + + public AcceptUserRequestToJoinCommandHandler(IWhiteboardMembershipRepository whiteboardMembershipRepository, IUnitOfWork unitOfWork) + { + _whiteboardMembershipRepository = whiteboardMembershipRepository; + _unitOfWork = unitOfWork; + } + + public async Task Handle(AcceptUserRequestToJoinCommand 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.Accepted); + + await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommand.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommand.cs deleted file mode 100644 index 04f3faf..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AipsCore.Application.Abstract.Command; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Application.Models.Whiteboard.Command.AddUserToWhiteboard; - -public record AddUserToWhiteboardCommand( - string UserId, - string WhiteboardId) - : ICommand; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandErrors.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandErrors.cs deleted file mode 100644 index b530c3e..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandErrors.cs +++ /dev/null @@ -1,12 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; - -namespace AipsCore.Application.Models.Whiteboard.Command.AddUserToWhiteboard; - -public static class AddUserToWhiteboardCommandErrors -{ - public static ValidationError WhiteboardDoesNotExist(WhiteboardId whiteboardId) - => new ValidationError( - Code: "whiteboard_not_exists", - Message: $"Whiteboard with id '{whiteboardId}' does not exist."); -} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandHandler.cs deleted file mode 100644 index 645abf3..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/AddUserToWhiteboard/AddUserToWhiteboardCommandHandler.cs +++ /dev/null @@ -1,65 +0,0 @@ -using AipsCore.Application.Abstract; -using AipsCore.Application.Abstract.Command; -using AipsCore.Domain.Abstract; -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.External; -using AipsCore.Domain.Models.User.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.External; -using AipsCore.Domain.Models.Whiteboard.Validation; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; -using AipsCore.Domain.Models.WhiteboardMembership.External; - -namespace AipsCore.Application.Models.Whiteboard.Command.AddUserToWhiteboard; - -public class AddUserToWhiteboardCommandHandler - : ICommandHandler -{ - private readonly IWhiteboardRepository _whiteboardRepository; - private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; - private readonly IUserRepository _userRepository; - private readonly IUnitOfWork _unitOfWork; - private readonly IDispatcher _dispatcher; - - public AddUserToWhiteboardCommandHandler( - IWhiteboardRepository whiteboardRepository, - IWhiteboardMembershipRepository whiteboardMembershipRepository, - IUserRepository userRepository, - IUnitOfWork unitOfWork, - IDispatcher dispatcher) - { - _whiteboardRepository = whiteboardRepository; - _whiteboardMembershipRepository = whiteboardMembershipRepository; - _userRepository = userRepository; - _unitOfWork = unitOfWork; - _dispatcher = dispatcher; - } - - private Domain.Models.Whiteboard.Whiteboard? _whiteboard; - private Domain.Models.User.User? _user; - - public async Task Handle(AddUserToWhiteboardCommand command, CancellationToken cancellationToken = default) - { - _whiteboard = await _whiteboardRepository.GetByIdAsync(new WhiteboardId(command.WhiteboardId), cancellationToken); - _user = await _userRepository.GetByIdAsync(new UserId(command.UserId), cancellationToken); - - Validate(command); - - await _whiteboard!.AddUserAsync(_user!, _whiteboardMembershipRepository, cancellationToken); - - await _unitOfWork.SaveChangesAsync(cancellationToken); - } - - private void Validate(AddUserToWhiteboardCommand command) - { - if (_whiteboard is null) - { - throw new ValidationException(WhiteboardErrors.NotFound(new WhiteboardId(command.WhiteboardId))); - } - - if (_user is null) - { - throw new ValidationException(UserErrors.NotFound(new UserId(command.UserId))); - } - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandErrors.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandErrors.cs deleted file mode 100644 index 303a529..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandErrors.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; - -namespace AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard; - -public static class BanUserFromWhiteboardCommandErrors -{ - public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId) - => new ValidationError( - Code: "whiteboard_membership_not_found", - Message: $"User with id '{userId.IdValue}' is not a member of whiteboard with id '{whiteboardId.IdValue}'"); - - public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId) - => new ValidationError( - Code: "whiteboard_not_found", - Message: $"Whiteboard with id '{whiteboardId.IdValue}' not found."); -} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandHandler.cs index efcc9a9..312241c 100644 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandHandler.cs +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/BanUserFromWhiteboard/BanUserFromWhiteboardCommandHandler.cs @@ -4,8 +4,10 @@ using AipsCore.Domain.Abstract; using AipsCore.Domain.Common.Validation; using AipsCore.Domain.Models.User.ValueObjects; using AipsCore.Domain.Models.Whiteboard.External; +using AipsCore.Domain.Models.Whiteboard.Validation; using AipsCore.Domain.Models.Whiteboard.ValueObjects; using AipsCore.Domain.Models.WhiteboardMembership.External; +using AipsCore.Domain.Models.WhiteboardMembership.Validation; namespace AipsCore.Application.Models.Whiteboard.Command.BanUserFromWhiteboard; @@ -37,14 +39,14 @@ public class BanUserFromWhiteboardCommandHandler : ICommandHandler; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeCommandHandler.cs new file mode 100644 index 0000000..c9139a2 --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeCommandHandler.cs @@ -0,0 +1,65 @@ +using AipsCore.Application.Abstract.Command; +using AipsCore.Application.Abstract.UserContext; +using AipsCore.Domain.Abstract; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Models.Whiteboard.External; +using AipsCore.Domain.Models.Whiteboard.Validation; +using AipsCore.Domain.Models.Whiteboard.ValueObjects; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; +using AipsCore.Domain.Models.WhiteboardMembership.External; + +namespace AipsCore.Application.Models.Whiteboard.Command.JoinWithCode; + +public class JoinWithCodeCommandHandler : ICommandHandler +{ + private readonly IWhiteboardRepository _whiteboardRepository; + private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IUserContext _userContext; + + public JoinWithCodeCommandHandler( + IWhiteboardRepository whiteboardRepository, + IWhiteboardMembershipRepository whiteboardMembershipRepository, + IUnitOfWork unitOfWork, + IUserContext userContext) + { + _whiteboardRepository = whiteboardRepository; + _whiteboardMembershipRepository = whiteboardMembershipRepository; + _unitOfWork = unitOfWork; + _userContext = userContext; + } + + public async Task Handle(JoinWithCodeCommand command, CancellationToken cancellationToken = default) + { + var userId = _userContext.GetCurrentUserId(); + + var code = new WhiteboardCode(command.Code); + var whiteboard = await _whiteboardRepository.GetByCodeAsync(code, cancellationToken); + + if (whiteboard is null) + { + throw new ValidationException(WhiteboardErrors.NotFound(code)); + } + + if (!whiteboard.ShouldRequestToJoin(userId)) + { + return new JoinWithCodeDto(whiteboard.Id.IdValue, WhiteboardMembershipStatus.Accepted); + } + + var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboard.Id, userId, cancellationToken); + + if (membership is null) + { + membership = whiteboard.RequestJoin(userId); + } + else + { + whiteboard.RequestReJoin(membership); + } + + await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); + + return new JoinWithCodeDto(whiteboard.Id.IdValue, membership.Status); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeDto.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeDto.cs new file mode 100644 index 0000000..c629e28 --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/JoinWithCode/JoinWithCodeDto.cs @@ -0,0 +1,5 @@ +using AipsCore.Domain.Models.WhiteboardMembership.Enums; + +namespace AipsCore.Application.Models.Whiteboard.Command.JoinWithCode; + +public record JoinWithCodeDto(string WhiteboardId, WhiteboardMembershipStatus Status); \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandErrors.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandErrors.cs deleted file mode 100644 index dbc5489..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandErrors.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; - -namespace AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard; - -public static class KickUserFromWhiteboardCommandErrors -{ - public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId) - => new ValidationError( - Code: "whiteboard_membership_not_found", - Message: $"User with id '{userId}' is not a member of whiteboard with id '{whiteboardId}'"); - - public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId) - => new ValidationError( - Code: "whiteboard_not_found", - Message: $"Whiteboard with id '{whiteboardId}' not found."); -} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandHandler.cs index b30b825..1059152 100644 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandHandler.cs +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/KickUserFromWhiteboard/KickUserFromWhiteboardCommandHandler.cs @@ -4,8 +4,10 @@ using AipsCore.Domain.Abstract; using AipsCore.Domain.Common.Validation; using AipsCore.Domain.Models.User.ValueObjects; using AipsCore.Domain.Models.Whiteboard.External; +using AipsCore.Domain.Models.Whiteboard.Validation; using AipsCore.Domain.Models.Whiteboard.ValueObjects; using AipsCore.Domain.Models.WhiteboardMembership.External; +using AipsCore.Domain.Models.WhiteboardMembership.Validation; namespace AipsCore.Application.Models.Whiteboard.Command.KickUserFromWhiteboard; @@ -37,14 +39,14 @@ public class KickUserFromWhiteboardCommandHandler : ICommandHandler +{ + private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; + private readonly IUnitOfWork _unitOfWork; + + public RejectUserRequestToJoinCommandHandler(IWhiteboardMembershipRepository whiteboardMembershipRepository, IUnitOfWork unitOfWork) + { + _whiteboardMembershipRepository = whiteboardMembershipRepository; + _unitOfWork = unitOfWork; + } + + public async Task Handle(RejectUserRequestToJoinCommand 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.Rejected); + + await _whiteboardMembershipRepository.SaveAsync(membership, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandErrors.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandErrors.cs deleted file mode 100644 index f04e3d9..0000000 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandErrors.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; - -namespace AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard; - -public static class UnbanUserFromWhiteboardCommandErrors -{ - public static ValidationError WhiteboardMembershipNotFound(WhiteboardId whiteboardId, UserId userId) - => new ValidationError( - Code: "whiteboard_membership_not_found", - Message: $"User with id '{userId}' is not a member of whiteboard with id '{whiteboardId}'"); - - public static ValidationError WhiteboardNotFound(WhiteboardId whiteboardId) - => new ValidationError( - Code: "whiteboard_not_found", - Message: $"Whiteboard with id '{whiteboardId}' not found."); -} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandHandler.cs index 326a5b3..83933a6 100644 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandHandler.cs +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Command/UnbanUserFromWhiteboard/UnbanUserFromWhiteboardCommandHandler.cs @@ -4,8 +4,10 @@ using AipsCore.Domain.Abstract; using AipsCore.Domain.Common.Validation; using AipsCore.Domain.Models.User.ValueObjects; using AipsCore.Domain.Models.Whiteboard.External; +using AipsCore.Domain.Models.Whiteboard.Validation; using AipsCore.Domain.Models.Whiteboard.ValueObjects; using AipsCore.Domain.Models.WhiteboardMembership.External; +using AipsCore.Domain.Models.WhiteboardMembership.Validation; namespace AipsCore.Application.Models.Whiteboard.Command.UnbanUserFromWhiteboard; @@ -37,14 +39,14 @@ public class UnbanUserFromWhiteboardCommandHandler : ICommandHandler; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetMembershipStatus/GetMembershipStatusQueryHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetMembershipStatus/GetMembershipStatusQueryHandler.cs new file mode 100644 index 0000000..7352fac --- /dev/null +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetMembershipStatus/GetMembershipStatusQueryHandler.cs @@ -0,0 +1,34 @@ +using AipsCore.Application.Abstract.Query; +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.Query.GetMembershipStatus; + +public class GetMembershipStatusQueryHandler : IQueryHandler +{ + private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; + + public GetMembershipStatusQueryHandler(IWhiteboardMembershipRepository whiteboardMembershipRepository) + { + _whiteboardMembershipRepository = whiteboardMembershipRepository; + } + + public async Task Handle(GetMembershipStatusQuery query, CancellationToken cancellationToken = default) + { + var userId = new UserId(query.UserId); + var whiteboardId = new WhiteboardId(query.WhiteboardId); + + var membership = await _whiteboardMembershipRepository.GetByWhiteboardAndUserAsync(whiteboardId, userId, cancellationToken); + + if (membership is null) + { + throw new ValidationException(WhiteboardMembershipErrors.NotFound(whiteboardId, userId)); + } + + return membership.Status; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetRecentWhiteboards/GetRecentWhiteboardsQueryHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetRecentWhiteboards/GetRecentWhiteboardsQueryHandler.cs index 23ddb96..5d11f4f 100644 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetRecentWhiteboards/GetRecentWhiteboardsQueryHandler.cs +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetRecentWhiteboards/GetRecentWhiteboardsQueryHandler.cs @@ -1,6 +1,7 @@ using AipsCore.Application.Abstract.Query; using AipsCore.Application.Abstract.UserContext; using AipsCore.Domain.Models.Whiteboard.Enums; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; using AipsCore.Infrastructure.Persistence.Db; using Microsoft.EntityFrameworkCore; @@ -32,7 +33,7 @@ public class GetRecentWhiteboardsQueryHandler : IQueryHandler m.Whiteboard) .Where(m => ( m.UserId == userIdGuid && - m.IsBanned == false && + m.Status != WhiteboardMembershipStatus.Banned && m.Whiteboard != null && m.Whiteboard.State != WhiteboardState.Deleted )) diff --git a/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetWhiteboardInfoRT/GetWhiteboardInfoRTQueryHandler.cs b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetWhiteboardInfoRT/GetWhiteboardInfoRTQueryHandler.cs index 142245a..14c7dc5 100644 --- a/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetWhiteboardInfoRT/GetWhiteboardInfoRTQueryHandler.cs +++ b/dotnet/AipsCore/Application/Models/Whiteboard/Query/GetWhiteboardInfoRT/GetWhiteboardInfoRTQueryHandler.cs @@ -34,6 +34,7 @@ public class GetWhiteboardInfoRTQueryHandler return _context.Whiteboards .Where(w => w.Id == whiteboardId) .Include(w => w.Memberships) + .ThenInclude(m => m.User) .Include(w => w.Owner) .Include(w => w.Shapes); } diff --git a/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommand.cs b/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommand.cs deleted file mode 100644 index ebe3367..0000000 --- a/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AipsCore.Application.Abstract.Command; -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Application.Models.WhiteboardMembership.Command.CreateWhiteboardMembership; - -public record CreateWhiteboardMembershipCommand( - string WhiteboardId, - bool IsBanned, - bool EditingEnabled, - bool CanJoin) - : ICommand; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommandHandler.cs b/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommandHandler.cs deleted file mode 100644 index a336e36..0000000 --- a/dotnet/AipsCore/Application/Models/WhiteboardMembership/Command/CreateWhiteboardMembership/CreateWhiteboardMembershipCommandHandler.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AipsCore.Application.Abstract.Command; -using AipsCore.Application.Abstract.UserContext; -using AipsCore.Domain.Abstract; -using AipsCore.Domain.Models.WhiteboardMembership.External; -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Application.Models.WhiteboardMembership.Command.CreateWhiteboardMembership; - -public class CreateWhiteboardMembershipCommandHandler : ICommandHandler -{ - private readonly IWhiteboardMembershipRepository _whiteboardMembershipRepository; - private readonly IUserContext _userContext; - private readonly IUnitOfWork _unitOfWork; - - public CreateWhiteboardMembershipCommandHandler( - IWhiteboardMembershipRepository whiteboardMembershipRepository, - IUserContext userContext, - IUnitOfWork unitOfWork) - { - _whiteboardMembershipRepository = whiteboardMembershipRepository; - _userContext = userContext; - _unitOfWork = unitOfWork; - } - - public async Task Handle(CreateWhiteboardMembershipCommand command, CancellationToken cancellationToken = default) - { - var userId = _userContext.GetCurrentUserId(); - - var whiteboardMembership = Domain.Models.WhiteboardMembership.WhiteboardMembership.Create( - command.WhiteboardId, - userId.IdValue, - command.IsBanned, - command.EditingEnabled, - command.CanJoin, - DateTime.UtcNow); - - await _whiteboardMembershipRepository.SaveAsync(whiteboardMembership, cancellationToken); - await _unitOfWork.SaveChangesAsync(cancellationToken); - - return whiteboardMembership.Id; - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/External/IWhiteboardRepository.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/External/IWhiteboardRepository.cs index 67812ec..8a4c2a4 100644 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/External/IWhiteboardRepository.cs +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/External/IWhiteboardRepository.cs @@ -6,5 +6,6 @@ namespace AipsCore.Domain.Models.Whiteboard.External; public interface IWhiteboardRepository : IAbstractRepository, ISoftDeletableRepository { - Task WhiteboardCodeExists(WhiteboardCode whiteboardCode); + Task WhiteboardCodeExistsAsync(WhiteboardCode whiteboardCode); + Task GetByCodeAsync(WhiteboardCode whiteboardCode, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhiteboardErrors.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhiteboardErrors.cs index ad1070e..4640099 100644 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhiteboardErrors.cs +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Validation/WhiteboardErrors.cs @@ -7,6 +7,38 @@ namespace AipsCore.Domain.Models.Whiteboard.Validation; public class WhiteboardErrors : AbstractErrors { + public static ValidationError NotFound(WhiteboardCode whiteboardCode) + { + const string code = "not_found"; + string message = $"Whiteboard with code '{whiteboardCode.CodeValue}' was not found!"; + + return CreateValidationError(code,message); + } + + public static ValidationError CannotJoin(WhiteboardCode whiteboardCode) + { + const string code = "cannot_join_whiteboard"; + string message = $"Cannot join the whiteboard with code '{whiteboardCode.CodeValue}'"; + + return CreateValidationError(code,message); + } + + public static ValidationError UserBanned(UserId userId) + { + const string code = "user_banned_from_whiteboard"; + string message = $"User with id '{userId}' is banned from this whiteboard."; + + return CreateValidationError(code,message); + } + + public static ValidationError UserAlreadyTryingToJoin(UserId userId) + { + const string code = "user_already_trying_to_join_whiteboard"; + string message = $"User with id '{userId}' is already trying to join the whiteboard."; + + return CreateValidationError(code,message); + } + public static ValidationError UserAlreadyAdded(UserId userId) { string code = "user_already_added"; diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs index 895fe5e..872957c 100644 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/ValueObjects/WhiteboardCode.cs @@ -36,7 +36,7 @@ public record WhiteboardCode : AbstractValueObject { whiteboardCode = Generate(); - codeExists = await whiteboardRepository.WhiteboardCodeExists(whiteboardCode); + codeExists = await whiteboardRepository.WhiteboardCodeExistsAsync(whiteboardCode); } while (codeExists); return whiteboardCode; diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.AddUser.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.AddUser.cs index cf55327..dcbb7c0 100644 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.AddUser.cs +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.AddUser.cs @@ -1,42 +1,47 @@ -using System.Runtime.InteropServices.Swift; -using AipsCore.Domain.Abstract; using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Models.User.ValueObjects; using AipsCore.Domain.Models.Whiteboard.Enums; using AipsCore.Domain.Models.Whiteboard.Validation; -using AipsCore.Domain.Models.Whiteboard.ValueObjects; -using AipsCore.Domain.Models.WhiteboardMembership.External; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; namespace AipsCore.Domain.Models.Whiteboard; -public partial class Whiteboard : DomainModel +public partial class Whiteboard { - public async Task AddUserAsync( - User.User user, - IWhiteboardMembershipRepository membershipRepository, - CancellationToken cancellationToken = default) + public WhiteboardMembership.WhiteboardMembership RequestJoin(UserId userId) { - var membership - = await membershipRepository.GetByWhiteboardAndUserAsync(this.Id, user.Id, cancellationToken); + return WhiteboardMembership.WhiteboardMembership.Create( + Id.IdValue, + userId.IdValue, + false, + DetermineJoinStatus(), + DateTime.UtcNow); + } - if (membership is not null) + public void RequestReJoin(WhiteboardMembership.WhiteboardMembership membership) + { + switch (membership.Status) { - throw new ValidationException(WhiteboardErrors.UserAlreadyAdded(user.Id)); + case WhiteboardMembershipStatus.Banned: + throw new ValidationException(WhiteboardErrors.UserBanned(membership.UserId)); + case WhiteboardMembershipStatus.Pending: + throw new ValidationException(WhiteboardErrors.UserAlreadyTryingToJoin(membership.UserId)); + case WhiteboardMembershipStatus.Accepted: + break; + default: + membership.UpdateStatus(DetermineJoinStatus()); + break; } - - membership = WhiteboardMembership.WhiteboardMembership.Create( - this.Id.IdValue, - user.Id.IdValue, - false, - false, - this.GetCanJoin(), - DateTime.UtcNow - ); - - await membershipRepository.AddAsync(membership, cancellationToken); } - private bool GetCanJoin() + private WhiteboardMembershipStatus DetermineJoinStatus() { - return this.JoinPolicy == WhiteboardJoinPolicy.FreeToJoin; + return JoinPolicy switch + { + WhiteboardJoinPolicy.FreeToJoin => WhiteboardMembershipStatus.Accepted, + WhiteboardJoinPolicy.RequestToJoin => WhiteboardMembershipStatus.Pending, + WhiteboardJoinPolicy.Private => throw new ValidationException(WhiteboardErrors.CannotJoin(Code)), + _ => throw new ArgumentOutOfRangeException() + }; } -} +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.BanUser.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.BanUser.cs deleted file mode 100644 index 57d5dfb..0000000 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.BanUser.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.Validation; - -namespace AipsCore.Domain.Models.Whiteboard; - -public partial class Whiteboard -{ - public void BanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) - { - if (WhiteboardOwnerId != currentUserId) - { - throw new ValidationException(WhiteboardErrors.OnlyOwnerCanBanOtherUsers(currentUserId)); - } - - whiteboardMembership.Ban(); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.CanUserDelete.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.CanUserDelete.cs index b11e8de..bf2a429 100644 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.CanUserDelete.cs +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.CanUserDelete.cs @@ -6,6 +6,11 @@ public partial class Whiteboard { public bool CanUserDelete(UserId userId) { - return WhiteboardOwnerId.IdValue == userId.IdValue; + return IsOwner(userId); + } + + public bool ShouldRequestToJoin(UserId userId) + { + return !IsOwner(userId); } } \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.KickUser.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.KickUser.cs deleted file mode 100644 index e9d4579..0000000 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.KickUser.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.Validation; - -namespace AipsCore.Domain.Models.Whiteboard; - -public partial class Whiteboard -{ - public void KickUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) - { - if (WhiteboardOwnerId != currentUserId) - { - throw new ValidationException(WhiteboardErrors.OnlyOwnerCanKickOtherUsers(currentUserId)); - } - - whiteboardMembership.Kick(); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.ManageUsers.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.ManageUsers.cs new file mode 100644 index 0000000..76ae127 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.ManageUsers.cs @@ -0,0 +1,43 @@ +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Models.User.ValueObjects; +using AipsCore.Domain.Models.Whiteboard.Validation; + +namespace AipsCore.Domain.Models.Whiteboard; + +public partial class Whiteboard +{ + public void BanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) + { + if (IsOwner(currentUserId)) + { + throw new ValidationException(WhiteboardErrors.OnlyOwnerCanBanOtherUsers(currentUserId)); + } + + whiteboardMembership.Ban(); + } + + public void UnbanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) + { + if (IsOwner(currentUserId)) + { + throw new ValidationException(WhiteboardErrors.OnlyOwnerCanUnbanOtherUsers(currentUserId)); + } + + whiteboardMembership.Unban(); + } + + public void KickUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) + { + if (IsOwner(currentUserId)) + { + throw new ValidationException(WhiteboardErrors.OnlyOwnerCanKickOtherUsers(currentUserId)); + } + + whiteboardMembership.Kick(); + } + + private bool IsOwner(UserId userId) + { + return WhiteboardOwnerId.IdValue == userId.IdValue; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.UnbanUser.cs b/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.UnbanUser.cs deleted file mode 100644 index dabc832..0000000 --- a/dotnet/AipsCore/Domain/Models/Whiteboard/Whiteboard.UnbanUser.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AipsCore.Domain.Common.Validation; -using AipsCore.Domain.Models.User.ValueObjects; -using AipsCore.Domain.Models.Whiteboard.Validation; - -namespace AipsCore.Domain.Models.Whiteboard; - -public partial class Whiteboard -{ - public void UnbanUser(UserId currentUserId, WhiteboardMembership.WhiteboardMembership whiteboardMembership) - { - if (WhiteboardOwnerId != currentUserId) - { - throw new ValidationException(WhiteboardErrors.OnlyOwnerCanUnbanOtherUsers(currentUserId)); - } - - whiteboardMembership.Unban(); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Enums/WhiteboardMembershipStatus.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Enums/WhiteboardMembershipStatus.cs new file mode 100644 index 0000000..c69f5c6 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Enums/WhiteboardMembershipStatus.cs @@ -0,0 +1,13 @@ +namespace AipsCore.Domain.Models.WhiteboardMembership.Enums; + +public enum WhiteboardMembershipStatus +{ + Pending, + Accepted, + Active, + Inactive, + Rejected, + Cancelled, + Kicked, + Banned +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Validation/WhiteboardMembershipErrors.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Validation/WhiteboardMembershipErrors.cs new file mode 100644 index 0000000..5858d56 --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/Validation/WhiteboardMembershipErrors.cs @@ -0,0 +1,18 @@ +using AipsCore.Domain.Abstract.Validation; +using AipsCore.Domain.Common.Validation; +using AipsCore.Domain.Models.User.ValueObjects; +using AipsCore.Domain.Models.Whiteboard.ValueObjects; +using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; + +namespace AipsCore.Domain.Models.WhiteboardMembership.Validation; + +public class WhiteboardMembershipErrors : AbstractErrors +{ + public static ValidationError NotFound(WhiteboardId whiteboardId, UserId userId) + { + const string code = "whiteboard_membership_not_found"; + string message = $"Whiteboard membership with whiteboard id {whiteboardId.IdValue} and user id {userId.IdValue} not found."; + + return CreateValidationError(code, message); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs deleted file mode 100644 index 48591ad..0000000 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AipsCore.Domain.Abstract.Rule; -using AipsCore.Domain.Abstract.ValueObject; - -namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -public record WhiteboardMembershipCanJoin : AbstractValueObject -{ - public bool CanJoinValue { get; init; } - - public WhiteboardMembershipCanJoin(bool CanJoinValue) - { - this.CanJoinValue = CanJoinValue; - Validate(); - } - - protected override ICollection GetValidationRules() - { - return []; - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs deleted file mode 100644 index 363e9ab..0000000 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AipsCore.Domain.Abstract.Rule; -using AipsCore.Domain.Abstract.ValueObject; - -namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -public record WhiteboardMembershipIsBanned : AbstractValueObject -{ - public bool IsBannedValue { get; init; } - - public WhiteboardMembershipIsBanned(bool IsBannedValue) - { - this.IsBannedValue = IsBannedValue; - Validate(); - } - - protected override ICollection GetValidationRules() - { - return []; - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Ban.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Ban.cs deleted file mode 100644 index 6befd88..0000000 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Ban.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Domain.Models.WhiteboardMembership; - -public partial class WhiteboardMembership -{ - public void Ban() - { - IsBanned = new WhiteboardMembershipIsBanned(true); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Kick.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Kick.cs deleted file mode 100644 index 92acf4a..0000000 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Kick.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Domain.Models.WhiteboardMembership; - -public partial class WhiteboardMembership -{ - public void Kick() - { - CanJoin = new WhiteboardMembershipCanJoin(false); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Status.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Status.cs new file mode 100644 index 0000000..5c6f50b --- /dev/null +++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Status.cs @@ -0,0 +1,26 @@ +using AipsCore.Domain.Models.WhiteboardMembership.Enums; + +namespace AipsCore.Domain.Models.WhiteboardMembership; + +public partial class WhiteboardMembership +{ + public void Ban() + { + Status = WhiteboardMembershipStatus.Banned; + } + + public void Unban() + { + Status = WhiteboardMembershipStatus.Cancelled; + } + + public void Kick() + { + Status = WhiteboardMembershipStatus.Kicked; + } + + public void UpdateStatus(WhiteboardMembershipStatus newStatus) + { + Status = newStatus; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Unban.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Unban.cs deleted file mode 100644 index 9072889..0000000 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.Unban.cs +++ /dev/null @@ -1,11 +0,0 @@ -using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; - -namespace AipsCore.Domain.Models.WhiteboardMembership; - -public partial class WhiteboardMembership -{ - public void Unban() - { - IsBanned = new WhiteboardMembershipIsBanned(false); - } -} \ No newline at end of file diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs index bfad6da..7635c4d 100644 --- a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs +++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs @@ -1,6 +1,7 @@ using AipsCore.Domain.Abstract; using AipsCore.Domain.Models.User.ValueObjects; using AipsCore.Domain.Models.Whiteboard.ValueObjects; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects; namespace AipsCore.Domain.Models.WhiteboardMembership; @@ -9,97 +10,84 @@ public partial class WhiteboardMembership : DomainModel { public WhiteboardId WhiteboardId { get; private set; } public UserId UserId { get; private set; } - public WhiteboardMembershipIsBanned IsBanned { get; private set; } public WhiteboardMembershipEditingEnabled EditingEnabled { get; private set; } - public WhiteboardMembershipCanJoin CanJoin { get; private set; } + public WhiteboardMembershipStatus Status { get; private set; } public WhiteboardMembershipLastInteractedAt LastInteractedAt { get; private set; } public WhiteboardMembership( WhiteboardMembershipId id, - Whiteboard.Whiteboard owner, + Whiteboard.Whiteboard whiteboard, User.User user, - WhiteboardMembershipIsBanned isBanned, WhiteboardMembershipEditingEnabled editingEnabled, - WhiteboardMembershipCanJoin canJoin, + WhiteboardMembershipStatus status, WhiteboardMembershipLastInteractedAt lastInteractedAt) : base(id) { - WhiteboardId = owner.Id; + WhiteboardId = whiteboard.Id; UserId = user.Id; - IsBanned = isBanned; EditingEnabled = editingEnabled; - CanJoin = canJoin; + Status = status; LastInteractedAt = lastInteractedAt; } public WhiteboardMembership( WhiteboardMembershipId id, - WhiteboardId ownerId, + WhiteboardId whiteboardId, UserId userId, - WhiteboardMembershipIsBanned isBanned, WhiteboardMembershipEditingEnabled editingEnabled, - WhiteboardMembershipCanJoin canJoin, + WhiteboardMembershipStatus status, WhiteboardMembershipLastInteractedAt lastInteractedAt) : base(id) { - WhiteboardId = ownerId; + WhiteboardId = whiteboardId; UserId = userId; - IsBanned = isBanned; EditingEnabled = editingEnabled; - CanJoin = canJoin; + Status = status; LastInteractedAt = lastInteractedAt; } public static WhiteboardMembership Create( string id, - string ownerId, + string whiteboardId, string userId, - bool isBanned, bool editingEnabled, - bool canJoin, + WhiteboardMembershipStatus status, DateTime lastInteractedAt) { var whiteboardMembershipId = new WhiteboardMembershipId(id); - var whiteboardId = new WhiteboardId(ownerId); + var whiteboardIdVo = new WhiteboardId(whiteboardId); var userIdVo = new UserId(userId); - var isBannedVo = new WhiteboardMembershipIsBanned(isBanned); var editingEnabledVo = new WhiteboardMembershipEditingEnabled(editingEnabled); - var canJoinVo = new WhiteboardMembershipCanJoin(canJoin); var lastInteractedAtVo = new WhiteboardMembershipLastInteractedAt(lastInteractedAt); return new WhiteboardMembership( whiteboardMembershipId, - whiteboardId, - userIdVo, - isBannedVo, + whiteboardIdVo, + userIdVo, editingEnabledVo, - canJoinVo, + status, lastInteractedAtVo); } public static WhiteboardMembership Create( - string ownerId, + string whiteboardId, string userId, - bool isBanned, bool editingEnabled, - bool canJoin, + WhiteboardMembershipStatus status, DateTime lastInteractedAt) { var whiteboardMembershipId = WhiteboardMembershipId.Any(); - var whiteboardId = new WhiteboardId(ownerId); + var whiteboardIdVo = new WhiteboardId(whiteboardId); var userIdVo = new UserId(userId); - var isBannedVo = new WhiteboardMembershipIsBanned(isBanned); var editingEnabledVo = new WhiteboardMembershipEditingEnabled(editingEnabled); - var canJoinVo = new WhiteboardMembershipCanJoin(canJoin); var lastInteractedAtVo = new WhiteboardMembershipLastInteractedAt(lastInteractedAt); return new WhiteboardMembership( whiteboardMembershipId, - whiteboardId, + whiteboardIdVo, userIdVo, - isBannedVo, editingEnabledVo, - canJoinVo, + status, lastInteractedAtVo); } } \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.Designer.cs b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.Designer.cs new file mode 100644 index 0000000..8c0052f --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.Designer.cs @@ -0,0 +1,503 @@ +// +using System; +using AipsCore.Infrastructure.Persistence.Db; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AipsCore.Infrastructure.Persistence.Db.Migrations +{ + [DbContext(typeof(AipsDbContext))] + [Migration("20260226153018_ReworkedMembership")] + partial class ReworkedMembership + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.RefreshToken.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Shape.Shape", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("EndPositionX") + .HasColumnType("integer"); + + b.Property("EndPositionY") + .HasColumnType("integer"); + + b.Property("PositionX") + .HasColumnType("integer"); + + b.Property("PositionY") + .HasColumnType("integer"); + + b.Property("TextSize") + .HasColumnType("integer"); + + b.Property("TextValue") + .HasColumnType("text"); + + b.Property("Thickness") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("WhiteboardId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("WhiteboardId"); + + b.ToTable("Shapes"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.User.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("JoinPolicy") + .HasColumnType("integer"); + + b.Property("MaxParticipants") + .HasColumnType("integer"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("Whiteboards"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.WhiteboardMembership.WhiteboardMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EditingEnabled") + .HasColumnType("boolean"); + + b.Property("LastInteractedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WhiteboardId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("WhiteboardId"); + + b.ToTable("WhiteboardMemberships"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.RefreshToken.RefreshToken", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Shape.Shape", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", "Author") + .WithMany("Shapes") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard", "Whiteboard") + .WithMany("Shapes") + .HasForeignKey("WhiteboardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Whiteboard"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", "Owner") + .WithMany("Whiteboards") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.WhiteboardMembership.WhiteboardMembership", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", "User") + .WithMany("Memberships") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard", "Whiteboard") + .WithMany("Memberships") + .HasForeignKey("WhiteboardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("Whiteboard"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("AipsCore.Infrastructure.Persistence.User.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.User.User", b => + { + b.Navigation("Memberships"); + + b.Navigation("Shapes"); + + b.Navigation("Whiteboards"); + }); + + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard", b => + { + b.Navigation("Memberships"); + + b.Navigation("Shapes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.cs b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.cs new file mode 100644 index 0000000..0d64f72 --- /dev/null +++ b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/20260226153018_ReworkedMembership.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AipsCore.Infrastructure.Persistence.Db.Migrations +{ + /// + public partial class ReworkedMembership : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CanJoin", + table: "WhiteboardMemberships"); + + migrationBuilder.DropColumn( + name: "IsBanned", + table: "WhiteboardMemberships"); + + migrationBuilder.AddColumn( + name: "Status", + table: "WhiteboardMemberships", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "WhiteboardMemberships"); + + migrationBuilder.AddColumn( + name: "CanJoin", + table: "WhiteboardMemberships", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsBanned", + table: "WhiteboardMemberships", + type: "boolean", + nullable: false, + defaultValue: false); + } + } +} diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/AipsDbContextModelSnapshot.cs b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/AipsDbContextModelSnapshot.cs index fe16030..30e60d2 100644 --- a/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/AipsDbContextModelSnapshot.cs +++ b/dotnet/AipsCore/Infrastructure/Persistence/Db/Migrations/AipsDbContextModelSnapshot.cs @@ -22,7 +22,7 @@ namespace AipsCore.Infrastructure.Persistence.Db.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Authentication.RefreshToken.RefreshToken", b => + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.RefreshToken.RefreshToken", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -214,18 +214,15 @@ namespace AipsCore.Infrastructure.Persistence.Db.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("CanJoin") - .HasColumnType("boolean"); - b.Property("EditingEnabled") .HasColumnType("boolean"); - b.Property("IsBanned") - .HasColumnType("boolean"); - b.Property("LastInteractedAt") .HasColumnType("timestamp with time zone"); + b.Property("Status") + .HasColumnType("integer"); + b.Property("UserId") .HasColumnType("uuid"); @@ -371,7 +368,7 @@ namespace AipsCore.Infrastructure.Persistence.Db.Migrations b.ToTable("AspNetUserTokens", (string)null); }); - modelBuilder.Entity("AipsCore.Infrastructure.Persistence.Authentication.RefreshToken.RefreshToken", b => + modelBuilder.Entity("AipsCore.Infrastructure.Persistence.RefreshToken.RefreshToken", b => { b.HasOne("AipsCore.Infrastructure.Persistence.User.User", "User") .WithMany() diff --git a/dotnet/AipsCore/Infrastructure/Persistence/Whiteboard/WhiteboardRepository.cs b/dotnet/AipsCore/Infrastructure/Persistence/Whiteboard/WhiteboardRepository.cs index 29518f9..f647ef2 100644 --- a/dotnet/AipsCore/Infrastructure/Persistence/Whiteboard/WhiteboardRepository.cs +++ b/dotnet/AipsCore/Infrastructure/Persistence/Whiteboard/WhiteboardRepository.cs @@ -58,11 +58,18 @@ public class WhiteboardRepository entity.State = model.State; } - public async Task WhiteboardCodeExists(WhiteboardCode whiteboardCode) + public async Task WhiteboardCodeExistsAsync(WhiteboardCode whiteboardCode) { return await Context.Whiteboards.AnyAsync(w => w.Code == whiteboardCode.CodeValue); } + public async Task GetByCodeAsync(WhiteboardCode whiteboardCode, CancellationToken cancellationToken = default) + { + var entity = await Context.Whiteboards.FirstOrDefaultAsync(w => w.Code == whiteboardCode.CodeValue, cancellationToken); + + return entity != null ? MapToModel(entity) : null; + } + public async Task SoftDeleteAsync(WhiteboardId id, CancellationToken cancellationToken = default) { var entity = await Context.Whiteboards.FindAsync([new Guid(id.IdValue)], cancellationToken); diff --git a/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembership.cs b/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembership.cs index c2cd8d8..469654d 100644 --- a/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembership.cs +++ b/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembership.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; namespace AipsCore.Infrastructure.Persistence.WhiteboardMembership; @@ -15,11 +16,9 @@ public class WhiteboardMembership public User.User? User { get; set; } = null!; - public bool IsBanned { get; set; } - public bool EditingEnabled { get; set; } - public bool CanJoin { get; set; } + public WhiteboardMembershipStatus Status { get; set; } public DateTime LastInteractedAt { get; set; } } \ No newline at end of file diff --git a/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembershipRepository.cs b/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembershipRepository.cs index 52bd6ba..b556326 100644 --- a/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembershipRepository.cs +++ b/dotnet/AipsCore/Infrastructure/Persistence/WhiteboardMembership/WhiteboardMembershipRepository.cs @@ -24,9 +24,8 @@ public class WhiteboardMembershipRepository entity.Id.ToString(), entity.WhiteboardId.ToString(), entity.UserId.ToString(), - entity.IsBanned, entity.EditingEnabled, - entity.CanJoin, + entity.Status, entity.LastInteractedAt ); } @@ -38,18 +37,16 @@ public class WhiteboardMembershipRepository Id = new Guid(model.Id.IdValue), WhiteboardId = new Guid(model.WhiteboardId.IdValue), UserId = new Guid(model.UserId.IdValue), - IsBanned = model.IsBanned.IsBannedValue, EditingEnabled = model.EditingEnabled.EditingEnabledValue, - CanJoin = model.CanJoin.CanJoinValue, + Status = model.Status, LastInteractedAt = model.LastInteractedAt.LastInteractedAtValue }; } protected override void UpdateEntity(WhiteboardMembership entity, Domain.Models.WhiteboardMembership.WhiteboardMembership model) { - entity.IsBanned = model.IsBanned.IsBannedValue; entity.EditingEnabled = model.EditingEnabled.EditingEnabledValue; - entity.CanJoin = model.CanJoin.CanJoinValue; + entity.Status = model.Status; entity.LastInteractedAt = model.LastInteractedAt.LastInteractedAtValue; } diff --git a/dotnet/AipsRT/Hubs/WhiteboardHub.cs b/dotnet/AipsRT/Hubs/WhiteboardHub.cs index 38e49f7..2e95221 100644 --- a/dotnet/AipsRT/Hubs/WhiteboardHub.cs +++ b/dotnet/AipsRT/Hubs/WhiteboardHub.cs @@ -1,6 +1,10 @@ -using AipsCore.Application.Abstract.MessageBroking; -using AipsCore.Application.Common.Message.MoveShape; +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.Query.GetMembershipStatus; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; +using AipsRT.Model.Memberships; using AipsRT.Model.Whiteboard; using AipsRT.Model.Whiteboard.Shapes; using AipsRT.Model.Whiteboard.Structs; @@ -15,27 +19,87 @@ public class WhiteboardHub : Hub { private readonly WhiteboardManager _whiteboardManager; private readonly IMessagingService _messagingService; + private readonly MembershipService _membershipService; - public WhiteboardHub(WhiteboardManager whiteboardManager, IMessagingService messagingService) + public WhiteboardHub(WhiteboardManager whiteboardManager, IMessagingService messagingService, MembershipService membershipService) { _whiteboardManager = whiteboardManager; _messagingService = messagingService; + _membershipService = membershipService; } public async Task JoinWhiteboard(Guid whiteboardId) { if (!_whiteboardManager.WhiteboardExists(whiteboardId)) + { await _whiteboardManager.LoadWhiteboard(whiteboardId); + } await Groups.AddToGroupAsync(Context.ConnectionId, whiteboardId.ToString()); + + var whiteboard = _whiteboardManager.GetWhiteboard(whiteboardId)!; - var state = _whiteboardManager.GetWhiteboard(whiteboardId)!; + var userId = CurrentUserId; + var ownerId = whiteboard.OwnerId; - _whiteboardManager.AddUserToWhiteboard(Guid.Parse(Context.UserIdentifier!), whiteboardId); + WhiteboardMembershipStatus status; + + if (userId == ownerId) + { + status = WhiteboardMembershipStatus.Accepted; + } + else + { + status = await _membershipService.GetMembershipStatus(whiteboardId, userId); + } - await Clients.Caller.SendAsync("InitWhiteboard", state); - await Clients.GroupExcept(whiteboardId.ToString(), Context.ConnectionId) - .SendAsync("Joined", Context.UserIdentifier!); + if (status == WhiteboardMembershipStatus.Accepted) + { + _whiteboardManager.AddUserToWhiteboard(userId, whiteboardId); + + var state = _whiteboardManager.GetWhiteboard(whiteboardId)!; + await Clients.Caller.SendAsync("InitWhiteboard", state); + + await Clients.GroupExcept(whiteboardId.ToString(), Context.ConnectionId).SendAsync("Joined", Context.UserIdentifier!); + } + else + { + await Clients.Caller.SendAsync("WaitingForApproval", userId.ToString()); + + var user = whiteboard.Users.First(u => u.UserId == userId); + + await Clients.User(ownerId.ToString()).SendAsync("UserWaitingForApproval", user); + } + } + + public async Task AcceptUser(Guid targetUserId) + { + var whiteboard = CurrentWhiteboard; + + await _messagingService.AcceptedUser(new AcceptUserRequestToJoinCommand(whiteboard.WhiteboardId.ToString(), targetUserId.ToString())); + + await Clients.User(targetUserId.ToString()).SendAsync("Accepted"); + await Clients.User(targetUserId.ToString()).SendAsync("InitWhiteboard", whiteboard); + } + + public async Task RejectUser(Guid targetUserId) + { + var whiteboard = CurrentWhiteboard; + + await _messagingService.RejectedUser(new RejectUserRequestToJoinCommand(whiteboard.WhiteboardId.ToString(), targetUserId.ToString())); + + await Clients.User(targetUserId.ToString()).SendAsync("Rejected"); + } + + public async Task CancelJoinRequest() + { + var userId = CurrentUserId; + var whiteboard = _whiteboardManager.GetWhiteboardForUser(userId); + + if (whiteboard != null) + { + await Clients.User(whiteboard.OwnerId.ToString()).SendAsync("UserCanceledJoinRequest", userId.ToString()); + } } public async Task LeaveWhiteboard(Guid whiteboardId) diff --git a/dotnet/AipsRT/Model/Memberships/MembershipService.cs b/dotnet/AipsRT/Model/Memberships/MembershipService.cs new file mode 100644 index 0000000..4c64e73 --- /dev/null +++ b/dotnet/AipsRT/Model/Memberships/MembershipService.cs @@ -0,0 +1,21 @@ +using AipsCore.Application.Abstract; +using AipsCore.Application.Models.Whiteboard.Query.GetMembershipStatus; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; + +namespace AipsRT.Model.Memberships; + +public class MembershipService +{ + private readonly IDispatcher _dispatcher; + + public MembershipService(IDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task GetMembershipStatus(Guid whiteboardId, Guid userId) + { + var query = new GetMembershipStatusQuery(whiteboardId.ToString(), userId.ToString()); + return await _dispatcher.Execute(query); + } +} \ No newline at end of file diff --git a/dotnet/AipsRT/Model/Users/User.cs b/dotnet/AipsRT/Model/Users/User.cs new file mode 100644 index 0000000..900c017 --- /dev/null +++ b/dotnet/AipsRT/Model/Users/User.cs @@ -0,0 +1,17 @@ +namespace AipsRT.Model.Users; + +public class User +{ + public Guid UserId { get; private set; } + + public string Username { get; private set; } + + public string Email { get; private set; } + + public User(Guid userId, string username, string email) + { + UserId = userId; + Username = username; + Email = email; + } +} \ No newline at end of file diff --git a/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs b/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs index 1ae70f7..3ed67ad 100644 --- a/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs +++ b/dotnet/AipsRT/Model/Whiteboard/GetWhiteboardService.cs @@ -1,7 +1,9 @@ using AipsCore.Application.Abstract; using AipsCore.Application.Models.Whiteboard.Query.GetWhiteboardInfoRT; using AipsCore.Domain.Models.Shape.Enums; +using AipsCore.Domain.Models.WhiteboardMembership.Enums; using AipsRT.Model.Whiteboard.Shapes.Map; +using AipsRT.Model.Users; namespace AipsRT.Model.Whiteboard; @@ -28,6 +30,11 @@ public class GetWhiteboardService OwnerId = entity.OwnerId, }; + foreach (var membership in entity.Memberships) + { + whiteboard.AddUser(new User(membership.UserId, membership.User!.UserName!, membership.User.Email!)); + } + foreach (var shape in entity.Shapes) { switch (shape.Type) diff --git a/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs b/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs index 074a955..8d48b1f 100644 --- a/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs +++ b/dotnet/AipsRT/Model/Whiteboard/Whiteboard.cs @@ -1,13 +1,16 @@ using AipsRT.Model.Whiteboard.Shapes; +using AipsRT.Model.Users; namespace AipsRT.Model.Whiteboard; public class Whiteboard { public Guid WhiteboardId { get; set; } - + public Guid OwnerId { get; set; } + public List Users { get; } = []; + public List Shapes { get; } = []; public List Rectangles { get; } = []; @@ -20,22 +23,24 @@ public class Whiteboard Shapes.Add(shape); Rectangles.Add(shape); } - + public void AddArrow(Arrow shape) { Shapes.Add(shape); Arrows.Add(shape); } - + public void AddLine(Line shape) { Shapes.Add(shape); Lines.Add(shape); } - + public void AddTextShape(TextShape shape) { Shapes.Add(shape); TextShapes.Add(shape); } + + public void AddUser(User user) => Users.Add(user); } \ No newline at end of file diff --git a/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs b/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs index 75125e6..ed5eede 100644 --- a/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs +++ b/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs @@ -47,9 +47,9 @@ public class WhiteboardManager return _userInWhiteboards[userId]; } - public void RemoveUserFromWhiteboard(Guid userId, Guid whiteboardId) + public void RemoveUserFromWhiteboard(Guid userId) { - _userInWhiteboards.TryRemove(whiteboardId, out _); + _userInWhiteboards.TryRemove(userId, out _); } public Whiteboard? GetWhiteboardForUser(Guid userId) diff --git a/dotnet/AipsRT/Program.cs b/dotnet/AipsRT/Program.cs index 97c3f0b..8d0d68e 100644 --- a/dotnet/AipsRT/Program.cs +++ b/dotnet/AipsRT/Program.cs @@ -1,6 +1,7 @@ using AipsCore.Application.Common.Message.ErrorMessage; using AipsCore.Infrastructure.DI; using AipsRT.Hubs; +using AipsRT.Model.Memberships; using AipsRT.Model.Whiteboard; using AipsRT.Services; using AipsRT.Services.Interfaces; @@ -21,6 +22,8 @@ builder.Services.AddAipsMessageHandlers(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); +builder.Services.AddTransient(); + builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs index 97838e0..ca8b3cb 100644 --- a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs +++ b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs @@ -1,5 +1,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 AipsRT.Model.Whiteboard.Shapes; namespace AipsRT.Services.Interfaces; @@ -10,6 +12,9 @@ public interface IMessagingService Task CreatedArrow(Guid whiteboardId, Arrow arrow); Task CreateLine(Guid whiteboardId, Line line); Task CreateTextShape(Guid whiteboardId, TextShape textShape); - + Task MoveShape(Guid whiteboardId, MoveShapeCommand moveShape); + + Task AcceptedUser(AcceptUserRequestToJoinCommand command); + Task RejectedUser(RejectUserRequestToJoinCommand command); } \ No newline at end of file diff --git a/dotnet/AipsRT/Services/MessagingService.cs b/dotnet/AipsRT/Services/MessagingService.cs index 3f139bd..7789852 100644 --- a/dotnet/AipsRT/Services/MessagingService.cs +++ b/dotnet/AipsRT/Services/MessagingService.cs @@ -1,14 +1,18 @@ using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Common.Message.AcceptUserRequestToJoin; using AipsCore.Application.Common.Message.AddArrow; using AipsCore.Application.Common.Message.AddLine; 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.Models.Shape.Command.CreateArrow; using AipsCore.Application.Models.Shape.Command.CreateLine; using AipsCore.Application.Models.Shape.Command.CreateRectangle; 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 AipsRT.Model.Whiteboard.Shapes; using AipsRT.Services.Interfaces; @@ -100,4 +104,16 @@ public class MessagingService : IMessagingService var message = new MoveShapeMessage(whiteboardId, moveShape); await _messagePublisher.PublishAsync(message); } + + public async Task AcceptedUser(AcceptUserRequestToJoinCommand command) + { + var message = new AcceptUserRequestToJoinMessage(command); + await _messagePublisher.PublishAsync(message); + } + + public async Task RejectedUser(RejectUserRequestToJoinCommand command) + { + var message = new RejectUserRequestToJoinMessage(command); + await _messagePublisher.PublishAsync(message); + } } \ No newline at end of file diff --git a/dotnet/AipsWebApi/Controllers/WhiteboardController.cs b/dotnet/AipsWebApi/Controllers/WhiteboardController.cs index fd418ae..109cc5c 100644 --- a/dotnet/AipsWebApi/Controllers/WhiteboardController.cs +++ b/dotnet/AipsWebApi/Controllers/WhiteboardController.cs @@ -1,10 +1,10 @@ using AipsCore.Application.Abstract; using AipsCore.Application.Models.Whiteboard.Command.CreateWhiteboard; using AipsCore.Application.Models.Whiteboard.Command.DeleteWhiteboard; +using AipsCore.Application.Models.Whiteboard.Command.JoinWithCode; using AipsCore.Application.Models.Whiteboard.Query.GetRecentWhiteboards; using AipsCore.Application.Models.Whiteboard.Query.GetWhiteboard; using AipsCore.Application.Models.Whiteboard.Query.GetWhiteboardHistory; -using AipsCore.Application.Models.WhiteboardMembership.Command.CreateWhiteboardMembership; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Whiteboard = AipsCore.Infrastructure.Persistence.Whiteboard.Whiteboard; @@ -69,7 +69,7 @@ public class WhiteboardController : ControllerBase [Authorize] [HttpPost("join")] - public async Task JoinWhiteboard(CreateWhiteboardMembershipCommand command, CancellationToken cancellationToken) + public async Task> JoinWhiteboardWithCode(JoinWithCodeCommand command, CancellationToken cancellationToken) { var result = await _dispatcher.Execute(command, cancellationToken); return Ok(result); diff --git a/dotnet/AipsWorker/Messages/MessageTypesProvider.cs b/dotnet/AipsWorker/Messages/MessageTypesProvider.cs index 3c6b538..a2c62b4 100644 --- a/dotnet/AipsWorker/Messages/MessageTypesProvider.cs +++ b/dotnet/AipsWorker/Messages/MessageTypesProvider.cs @@ -1,9 +1,11 @@ using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Common.Message.AcceptUserRequestToJoin; using AipsCore.Application.Common.Message.AddArrow; using AipsCore.Application.Common.Message.AddLine; using AipsCore.Application.Common.Message.AddRectangle; using AipsCore.Application.Common.Message.AddTextShape; using AipsCore.Application.Common.Message.MoveShape; +using AipsCore.Application.Common.Message.RejectUserRequestToJoin; namespace AipsWorker.Messages; @@ -17,7 +19,9 @@ public class MessageTypesProvider : IMessageTypesProvider typeof(AddLineMessage), typeof(AddRectangleMessage), typeof(AddTextShapeMessage), - typeof(MoveShapeMessage) + typeof(MoveShapeMessage), + typeof(AcceptUserRequestToJoinMessage), + typeof(RejectUserRequestToJoinMessage) ]; } } \ No newline at end of file diff --git a/front/bun.lock b/front/bun.lock index a487960..9f9773b 100644 --- a/front/bun.lock +++ b/front/bun.lock @@ -152,15 +152,15 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], - "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], @@ -298,35 +298,35 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="], + "@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.55.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/type-utils": "8.55.0", "@typescript-eslint/utils": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.55.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.55.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.55.0", "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.55.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.55.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.55.0", "@typescript-eslint/tsconfig-utils": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.55.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA=="], "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="], - "@volar/language-core": ["@volar/language-core@2.4.27", "", { "dependencies": { "@volar/source-map": "2.4.27" } }, "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ=="], + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], - "@volar/source-map": ["@volar/source-map@2.4.27", "", {}, "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg=="], + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], - "@volar/typescript": ["@volar/typescript@2.4.27", "", { "dependencies": { "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg=="], + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], "@vue-macros/common": ["@vue-macros/common@3.1.2", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng=="], @@ -336,35 +336,35 @@ "@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + "@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="], - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.29", "", { "dependencies": { "@vue/compiler-core": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg=="], - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="], + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.29", "@vue/compiler-dom": "3.5.29", "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA=="], - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="], + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw=="], "@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="], - "@vue/devtools-core": ["@vue/devtools-core@8.0.6", "", { "dependencies": { "@vue/devtools-kit": "^8.0.6", "@vue/devtools-shared": "^8.0.6", "mitt": "^3.0.1", "nanoid": "^5.1.5", "pathe": "^2.0.3", "vite-hot-client": "^2.1.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-fN7iVtpSQQdtMORWwVZ1JiIAKriinhD+lCHqPw9Rr252ae2TczILEmW0zcAZifPW8HfYcbFkn+h7Wv6kQQCayw=="], + "@vue/devtools-core": ["@vue/devtools-core@8.0.7", "", { "dependencies": { "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-PmpiPxvg3Of80ODHVvyckxwEW1Z02VIAvARIZS1xegINn3VuNQLm9iHUmKD+o6cLkMNWV8OG8x7zo0kgydZgdg=="], - "@vue/devtools-kit": ["@vue/devtools-kit@8.0.6", "", { "dependencies": { "@vue/devtools-shared": "^8.0.6", "birpc": "^2.6.1", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^2.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw=="], + "@vue/devtools-kit": ["@vue/devtools-kit@8.0.7", "", { "dependencies": { "@vue/devtools-shared": "^8.0.7", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw=="], - "@vue/devtools-shared": ["@vue/devtools-shared@8.0.6", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg=="], + "@vue/devtools-shared": ["@vue/devtools-shared@8.0.7", "", {}, "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA=="], - "@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.6.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.35.1", "fast-glob": "^3.3.3", "typescript-eslint": "^8.35.1", "vue-eslint-parser": "^10.2.0" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ=="], + "@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="], - "@vue/language-core": ["@vue/language-core@3.2.4", "", { "dependencies": { "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew=="], + "@vue/language-core": ["@vue/language-core@3.2.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="], - "@vue/reactivity": ["@vue/reactivity@3.5.28", "", { "dependencies": { "@vue/shared": "3.5.28" } }, "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw=="], + "@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="], - "@vue/runtime-core": ["@vue/runtime-core@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ=="], + "@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="], - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/runtime-core": "3.5.28", "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA=="], + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/runtime-core": "3.5.29", "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg=="], - "@vue/server-renderer": ["@vue/server-renderer@3.5.28", "", { "dependencies": { "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "vue": "3.5.28" } }, "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg=="], + "@vue/server-renderer": ["@vue/server-renderer@3.5.29", "", { "dependencies": { "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "vue": "3.5.29" } }, "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g=="], - "@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], + "@vue/shared": ["@vue/shared@3.5.29", "", {}, "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg=="], "@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="], @@ -374,7 +374,7 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="], @@ -460,7 +460,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], "eslint-plugin-oxlint": ["eslint-plugin-oxlint@1.42.0", "", { "dependencies": { "jsonc-parser": "^3.3.1" } }, "sha512-3hB/TDbS0f+8ZnPffl0Z/wZ7Yc5NeY5slrxG60kEWInAA9047pdqRcv+Ckk/5KiL3HS2vWtHvmkavPKSFZVKxA=="], @@ -592,7 +592,7 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], @@ -776,7 +776,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.55.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.55.0", "@typescript-eslint/parser": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw=="], + "typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], @@ -806,19 +806,19 @@ "vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="], - "vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.0.6", "", { "dependencies": { "@vue/devtools-core": "^8.0.6", "@vue/devtools-kit": "^8.0.6", "@vue/devtools-shared": "^8.0.6", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-IiTCIJDb1ZliOT8fPbYXllyfgARzz1+R1r8RN9ScGIDzAB6o8bDME1a9JjrfdSJibL7i8DIPQH+pGv0U7haBeA=="], + "vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.0.7", "", { "dependencies": { "@vue/devtools-core": "^8.0.7", "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-BWj/ykGpqVAJVdPyHmSTUm44buz3jPv+6jnvuFdQSRH0kAgP1cEIE4doHiFyqHXOmuB5EQVR/nh2g9YRiRNs9g=="], "vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.2", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q=="], "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], - "vue": ["vue@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", "@vue/runtime-dom": "3.5.28", "@vue/server-renderer": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg=="], + "vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="], "vue-eslint-parser": ["vue-eslint-parser@10.4.0", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg=="], - "vue-router": ["vue-router@5.0.2", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.0", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w=="], + "vue-router": ["vue-router@5.0.3", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw=="], - "vue-tsc": ["vue-tsc@3.2.4", "", { "dependencies": { "@volar/typescript": "2.4.27", "@vue/language-core": "3.2.4" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g=="], + "vue-tsc": ["vue-tsc@3.2.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -850,14 +850,34 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], + + "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.55.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.55.0", "@typescript-eslint/tsconfig-utils": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw=="], + + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], + + "@vue-macros/common/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="], + + "@vue/babel-plugin-jsx/@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], + + "@vue/babel-plugin-resolve-type/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="], "@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="], - "@vue/devtools-core/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -872,9 +892,41 @@ "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], + + "vite-plugin-vue-inspector/@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + "vue-router/@vue/devtools-api": ["@vue/devtools-api@8.0.6", "", { "dependencies": { "@vue/devtools-kit": "^8.0.6" } }, "sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.55.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.55.0", "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.55.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="], + + "@vue-macros/common/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], + + "@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + + "@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="], + + "@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="], + + "@vue/babel-plugin-resolve-type/@vue/compiler-sfc/@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], "@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="], @@ -885,5 +937,21 @@ "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], "sass/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + + "vite-plugin-vue-inspector/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="], + + "vite-plugin-vue-inspector/@vue/compiler-dom/@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="], + + "vue-router/@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@8.0.6", "", { "dependencies": { "@vue/devtools-shared": "^8.0.6", "birpc": "^2.6.1", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^2.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw=="], + + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "vue-router/@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@8.0.6", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg=="], } } diff --git a/front/package.json b/front/package.json index bdb898f..40ba56c 100644 --- a/front/package.json +++ b/front/package.json @@ -19,16 +19,16 @@ "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", "pinia": "^3.0.4", - "vue": "^3.5.27", - "vue-router": "^5.0.1" + "vue": "^3.5.29", + "vue-router": "^5.0.3" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", - "@types/node": "^24.10.9", - "@vitejs/plugin-vue": "^6.0.3", - "@vue/eslint-config-typescript": "^14.6.0", + "@types/node": "^24.12.0", + "@vitejs/plugin-vue": "^6.0.4", + "@vue/eslint-config-typescript": "^14.7.0", "@vue/tsconfig": "^0.8.1", - "eslint": "^9.39.2", + "eslint": "^9.39.4", "eslint-plugin-oxlint": "~1.42.0", "eslint-plugin-vue": "~10.7.0", "jiti": "^2.6.1", @@ -37,8 +37,8 @@ "sass-embedded": "^1.97.3", "typescript": "~5.9.3", "vite": "^7.3.1", - "vite-plugin-vue-devtools": "^8.0.5", - "vue-tsc": "^3.2.4" + "vite-plugin-vue-devtools": "^8.0.7", + "vue-tsc": "^3.2.5" }, "engines": { "node": "^20.19.0 || >=22.12.0" diff --git a/front/src/enums/index.ts b/front/src/enums/index.ts index 21b7edd..170f8ef 100644 --- a/front/src/enums/index.ts +++ b/front/src/enums/index.ts @@ -9,3 +9,14 @@ export enum WhiteboardState { Inactive, Deleted } + +export enum MembershipStatus { + Pending, + Accepted, + Rejected, + Active, + Inactive, + Cancelled, + Kicked, + Banned +} diff --git a/front/src/services/authService.ts b/front/src/services/authService.ts index fd74c70..b1f41de 100644 --- a/front/src/services/authService.ts +++ b/front/src/services/authService.ts @@ -37,6 +37,6 @@ export const authService = { const userId = raw?.userId ?? '' const username = raw?.userName ?? raw?.UserName ?? raw?.username ?? raw?.name ?? '' const email = raw?.email ?? raw?.Email ?? '' - return { userId, username, email } + return { userId: userId, username, email } }, } diff --git a/front/src/services/whiteboardHubService.ts b/front/src/services/whiteboardHubService.ts index 0ef37f8..beecd30 100644 --- a/front/src/services/whiteboardHubService.ts +++ b/front/src/services/whiteboardHubService.ts @@ -1,5 +1,6 @@ import { SignalRService } from '@/services/signalr.ts' import type { Arrow, Line, MoveShapeCommand, Rectangle, TextShape, Whiteboard } from '@/types/whiteboard.ts' +import type {User} from "@/types"; const client = new SignalRService(`/hubs/whiteboard`) @@ -76,6 +77,38 @@ export const whiteboardHubService = { client.on('Leaved', callback) }, + onWaitingForApproval(callback: (userId: string) => void) { + client.on('WaitingForApproval', callback) + }, + + onUserWaitingForApproval(callback: (user: User) => void) { + client.on('UserWaitingForApproval', callback) + }, + + onAccepted(callback: () => void) { + client.on('Accepted', callback) + }, + + onRejected(callback: () => void) { + client.on('Rejected', callback) + }, + + onUserCanceledJoinRequest(callback: (userId: string) => void) { + client.on('UserCanceledJoinRequest', callback) + }, + + async acceptUser(userId: string) { + await client.invoke('AcceptUser', userId) + }, + + async rejectUser(userId: string) { + await client.invoke('RejectUser', userId) + }, + + async cancelJoinRequest() { + await client.invoke('CancelJoinRequest') + }, + offAll() { client.off('InitWhiteboard') client.off('AddedRectangle') @@ -85,5 +118,10 @@ export const whiteboardHubService = { client.off('MovedShape') client.off('Joined') client.off('Leaved') + client.off('WaitingForApproval') + client.off('UserWaitingForApproval') + client.off('Accepted') + client.off('Rejected') + client.off('UserCanceledJoinRequest') }, } diff --git a/front/src/services/whiteboardService.ts b/front/src/services/whiteboardService.ts index db0711e..0757a88 100644 --- a/front/src/services/whiteboardService.ts +++ b/front/src/services/whiteboardService.ts @@ -1,4 +1,4 @@ -import type {Whiteboard} from "@/types"; +import type {JoinResult, Whiteboard} from "@/types"; import {api} from './api' export const whiteboardService = { @@ -22,6 +22,14 @@ export const whiteboardService = { async deleteWhiteboard(id: string): Promise { await api.delete(`/api/Whiteboard/${id}`) + }, + + async joinWhiteboard(code: string): Promise { + const raw = await api.post(`/api/Whiteboard/join`, {code: code}) + return { + whiteboardId: raw.whiteboardId, + status: raw.status + } } } diff --git a/front/src/stores/whiteboard.ts b/front/src/stores/whiteboard.ts index 01c7c17..f30335e 100644 --- a/front/src/stores/whiteboard.ts +++ b/front/src/stores/whiteboard.ts @@ -2,9 +2,14 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' import type { Arrow, Line, Rectangle, Shape, ShapeTool, ShapeType, TextShape, Whiteboard } from '@/types/whiteboard.ts' import { whiteboardHubService } from '@/services/whiteboardHubService.ts' +import {useWhiteboardsStore} from "@/stores/whiteboards.ts"; +import router from "@/router"; +import type {User} from "@/types"; export const useWhiteboardStore = defineStore('whiteboard', () => { const whiteboard = ref(null) + const pendingUsers = ref([]) + const selectedTool = ref('hand') const isConnected = ref(false) const isLoading = ref(false) @@ -27,6 +32,99 @@ export const useWhiteboardStore = defineStore('whiteboard', () => { } }) + async function initializeSession(id: string) { + isLoading.value = true; + error.value = null; + + try{ + await whiteboardHubService.connect() + isConnected.value = true; + + registerHubEvents() + + await whiteboardHubService.joinWhiteboard(id) + } catch (e: any) { + error.value = e?.message ?? 'Failed to join whiteboard' + isLoading.value = false + } + } + + function registerHubEvents() { + whiteboardHubService.onInitWhiteboard((wb) => { + deselectShape() + whiteboard.value = wb + isLoading.value = false + }) + + whiteboardHubService.onAddedRectangle((rectangle) => { + whiteboard.value?.rectangles.push(rectangle) + }) + + whiteboardHubService.onAddedArrow((arrow) => { + whiteboard.value?.arrows.push(arrow) + }) + + whiteboardHubService.onAddedLine((line) => { + whiteboard.value?.lines.push(line) + }) + + whiteboardHubService.onAddedTextShape((textShape) => { + whiteboard.value?.textShapes.push(textShape) + }) + + whiteboardHubService.onMovedShape((command) => { + applyMoveShape(command.shapeId, command.newPositionX, command.newPositionY) + }) + + whiteboardHubService.onJoined((userId) => { + console.log('User joined:', userId) + }) + + whiteboardHubService.onLeaved((userId) => { + console.log('User left:', userId) + }) + + whiteboardHubService.onWaitingForApproval(() => { + const infoStore = useWhiteboardsStore() + infoStore.startWaitingToJoin() + }) + + whiteboardHubService.onUserWaitingForApproval((user) => { + if (!pendingUsers.value.includes(user)) { + pendingUsers.value.push(user) + } + }) + + whiteboardHubService.onAccepted(() => { + const infoStore = useWhiteboardsStore() + infoStore.stopWaitingToJoin() + }) + + whiteboardHubService.onRejected(() => { + router.push('/') + alert('Your request to join was rejected.') + }) + + whiteboardHubService.onUserCanceledJoinRequest((userId) => { + pendingUsers.value = pendingUsers.value.filter(user => user.userId !== userId) + }) + } + + async function approveUser(userId: string) { + await whiteboardHubService.acceptUser(userId) + pendingUsers.value = pendingUsers.value.filter(user => user.userId !== userId) + } + + async function rejectUser(userId: string) { + await whiteboardHubService.rejectUser(userId) + pendingUsers.value = pendingUsers.value.filter(user => user.userId !== userId) + } + + async function cancelJoinRequest() { + await whiteboardHubService.cancelJoinRequest() + whiteboard.value = null + } + async function joinWhiteboard(id: string) { isLoading.value = true error.value = null @@ -35,39 +133,7 @@ export const useWhiteboardStore = defineStore('whiteboard', () => { await whiteboardHubService.connect() isConnected.value = true - whiteboardHubService.onInitWhiteboard((wb) => { - deselectShape() - whiteboard.value = wb - isLoading.value = false - }) - - whiteboardHubService.onAddedRectangle((rectangle) => { - whiteboard.value?.rectangles.push(rectangle) - }) - - whiteboardHubService.onAddedArrow((arrow) => { - whiteboard.value?.arrows.push(arrow) - }) - - whiteboardHubService.onAddedLine((line) => { - whiteboard.value?.lines.push(line) - }) - - whiteboardHubService.onAddedTextShape((textShape) => { - whiteboard.value?.textShapes.push(textShape) - }) - - whiteboardHubService.onMovedShape((command) => { - applyMoveShape(command.shapeId, command.newPositionX, command.newPositionY) - }) - - whiteboardHubService.onJoined((userId) => { - console.log('User joined:', userId) - }) - - whiteboardHubService.onLeaved((userId) => { - console.log('User left:', userId) - }) + registerHubEvents() await whiteboardHubService.joinWhiteboard(id) } catch (e: any) { @@ -184,6 +250,10 @@ export const useWhiteboardStore = defineStore('whiteboard', () => { toolColor, toolThickness, toolTextSize, + pendingUsers, + approveUser, + rejectUser, + cancelJoinRequest, joinWhiteboard, leaveWhiteboard, addRectangle, diff --git a/front/src/stores/whiteboards.ts b/front/src/stores/whiteboards.ts index a67650a..b544299 100644 --- a/front/src/stores/whiteboards.ts +++ b/front/src/stores/whiteboards.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import type { Whiteboard } from '@/types' +import type {JoinResult, Whiteboard} from '@/types' import { whiteboardService } from '@/services/whiteboardService' export const useWhiteboardsStore = defineStore('whiteboards', () => { @@ -8,6 +8,7 @@ export const useWhiteboardsStore = defineStore('whiteboards', () => { const recentWhiteboards = ref([]) const currentWhiteboard = ref(null) const isLoading = ref(false) + const isWaitingToJoin = ref(false) const error = ref(null) async function getWhiteboardHistory() { @@ -53,6 +54,27 @@ export const useWhiteboardsStore = defineStore('whiteboards', () => { return newWhiteboard.id; } + async function joinWhiteboardWithCode(code: string): Promise { + isLoading.value = true; + + try { + return await whiteboardService.joinWhiteboard(code); + } catch (err: any) { + error.value = err.message ?? 'Failed to join whiteboard'; + throw err; + } finally { + isLoading.value = false; + } + } + + function startWaitingToJoin() { + isWaitingToJoin.value = true; + } + + function stopWaitingToJoin() { + isWaitingToJoin.value = false; + } + async function deleteWhiteboard(id: string): Promise { isLoading.value = true error.value = null @@ -92,10 +114,14 @@ export const useWhiteboardsStore = defineStore('whiteboards', () => { ownedWhiteboards: ownedWhiteboards, recentWhiteboards: recentWhiteboards, isLoading, + isWaitingToJoin, error, getWhiteboardHistory: getWhiteboardHistory, getRecentWhiteboards: getRecentWhiteboards, createNewWhiteboard: createNewWhiteboard, + joinWhiteboardWithCode: joinWhiteboardWithCode, + startWaitingToJoin: startWaitingToJoin, + stopWaitingToJoin: stopWaitingToJoin, deleteWhiteboard: deleteWhiteboard, getCurrentWhiteboard: getCurrentWhiteboard, selectWhiteboard: selectWhiteboard, diff --git a/front/src/types/index.ts b/front/src/types/index.ts index f26dc5e..d58463f 100644 --- a/front/src/types/index.ts +++ b/front/src/types/index.ts @@ -1,4 +1,4 @@ -import {type WhiteboardJoinPolicy, WhiteboardState} from "@/enums"; +import {MembershipStatus, type WhiteboardJoinPolicy, WhiteboardState} from "@/enums"; export interface User { userId: string @@ -22,6 +22,11 @@ export interface AuthResponse { refreshToken: string } +export interface JoinResult { + whiteboardId: string + status: MembershipStatus +} + export interface Whiteboard { id: string ownerId: string diff --git a/front/src/views/HomeView.vue b/front/src/views/HomeView.vue index 1d4d6a1..5a57e2a 100644 --- a/front/src/views/HomeView.vue +++ b/front/src/views/HomeView.vue @@ -1,11 +1,12 @@