diff --git a/BTCPayServer/App/BTCPayAppState.cs b/BTCPayServer/App/BTCPayAppState.cs index 65bdea308..95f03d3ff 100644 --- a/BTCPayServer/App/BTCPayAppState.cs +++ b/BTCPayServer/App/BTCPayAppState.cs @@ -80,6 +80,9 @@ public class BTCPayAppState : IHostedService _compositeDisposable.Add(_eventAggregator.SubscribeAsync(OnNewTransaction)); _compositeDisposable.Add(_eventAggregator.SubscribeAsync(UserNotificationsUpdatedEvent)); _compositeDisposable.Add(_eventAggregator.SubscribeAsync(InvoiceChangedEvent)); + // User events + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(UserUpdatedEvent)); + _compositeDisposable.Add(_eventAggregator.SubscribeAsync(UserDeletedEvent)); // Store events _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreCreatedEvent)); _compositeDisposable.Add(_eventAggregator.SubscribeAsync(StoreUpdatedEvent)); @@ -91,6 +94,18 @@ public class BTCPayAppState : IHostedService return Task.CompletedTask; } + private async Task UserUpdatedEvent(UserUpdatedEvent arg) + { + var ev = new ServerEvent("user-updated") { UserId = arg.User.Id }; + await _hubContext.Clients.Group(arg.User.Id).NotifyServerEvent(ev); + } + + private async Task UserDeletedEvent(UserDeletedEvent arg) + { + var ev = new ServerEvent("user-deleted") { UserId = arg.User.Id }; + await _hubContext.Clients.Group(arg.User.Id).NotifyServerEvent(ev); + } + private async Task InvoiceChangedEvent(InvoiceEvent arg) { var ev = new ServerEvent("invoice-updated") { StoreId = arg.Invoice.StoreId, InvoiceId = arg.InvoiceId }; diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs index 6f4a4db51..f6b124467 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs @@ -220,6 +220,10 @@ namespace BTCPayServer.Controllers.Greenfield ModelState.AddModelError(string.Empty, error.Description); } } + else + { + _eventAggregator.Publish(new UserUpdatedEvent(user)); + } } if (!ModelState.IsValid) @@ -259,7 +263,7 @@ namespace BTCPayServer.Controllers.Greenfield blob.ImageUrl = fileIdUri.ToString(); user.SetBlob(blob); await _userManager.UpdateAsync(user); - + _eventAggregator.Publish(new UserUpdatedEvent(user)); var model = await FromModel(user); return Ok(model); } @@ -287,6 +291,7 @@ namespace BTCPayServer.Controllers.Greenfield blob.ImageUrl = null; user.SetBlob(blob); await _userManager.UpdateAsync(user); + _eventAggregator.Publish(new UserUpdatedEvent(user)); } return Ok(); } @@ -447,6 +452,7 @@ namespace BTCPayServer.Controllers.Greenfield // Ok, this user is an admin but there are other admins as well so safe to delete await _userService.DeleteUserAndAssociatedData(user); + _eventAggregator.Publish(new UserDeletedEvent(user)); return Ok(); } diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs index b111b5551..418fc6632 100644 --- a/BTCPayServer/Controllers/UIManageController.cs +++ b/BTCPayServer/Controllers/UIManageController.cs @@ -6,6 +6,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Data; +using BTCPayServer.Events; using BTCPayServer.Fido2; using BTCPayServer.Models; using BTCPayServer.Models.ManageViewModels; @@ -45,6 +46,7 @@ namespace BTCPayServer.Controllers private readonly UserService _userService; private readonly UriResolver _uriResolver; private readonly IFileService _fileService; + private readonly EventAggregator _eventAggregator; readonly StoreRepository _StoreRepository; public UIManageController( @@ -63,8 +65,8 @@ namespace BTCPayServer.Controllers UriResolver uriResolver, IFileService fileService, UserLoginCodeService userLoginCodeService, - IHtmlHelper htmlHelper - ) + IHtmlHelper htmlHelper, + EventAggregator eventAggregator) { _userManager = userManager; _signInManager = signInManager; @@ -82,6 +84,7 @@ namespace BTCPayServer.Controllers _uriResolver = uriResolver; _fileService = fileService; _StoreRepository = storeRepository; + _eventAggregator = eventAggregator; } [HttpGet] @@ -207,9 +210,9 @@ namespace BTCPayServer.Controllers return View(model); } - if (needUpdate is true) + if (needUpdate && await _userManager.UpdateAsync(user) is { Succeeded: true }) { - needUpdate = await _userManager.UpdateAsync(user) is { Succeeded: true }; + _eventAggregator.Publish(new UserUpdatedEvent(user)); TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated"; } else @@ -348,6 +351,7 @@ namespace BTCPayServer.Controllers } await _userService.DeleteUserAndAssociatedData(user); + _eventAggregator.Publish(new UserDeletedEvent(user)); TempData[WellKnownTempData.SuccessMessage] = "Account successfully deleted."; await _signInManager.SignOutAsync(); return RedirectToAction(nameof(UIAccountController.Login), "UIAccount"); diff --git a/BTCPayServer/Events/UserDeletedEvent.cs b/BTCPayServer/Events/UserDeletedEvent.cs new file mode 100644 index 000000000..527b593be --- /dev/null +++ b/BTCPayServer/Events/UserDeletedEvent.cs @@ -0,0 +1,12 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class UserDeletedEvent(ApplicationUser user) : UserEvent(user) +{ + protected override string ToString() + { + return $"{base.ToString()} has been deleted"; + } +} + diff --git a/BTCPayServer/Events/UserEvent.cs b/BTCPayServer/Events/UserEvent.cs new file mode 100644 index 000000000..19075667e --- /dev/null +++ b/BTCPayServer/Events/UserEvent.cs @@ -0,0 +1,13 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class UserEvent(ApplicationUser user) +{ + public ApplicationUser User { get; } = user; + + protected new virtual string ToString() + { + return $"UserEvent: User \"{user.Email}\" ({user.Id})"; + } +} diff --git a/BTCPayServer/Events/UserUpdatedEvent.cs b/BTCPayServer/Events/UserUpdatedEvent.cs new file mode 100644 index 000000000..600c87d8b --- /dev/null +++ b/BTCPayServer/Events/UserUpdatedEvent.cs @@ -0,0 +1,11 @@ +using BTCPayServer.Data; + +namespace BTCPayServer.Events; + +public class UserUpdatedEvent(ApplicationUser user) : UserEvent(user) +{ + protected override string ToString() + { + return $"{base.ToString()} has been updated"; + } +}