Merge pull request #1670 from btcpayserver/refactor/notifications2

Introduce INotificationHandler
This commit is contained in:
Nicolas Dorier 2020-06-17 10:12:16 +09:00 committed by GitHub
commit 9f12fe7e0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 85 additions and 83 deletions

View file

@ -27,13 +27,19 @@ namespace BTCPayServer.Controllers
private readonly ApplicationDbContext _db;
private readonly NotificationSender _notificationSender;
private readonly UserManager<ApplicationUser> _userManager;
private readonly NotificationManager _notificationManager;
public NotificationsController(BTCPayServerEnvironment env, ApplicationDbContext db, NotificationSender notificationSender, UserManager<ApplicationUser> userManager)
public NotificationsController(BTCPayServerEnvironment env,
ApplicationDbContext db,
NotificationSender notificationSender,
UserManager<ApplicationUser> userManager,
NotificationManager notificationManager)
{
_env = env;
_db = db;
_notificationSender = notificationSender;
_userManager = userManager;
_notificationManager = notificationManager;
}
[HttpGet]
@ -50,7 +56,7 @@ namespace BTCPayServer.Controllers
.OrderByDescending(a => a.Created)
.Skip(skip).Take(count)
.Where(a => a.ApplicationUserId == userId)
.Select(a => a.ToViewModel())
.Select(a => _notificationManager.ToViewModel(a))
.ToList(),
Total = _db.Notifications.Where(a => a.ApplicationUserId == userId).Count()
};

View file

@ -47,6 +47,7 @@ using Serilog;
using BTCPayServer.Security.GreenField;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
namespace BTCPayServer.Hosting
{
@ -220,6 +221,7 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
services.AddSingleton<INotificationHandler, NewVersionNotification.Handler>();
services.TryAddSingleton<ExplorerClientProvider>();
services.TryAddSingleton<Bitpay>(o =>

View file

@ -27,31 +27,4 @@ namespace BTCPayServer.Models.NotificationViewModels
public string ActionLink { get; set; }
public bool Seen { get; set; }
}
public static class NotificationViewModelExt
{
static Dictionary<string, Type> _NotificationTypes;
static NotificationViewModelExt()
{
_NotificationTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Select(t => (t, NotificationType: t.GetCustomAttribute<NotificationAttribute>()?.NotificationType))
.Where(t => t.NotificationType is string)
.ToDictionary(o => o.NotificationType, o => o.t);
}
public static NotificationViewModel ToViewModel(this NotificationData data)
{
var casted = (BaseNotification)JsonConvert.DeserializeObject(ZipUtils.Unzip(data.Blob), _NotificationTypes[data.NotificationType]);
var obj = new NotificationViewModel
{
Id = data.Id,
Created = data.Created,
Seen = data.Seen
};
casted.FillViewModel(obj);
return obj;
}
}
}

View file

@ -1,15 +0,0 @@
using System;
using BTCPayServer.Data;
using BTCPayServer.Models.NotificationViewModels;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications.Blobs
{
// Make sure to keep all Blob Notification classes in same namespace
// because of dependent initialization and parsing to view models logic
// IndexViewModel.cs#32
public abstract class BaseNotification
{
public abstract void FillViewModel(NotificationViewModel data);
}
}

View file

@ -4,9 +4,17 @@ using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications.Blobs
{
[Notification("newversion")]
internal class NewVersionNotification : BaseNotification
internal class NewVersionNotification
{
internal class Handler : NotificationHandler<NewVersionNotification>
{
public override string NotificationType => "newversion";
protected override void FillViewModel(NewVersionNotification notification, NotificationViewModel vm)
{
vm.Body = $"New version {notification.Version} released!";
vm.ActionLink = $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{notification.Version}";
}
}
public NewVersionNotification()
{
@ -16,11 +24,5 @@ namespace BTCPayServer.Services.Notifications.Blobs
Version = version;
}
public string Version { get; set; }
public override void FillViewModel(NotificationViewModel vm)
{
vm.Body = $"New version {Version} released!";
vm.ActionLink = $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{Version}";
}
}
}

