Greenfield: Manage notifications (#6058)

Prerequisite for btcpayserver/app#12.
This commit is contained in:
d11n 2024-06-25 20:01:11 +02:00 committed by GitHub
parent e0c5ac5271
commit feffbbd980
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 306 additions and 3 deletions

View File

@ -33,6 +33,16 @@ public partial class BTCPayServerClient
return await SendHttpRequest<NotificationData>($"api/v1/users/me/notifications/{notificationId}", new UpdateNotification { Seen = seen }, HttpMethod.Put, token);
}
public virtual async Task<NotificationSettingsData> GetNotificationSettings(CancellationToken token = default)
{
return await SendHttpRequest<NotificationSettingsData>("api/v1/users/me/notification-settings", null, HttpMethod.Get, token);
}
public virtual async Task<NotificationSettingsData> UpdateNotificationSettings(UpdateNotificationSettingsRequest request, CancellationToken token = default)
{
return await SendHttpRequest<NotificationSettingsData>("api/v1/users/me/notification-settings", request, HttpMethod.Put, token);
}
public virtual async Task RemoveNotification(string notificationId, CancellationToken token = default)
{
await SendHttpRequest($"api/v1/users/me/notifications/{notificationId}", null, HttpMethod.Delete, token);

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace BTCPayServer.Client.Models;
public class NotificationSettingsData
{
public List<NotificationSettingsItemData> Notifications { get; set; }
}
public class NotificationSettingsItemData
{
public string Identifier { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
}

View File

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace BTCPayServer.Client.Models;
public class UpdateNotificationSettingsRequest
{
public List<string> Disabled { get; set; }
}

View File

@ -2851,6 +2851,30 @@ namespace BTCPayServer.Tests
await client.RemoveNotification(notification.Id);
Assert.Empty(await viewOnlyClient.GetNotifications(true));
Assert.Empty(await viewOnlyClient.GetNotifications(false));
// Settings
var settings = await client.GetNotificationSettings();
Assert.True(settings.Notifications.Find(n => n.Identifier == "newversion").Enabled);
Assert.True(settings.Notifications.Find(n => n.Identifier == "pluginupdate").Enabled);
Assert.True(settings.Notifications.Find(n => n.Identifier == "inviteaccepted").Enabled);
var request = new UpdateNotificationSettingsRequest { Disabled = ["newversion", "pluginupdate"] };
settings = await client.UpdateNotificationSettings(request);
Assert.False(settings.Notifications.Find(n => n.Identifier == "newversion").Enabled);
Assert.False(settings.Notifications.Find(n => n.Identifier == "pluginupdate").Enabled);
Assert.True(settings.Notifications.Find(n => n.Identifier == "inviteaccepted").Enabled);
request = new UpdateNotificationSettingsRequest { Disabled = ["all"] };
settings = await client.UpdateNotificationSettings(request);
Assert.False(settings.Notifications.Find(n => n.Identifier == "newversion").Enabled);
Assert.False(settings.Notifications.Find(n => n.Identifier == "pluginupdate").Enabled);
Assert.False(settings.Notifications.Find(n => n.Identifier == "inviteaccepted").Enabled);
request = new UpdateNotificationSettingsRequest { Disabled = [] };
settings = await client.UpdateNotificationSettings(request);
Assert.True(settings.Notifications.Find(n => n.Identifier == "newversion").Enabled);
Assert.True(settings.Notifications.Find(n => n.Identifier == "pluginupdate").Enabled);
Assert.True(settings.Notifications.Find(n => n.Identifier == "inviteaccepted").Enabled);
}
[Fact(Timeout = TestTimeout)]

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
@ -23,12 +24,16 @@ namespace BTCPayServer.Controllers.Greenfield
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly NotificationManager _notificationManager;
private readonly IEnumerable<INotificationHandler> _notificationHandlers;
public GreenfieldNotificationsController(UserManager<ApplicationUser> userManager,
NotificationManager notificationManager)
public GreenfieldNotificationsController(
UserManager<ApplicationUser> userManager,
NotificationManager notificationManager,
IEnumerable<INotificationHandler> notificationHandlers)
{
_userManager = userManager;
_notificationManager = notificationManager;
_notificationHandlers = notificationHandlers;
}
[Authorize(Policy = Policies.CanViewNotificationsForUser,
@ -95,6 +100,37 @@ namespace BTCPayServer.Controllers.Greenfield
return Ok();
}
[Authorize(Policy = Policies.CanManageNotificationsForUser, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/users/me/notification-settings")]
public async Task<IActionResult> GetNotificationSettings()
{
var user = await _userManager.GetUserAsync(User);
var model = GetNotificationSettingsData(user);
return Ok(model);
}
[Authorize(Policy = Policies.CanManageNotificationsForUser, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/users/me/notification-settings")]
public async Task<IActionResult> UpdateNotificationSettings(UpdateNotificationSettingsRequest request)
{
var user = await _userManager.GetUserAsync(User);
if (request.Disabled.Contains("all"))
{
user.DisabledNotifications = "all";
}
else
{
var disabled = _notificationHandlers
.SelectMany(handler => handler.Meta.Select(tuple => tuple.identifier))
.Where(id => request.Disabled.Contains(id)).ToList();
user.DisabledNotifications = disabled.Any() ? string.Join(';', disabled) + ";" : string.Empty;
}
await _userManager.UpdateAsync(user);
var model = GetNotificationSettingsData(user);
return Ok(model);
}
private NotificationData ToModel(NotificationViewModel entity)
{
@ -113,5 +149,19 @@ namespace BTCPayServer.Controllers.Greenfield
{
return this.CreateAPIError(404, "notification-not-found", "The notification was not found");
}
private NotificationSettingsData GetNotificationSettingsData(ApplicationUser user)
{
var disabledAll = user.DisabledNotifications == "all";
var disabledNotifications = user.DisabledNotifications?.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList() ?? [];
var notifications = _notificationHandlers.SelectMany(handler => handler.Meta.Select(tuple =>
new NotificationSettingsItemData
{
Identifier = tuple.identifier,
Name = tuple.name,
Enabled = !disabledAll && !disabledNotifications.Contains(tuple.identifier, StringComparer.InvariantCultureIgnoreCase)
})).ToList();
return new NotificationSettingsData { Notifications = notifications };
}
}
}

View File

@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json.Linq;
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
@ -644,6 +643,18 @@ namespace BTCPayServer.Controllers.Greenfield
HandleActionResult(await GetController<GreenfieldApiKeysController>().RevokeAPIKey(apikey));
}
public override async Task<NotificationSettingsData> GetNotificationSettings(CancellationToken token = default)
{
return GetFromActionResult<NotificationSettingsData>(
await GetController<GreenfieldNotificationsController>().GetNotificationSettings());
}
public override async Task<NotificationSettingsData> UpdateNotificationSettings(UpdateNotificationSettingsRequest request, CancellationToken token = default)
{
return GetFromActionResult<NotificationSettingsData>(
await GetController<GreenfieldNotificationsController>().UpdateNotificationSettings(request));
}
public override async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null,
int? skip = null, int? take = null, CancellationToken token = default)
{

View File

@ -203,6 +203,80 @@
}
]
}
},
"/api/v1/users/me/notification-settings": {
"get": {
"tags": [
"Notifications (Current User)"
],
"summary": "Get notification settings",
"description": "View information about your notification settings",
"operationId": "Notifications_GetNotificationSettings",
"responses": {
"200": {
"description": "The current user's notification settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationSettingsData"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden to view the notification settings"
}
},
"security": [
{
"API_Key": [
"btcpay.user.canmanagenotificationsforuser"
],
"Basic": []
}
]
},
"put": {
"tags": [
"Notifications (Current User)"
],
"summary": "Update notification settings",
"description": "Updates the current user's notification settings",
"operationId": "Notifications_UpdateNotification",
"responses": {
"200": {
"description": "The current user's notification settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationSettingsData"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden to update the notification settings"
}
},
"security": [
{
"API_Key": [
"btcpay.user.canmanagenotificationsforuser"
],
"Basic": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateNotificationSettingsRequest"
}
}
}
}
}
}
},
"components": {
@ -254,6 +328,117 @@
"description": "If the notification has been seen by the user"
}
}
},
"UpdateNotificationSettingsRequest": {
"type": "object",
"additionalProperties": false,
"properties": {
"disabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of the notification type identifiers, which should be disabled. Can also be a single item 'all'.",
"example": ["newversion", "pluginupdate"],
"nullable": false
}
}
},
"NotificationSettingsData": {
"type": "object",
"additionalProperties": false,
"properties": {
"notifications": {
"type": "array",
"description": "The notification types",
"items": {
"$ref": "#/components/schemas/NotificationSettingsItemData"
}
}
},
"example": [
{
"identifier": "newversion",
"name": "New version",
"enabled": false
},
{
"identifier": "newuserrequiresapproval",
"name": "New user requires approval",
"enabled": true
},
{
"identifier": "inviteaccepted",
"name": "User accepted invitation",
"enabled": true
},
{
"identifier": "pluginupdate",
"name": "Plugin update",
"enabled": false
},
{
"identifier": "invoicestate",
"name": "All invoice updates",
"enabled": true
},
{
"identifier": "invoicestate_invoice_paidAfterExpiration",
"name": "Invoice was paid after expiration",
"enabled": true
},
{
"identifier": "invoicestate_invoice_expiredPaidPartial",
"name": "Invoice expired with partial payments",
"enabled": true
},
{
"identifier": "invoicestate_invoice_failedToConfirm",
"name": "Invoice has payments that failed to confirm on time",
"enabled": true
},
{
"identifier": "invoicestate_invoice_confirmed",
"name": "Invoice is settled",
"enabled": true
},
{
"identifier": "payout",
"name": "Payouts",
"enabled": true
},
{
"identifier": "external-payout-transaction",
"name": "External payout approval",
"enabled": true
}
]
},
"NotificationSettingsItemData": {
"type": "object",
"additionalProperties": false,
"properties": {
"identifier": {
"type": "string",
"description": "The identifier of the notification type",
"nullable": false
},
"name": {
"type": "string",
"description": "The description of the notification type",
"nullable": false
},
"enabled": {
"type": "boolean",
"description": "If the notification type is enabled",
"nullable": false
}
},
"example": {
"identifier": "newversion",
"name": "New version",
"enabled": false
}
}
}
},