Merge pull request #1666 from NicolasDorier/refactor/notificatoin

Small refactoring of Notifications
This commit is contained in:
Nicolas Dorier 2020-06-16 15:46:27 +09:00 committed by GitHub
commit 677cc3bee9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 62 deletions

View file

@ -9,7 +9,9 @@ using BTCPayServer.Filters;
using BTCPayServer.HostedServices;
using BTCPayServer.Models.NotificationViewModels;
using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
using Google;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@ -21,12 +23,14 @@ namespace BTCPayServer.Controllers
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public class NotificationsController : Controller
{
private readonly BTCPayServerEnvironment _env;
private readonly ApplicationDbContext _db;
private readonly NotificationSender _notificationSender;
private readonly UserManager<ApplicationUser> _userManager;
public NotificationsController(ApplicationDbContext db, NotificationSender notificationSender, UserManager<ApplicationUser> userManager)
public NotificationsController(BTCPayServerEnvironment env, ApplicationDbContext db, NotificationSender notificationSender, UserManager<ApplicationUser> userManager)
{
_env = env;
_db = db;
_notificationSender = notificationSender;
_userManager = userManager;
@ -46,7 +50,7 @@ namespace BTCPayServer.Controllers
.OrderByDescending(a => a.Created)
.Skip(skip).Take(count)
.Where(a => a.ApplicationUserId == userId)
.Select(a => a.ViewModel())
.Select(a => a.ToViewModel())
.ToList(),
Total = _db.Notifications.Where(a => a.ApplicationUserId == userId).Count()
};
@ -57,9 +61,9 @@ namespace BTCPayServer.Controllers
[HttpGet]
public async Task<IActionResult> Generate(string version)
{
await _notificationSender.NoticeNewVersionAsync(version);
// waiting for event handler to catch up
await Task.Delay(500);
if (_env.NetworkType != NBitcoin.NetworkType.Regtest)
return NotFound();
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(version));
return RedirectToAction(nameof(Index));
}

View file

@ -1,10 +0,0 @@
using BTCPayServer.Services.Notifications.Blobs;
namespace BTCPayServer.Events
{
internal class NotificationEvent
{
internal string[] ApplicationUserIds { get; set; }
internal BaseNotification Notification { get; set; }
}
}

View file

@ -30,22 +30,26 @@ namespace BTCPayServer.Models.NotificationViewModels
public static class NotificationViewModelExt
{
public static NotificationViewModel ViewModel(this NotificationData data)
static Dictionary<string, Type> _NotificationTypes;
static NotificationViewModelExt()
{
var baseType = typeof(BaseNotification);
_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);
}
var fullTypeName = baseType.FullName.Replace(nameof(BaseNotification), data.NotificationType, StringComparison.OrdinalIgnoreCase);
var parsedType = baseType.Assembly.GetType(fullTypeName);
var casted = (BaseNotification)JsonConvert.DeserializeObject(ZipUtils.Unzip(data.Blob), parsedType);
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(ref obj);
casted.FillViewModel(obj);
return obj;
}

View file

@ -8,26 +8,8 @@ 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
internal abstract class BaseNotification
public abstract class BaseNotification
{
internal virtual string NotificationType { get { return GetType().Name; } }
public NotificationData ToData(string applicationUserId)
{
var obj = JsonConvert.SerializeObject(this);
var data = new NotificationData
{
Id = Guid.NewGuid().ToString(),
Created = DateTimeOffset.UtcNow,
ApplicationUserId = applicationUserId,
NotificationType = NotificationType,
Blob = ZipUtils.Zip(obj),
Seen = false
};
return data;
}
public abstract void FillViewModel(ref NotificationViewModel data);
public abstract void FillViewModel(NotificationViewModel data);
}
}

View file

@ -4,13 +4,20 @@ using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications.Blobs
{
[Notification("newversion")]
internal class NewVersionNotification : BaseNotification
{
internal override string NotificationType => "NewVersionNotification";
public NewVersionNotification()
{
}
public NewVersionNotification(string version)
{
Version = version;
}
public string Version { get; set; }
public override void FillViewModel(ref NotificationViewModel vm)
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

@ -0,0 +1,17 @@
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

@ -55,7 +55,7 @@ namespace BTCPayServer.Services.Notifications
.Where(a => a.ApplicationUserId == userId && !a.Seen)
.OrderByDescending(a => a.Created)
.Take(5)
.Select(a => a.ViewModel())
.Select(a => a.ToViewModel())
.ToList();
}
catch (System.IO.InvalidDataException)

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Services.Notifications
{
public class AdminScope : NotificationScope
{
public AdminScope()
{
}
}
public class StoreScope : NotificationScope
{
public StoreScope(string storeId)
{
if (storeId == null)
throw new ArgumentNullException(nameof(storeId));
StoreId = storeId;
}
public string StoreId { get; }
}
public interface NotificationScope
{
}
}

View file

@ -1,9 +1,13 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Services.Notifications.Blobs;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Notifications
{
@ -20,32 +24,58 @@ namespace BTCPayServer.Services.Notifications
_eventAggregator = eventAggregator;
}
internal async Task NoticeNewVersionAsync(string version)
public async Task SendNotification(NotificationScope scope, BaseNotification notification)
{
var admins = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
var adminUids = admins.Select(a => a.Id).ToArray();
var notif = new NewVersionNotification
{
Version = version
};
if (scope == null)
throw new ArgumentNullException(nameof(scope));
if (notification == null)
throw new ArgumentNullException(nameof(notification));
var users = await GetUsers(scope);
using (var db = _contextFactory.CreateContext())
{
foreach (var uid in adminUids)
foreach (var uid in users)
{
var data = notif.ToData(uid);
var obj = JsonConvert.SerializeObject(notification);
var data = new NotificationData
{
Id = Guid.NewGuid().ToString(),
Created = DateTimeOffset.UtcNow,
ApplicationUserId = uid,
NotificationType = GetNotificationTypeString(notification.GetType()),
Blob = ZipUtils.Zip(obj),
Seen = false
};
db.Notifications.Add(data);
}
await db.SaveChangesAsync();
}
}
// propagate notification
_eventAggregator.Publish(new NotificationEvent
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)
{
ApplicationUserIds = adminUids,
Notification = notif
});
var admins = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
return admins.Select(a => a.Id).ToArray();
}
if (scope is StoreScope s)
{
using var ctx = _contextFactory.CreateContext();
return ctx.UserStore
.Where(u => u.StoreDataId == s.StoreId)
.Select(u => u.ApplicationUserId)
.ToArray();
}
throw new NotSupportedException("Notification scope not supported");
}
}
}