Introduce INotificationHandler

This commit is contained in:
nicolas.dorier
2020-06-16 23:29:25 +09:00
parent 677cc3bee9
commit cff5b82b06
9 changed files with 85 additions and 83 deletions

View File

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

View File

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

View File

@@ -27,31 +27,4 @@ namespace BTCPayServer.Models.NotificationViewModels
public string ActionLink { get; set; } public string ActionLink { get; set; }
public bool Seen { 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 namespace BTCPayServer.Services.Notifications.Blobs
{ {
[Notification("newversion")] internal class NewVersionNotification
internal class NewVersionNotification : BaseNotification
{ {
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() public NewVersionNotification()
{ {
@@ -16,11 +24,5 @@ namespace BTCPayServer.Services.Notifications.Blobs
Version = version; Version = version;
} }
public string Version { get; set; } 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 System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models.NotificationViewModels; using BTCPayServer.Models.NotificationViewModels;
using Google.Apis.Storage.v1.Data;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications namespace BTCPayServer.Services.Notifications
{ {
@@ -16,12 +18,16 @@ namespace BTCPayServer.Services.Notifications
private readonly ApplicationDbContextFactory _factory; private readonly ApplicationDbContextFactory _factory;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly IMemoryCache _memoryCache; 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; _factory = factory;
_userManager = userManager; _userManager = userManager;
_memoryCache = memoryCache; _memoryCache = memoryCache;
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
} }
private const int _cacheExpiryMs = 5000; private const int _cacheExpiryMs = 5000;
@@ -55,7 +61,7 @@ namespace BTCPayServer.Services.Notifications
.Where(a => a.ApplicationUserId == userId && !a.Seen) .Where(a => a.ApplicationUserId == userId && !a.Seen)
.OrderByDescending(a => a.Created) .OrderByDescending(a => a.Created)
.Take(5) .Take(5)
.Select(a => a.ToViewModel()) .Select(a => ToViewModel(a))
.ToList(); .ToList();
} }
catch (System.IO.InvalidDataException) catch (System.IO.InvalidDataException)
@@ -77,6 +83,33 @@ namespace BTCPayServer.Services.Notifications
return resp; 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 public class NotificationSummaryViewModel

View File

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