View file

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Notifications.Blobs
{
[AttributeUsage(AttributeTargets.Class)]
public class NotificationAttribute : Attribute
{
public NotificationAttribute(string notificationType)
{
NotificationType = notificationType;
}
public string NotificationType { get; }
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Models.NotificationViewModels;
namespace BTCPayServer.Services.Notifications
{
public interface INotificationHandler
{
string NotificationType { get; }
Type NotificationBlobType { get; }
void FillViewModel(object notification, NotificationViewModel vm);
}
public abstract class NotificationHandler<TNotification> : INotificationHandler
{
public abstract string NotificationType { get; }
Type INotificationHandler.NotificationBlobType => typeof(TNotification);
void INotificationHandler.FillViewModel(object notification, NotificationViewModel vm)
{
FillViewModel((TNotification)notification, vm);
}
protected abstract void FillViewModel(TNotification notification, NotificationViewModel vm);
}
}

View file

@ -6,8 +6,10 @@ using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Models.NotificationViewModels;
using Google.Apis.Storage.v1.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications
{
@ -16,12 +18,16 @@ namespace BTCPayServer.Services.Notifications
private readonly ApplicationDbContextFactory _factory;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IMemoryCache _memoryCache;
private readonly Dictionary<string, INotificationHandler> _handlersByNotificationType;
private readonly Dictionary<Type, INotificationHandler> _handlersByBlobType;
public NotificationManager(ApplicationDbContextFactory factory, UserManager<ApplicationUser> userManager, IMemoryCache memoryCache)
public NotificationManager(ApplicationDbContextFactory factory, UserManager<ApplicationUser> userManager, IMemoryCache memoryCache, IEnumerable<INotificationHandler> handlers)
{
_factory = factory;
_userManager = userManager;
_memoryCache = memoryCache;
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
}
private const int _cacheExpiryMs = 5000;
@ -55,7 +61,7 @@ namespace BTCPayServer.Services.Notifications
.Where(a => a.ApplicationUserId == userId && !a.Seen)
.OrderByDescending(a => a.Created)
.Take(5)
.Select(a => a.ToViewModel())
.Select(a => ToViewModel(a))
.ToList();
}
catch (System.IO.InvalidDataException)
@ -77,6 +83,33 @@ namespace BTCPayServer.Services.Notifications
return resp;
}
public NotificationViewModel ToViewModel(NotificationData data)
{
var handler = GetHandler(data.NotificationType);
var notification = JsonConvert.DeserializeObject(ZipUtils.Unzip(data.Blob), handler.NotificationBlobType);
var obj = new NotificationViewModel
{
Id = data.Id,
Created = data.Created,
Seen = data.Seen
};
handler.FillViewModel(notification, obj);
return obj;
}
public INotificationHandler GetHandler(string notificationId)
{
if (_handlersByNotificationType.TryGetValue(notificationId, out var h))
return h;
throw new InvalidOperationException($"No INotificationHandler found for {notificationId}");
}
public INotificationHandler GetHandler(Type blobType)
{
if (_handlersByBlobType.TryGetValue(blobType, out var h))
return h;
throw new InvalidOperationException($"No INotificationHandler found for {blobType.Name}");
}
}
public class NotificationSummaryViewModel

View file

@ -16,15 +16,17 @@ namespace BTCPayServer.Services.Notifications
private readonly ApplicationDbContextFactory _contextFactory;
private readonly UserManager<ApplicationUser> _userManager;
private readonly EventAggregator _eventAggregator;
private readonly NotificationManager _notificationManager;
public NotificationSender(ApplicationDbContextFactory contextFactory, UserManager<ApplicationUser> userManager, EventAggregator eventAggregator)
public NotificationSender(ApplicationDbContextFactory contextFactory, UserManager<ApplicationUser> userManager, EventAggregator eventAggregator, NotificationManager notificationManager)
{
_contextFactory = contextFactory;
_userManager = userManager;
_eventAggregator = eventAggregator;
_notificationManager = notificationManager;
}
public async Task SendNotification(NotificationScope scope, BaseNotification notification)
public async Task SendNotification(NotificationScope scope, object notification)
{
if (scope == null)
throw new ArgumentNullException(nameof(scope));
@ -41,25 +43,16 @@ namespace BTCPayServer.Services.Notifications
Id = Guid.NewGuid().ToString(),
Created = DateTimeOffset.UtcNow,
ApplicationUserId = uid,
NotificationType = GetNotificationTypeString(notification.GetType()),
NotificationType = _notificationManager.GetHandler(notification.GetType()).NotificationType,
Blob = ZipUtils.Zip(obj),
Seen = false
};
db.Notifications.Add(data);
}
await db.SaveChangesAsync();
}
}
private string GetNotificationTypeString(Type type)
{
var str = type.GetCustomAttribute<NotificationAttribute>()?.NotificationType;
if (str is null)
throw new NotSupportedException($"{type} is not a notification");
return str;
}
private async Task<string[]> GetUsers(NotificationScope scope)
{
if (scope is AdminScope)