mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
GreenField: Notifications API (#2055)
* GreenField: Notifications API This refactors notifications so that we dont have a bunch of duplicated direct access to db contexts in controllers and then introduces new endpoints to fetch/toggle seen/remove notifications of the current user. * add tests + docs * fix test * pr changes * fix permission json
This commit is contained in:
parent
1c58fabc30
commit
0652e30c30
47
BTCPayServer.Client/BTCPayServerClient.Notifications.cs
Normal file
47
BTCPayServer.Client/BTCPayServerClient.Notifications.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client
|
||||||
|
{
|
||||||
|
public partial class BTCPayServerClient
|
||||||
|
{
|
||||||
|
public virtual async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response =
|
||||||
|
await _httpClient.SendAsync(
|
||||||
|
CreateHttpRequest($"api/v1/users/me/notifications",
|
||||||
|
seen != null ? new Dictionary<string, object>() {{nameof(seen), seen}} : null), token);
|
||||||
|
return await HandleResponse<IEnumerable<NotificationData>>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<NotificationData> GetNotification(string notificationId,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(
|
||||||
|
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}"), token);
|
||||||
|
return await HandleResponse<NotificationData>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<NotificationData> UpdateNotification(string notificationId, bool? seen,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(
|
||||||
|
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}",
|
||||||
|
method: HttpMethod.Put, bodyPayload: new UpdateNotification() {Seen = seen}), token);
|
||||||
|
return await HandleResponse<NotificationData>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task RemoveNotification(string notificationId, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(
|
||||||
|
CreateHttpRequest($"api/v1/users/me/notifications/{notificationId}",
|
||||||
|
method: HttpMethod.Delete), token);
|
||||||
|
await HandleResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
BTCPayServer.Client/Models/NotificationData.cs
Normal file
16
BTCPayServer.Client/Models/NotificationData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Client.Models
|
||||||
|
{
|
||||||
|
public class NotificationData
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public bool Seen { get; set; }
|
||||||
|
public Uri Link { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||||
|
public DateTimeOffset CreatedTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
BTCPayServer.Client/Models/UpdateNotification.cs
Normal file
7
BTCPayServer.Client/Models/UpdateNotification.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace BTCPayServer.Client.Models
|
||||||
|
{
|
||||||
|
public class UpdateNotification
|
||||||
|
{
|
||||||
|
public bool? Seen { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,8 @@ namespace BTCPayServer.Client
|
|||||||
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
|
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
|
||||||
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
|
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
|
||||||
public const string CanViewProfile = "btcpay.user.canviewprofile";
|
public const string CanViewProfile = "btcpay.user.canviewprofile";
|
||||||
|
public const string CanManageNotificationsForUser = "btcpay.user.canmanagenotificationsforuser";
|
||||||
|
public const string CanViewNotificationsForUser = "btcpay.user.canviewnotificationsforuser";
|
||||||
public const string CanCreateUser = "btcpay.server.cancreateuser";
|
public const string CanCreateUser = "btcpay.server.cancreateuser";
|
||||||
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
|
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
|
||||||
public const string Unrestricted = "unrestricted";
|
public const string Unrestricted = "unrestricted";
|
||||||
@ -39,6 +41,8 @@ namespace BTCPayServer.Client
|
|||||||
yield return CanModifyProfile;
|
yield return CanModifyProfile;
|
||||||
yield return CanViewProfile;
|
yield return CanViewProfile;
|
||||||
yield return CanCreateUser;
|
yield return CanCreateUser;
|
||||||
|
yield return CanManageNotificationsForUser;
|
||||||
|
yield return CanViewNotificationsForUser;
|
||||||
yield return Unrestricted;
|
yield return Unrestricted;
|
||||||
yield return CanUseInternalLightningNode;
|
yield return CanUseInternalLightningNode;
|
||||||
yield return CanCreateLightningInvoiceInternalNode;
|
yield return CanCreateLightningInvoiceInternalNode;
|
||||||
@ -168,6 +172,7 @@ namespace BTCPayServer.Client
|
|||||||
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
|
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
|
||||||
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
|
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
|
||||||
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
|
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
|
||||||
|
case Policies.CanViewNotificationsForUser when this.Policy == Policies.CanManageNotificationsForUser:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -12,7 +12,8 @@ using BTCPayServer.JsonConverters;
|
|||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Notifications;
|
||||||
|
using BTCPayServer.Services.Notifications.Blobs;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -21,7 +22,6 @@ using NBitcoin.OpenAsset;
|
|||||||
using NBitpayClient;
|
using NBitpayClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NUglify.Helpers;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||||
@ -1154,6 +1154,46 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.NotEqual(0, info.BlockHeight);
|
Assert.NotEqual(0, info.BlockHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
public async Task NotificationAPITests()
|
||||||
|
{
|
||||||
|
using var tester = ServerTester.Create();
|
||||||
|
await tester.StartAsync();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
await user.GrantAccessAsync(true);
|
||||||
|
var client = await user.CreateClient(Policies.CanManageNotificationsForUser);
|
||||||
|
var viewOnlyClient = await user.CreateClient(Policies.CanViewNotificationsForUser);
|
||||||
|
await tester.PayTester.GetService<NotificationSender>()
|
||||||
|
.SendNotification(new UserScope(user.UserId), new NewVersionNotification());
|
||||||
|
|
||||||
|
Assert.Single(await viewOnlyClient.GetNotifications());
|
||||||
|
Assert.Single(await viewOnlyClient.GetNotifications(false));
|
||||||
|
Assert.Empty(await viewOnlyClient.GetNotifications(true));
|
||||||
|
|
||||||
|
Assert.Single(await client.GetNotifications());
|
||||||
|
Assert.Single(await client.GetNotifications(false));
|
||||||
|
Assert.Empty(await client.GetNotifications(true));
|
||||||
|
var notification = (await client.GetNotifications()).First();
|
||||||
|
notification = await client.GetNotification(notification.Id);
|
||||||
|
Assert.False(notification.Seen);
|
||||||
|
await AssertHttpError(403, async () =>
|
||||||
|
{
|
||||||
|
await viewOnlyClient.UpdateNotification(notification.Id, true);
|
||||||
|
});
|
||||||
|
await AssertHttpError(403, async () =>
|
||||||
|
{
|
||||||
|
await viewOnlyClient.RemoveNotification(notification.Id);
|
||||||
|
});
|
||||||
|
Assert.True((await client.UpdateNotification(notification.Id, true)).Seen);
|
||||||
|
Assert.Single(await viewOnlyClient.GetNotifications(true));
|
||||||
|
Assert.Empty(await viewOnlyClient.GetNotifications(false));
|
||||||
|
await client.RemoveNotification(notification.Id);
|
||||||
|
Assert.Empty(await viewOnlyClient.GetNotifications(true));
|
||||||
|
Assert.Empty(await viewOnlyClient.GetNotifications(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1197,5 +1237,6 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
|
||||||
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
|
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1322,7 +1322,7 @@ namespace BTCPayServer.Tests
|
|||||||
var resp = await ctrl.Generate(newVersion);
|
var resp = await ctrl.Generate(newVersion);
|
||||||
|
|
||||||
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
||||||
Assert.IsType<ViewResult>(ctrl.Index()).Model);
|
Assert.IsType<ViewResult>(await ctrl.Index()).Model);
|
||||||
|
|
||||||
Assert.True(vm.Skip == 0);
|
Assert.True(vm.Skip == 0);
|
||||||
Assert.True(vm.Count == 50);
|
Assert.True(vm.Count == 50);
|
||||||
@ -3330,7 +3330,7 @@ namespace BTCPayServer.Tests
|
|||||||
var newVersion = MockVersionFetcher.MOCK_NEW_VERSION;
|
var newVersion = MockVersionFetcher.MOCK_NEW_VERSION;
|
||||||
|
|
||||||
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
|
||||||
Assert.IsType<ViewResult>(ctrl.Index()).Model);
|
Assert.IsType<ViewResult>(await ctrl.Index()).Model);
|
||||||
|
|
||||||
Assert.True(vm.Skip == 0);
|
Assert.True(vm.Skip == 0);
|
||||||
Assert.True(vm.Count == 50);
|
Assert.True(vm.Count == 50);
|
||||||
|
105
BTCPayServer/Controllers/GreenField/NotificationsController.cs
Normal file
105
BTCPayServer/Controllers/GreenField/NotificationsController.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Services.Notifications;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NotificationData = BTCPayServer.Client.Models.NotificationData;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Controllers.GreenField
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[EnableCors(CorsPolicies.All)]
|
||||||
|
public class NotificationsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly NotificationManager _notificationManager;
|
||||||
|
|
||||||
|
public NotificationsController(UserManager<ApplicationUser> userManager,
|
||||||
|
NotificationManager notificationManager)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_notificationManager = notificationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanViewNotificationsForUser,
|
||||||
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpGet("~/api/v1/users/me/notifications")]
|
||||||
|
public async Task<IActionResult> GetNotifications(bool? seen = null)
|
||||||
|
{
|
||||||
|
var items = await _notificationManager.GetNotifications(new NotificationsQuery()
|
||||||
|
{
|
||||||
|
Seen = seen, UserId = _userManager.GetUserId(User)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(items.Items.Select(ToModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanViewNotificationsForUser,
|
||||||
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpGet("~/api/v1/users/me/notifications/{id}")]
|
||||||
|
public async Task<IActionResult> GetNotification(string id)
|
||||||
|
{
|
||||||
|
var items = await _notificationManager.GetNotifications(new NotificationsQuery()
|
||||||
|
{
|
||||||
|
Ids = new[] {id}, UserId = _userManager.GetUserId(User)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ToModel(items.Items.First()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanManageNotificationsForUser,
|
||||||
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpPut("~/api/v1/users/me/notifications/{id}")]
|
||||||
|
public async Task<IActionResult> UpdateNotification(string id, UpdateNotification request)
|
||||||
|
{
|
||||||
|
var items = await _notificationManager.ToggleSeen(
|
||||||
|
new NotificationsQuery() {Ids = new[] {id}, UserId = _userManager.GetUserId(User)}, request.Seen);
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ToModel(items.First()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanManageNotificationsForUser,
|
||||||
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpDelete("~/api/v1/users/me/notifications/{id}")]
|
||||||
|
public async Task<IActionResult> DeleteNotification(string id)
|
||||||
|
{
|
||||||
|
await _notificationManager.Remove(new NotificationsQuery()
|
||||||
|
{
|
||||||
|
Ids = new[] {id}, UserId = _userManager.GetUserId(User)
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationData ToModel(NotificationViewModel entity)
|
||||||
|
{
|
||||||
|
return new NotificationData()
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
CreatedTime = entity.Created,
|
||||||
|
Body = entity.Body,
|
||||||
|
Seen = entity.Seen,
|
||||||
|
Link = string.IsNullOrEmpty(entity.ActionLink) ? null : new Uri(entity.ActionLink)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,12 +18,12 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[EnableCors(CorsPolicies.All)]
|
[EnableCors(CorsPolicies.All)]
|
||||||
public class GreenFieldController : ControllerBase
|
public class GreenFieldStoresController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
public GreenFieldController(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
public GreenFieldStoresController(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.GreenField
|
namespace BTCPayServer.Controllers.GreenField
|
||||||
@ -28,7 +27,6 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
public class UsersController : ControllerBase
|
public class UsersController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly BTCPayServerOptions _btcPayServerOptions;
|
|
||||||
private readonly RoleManager<IdentityRole> _roleManager;
|
private readonly RoleManager<IdentityRole> _roleManager;
|
||||||
private readonly SettingsRepository _settingsRepository;
|
private readonly SettingsRepository _settingsRepository;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
@ -38,8 +36,9 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly CssThemeManager _themeManager;
|
private readonly CssThemeManager _themeManager;
|
||||||
|
|
||||||
public UsersController(UserManager<ApplicationUser> userManager, BTCPayServerOptions btcPayServerOptions,
|
public UsersController(UserManager<ApplicationUser> userManager,
|
||||||
RoleManager<IdentityRole> roleManager, SettingsRepository settingsRepository,
|
RoleManager<IdentityRole> roleManager,
|
||||||
|
SettingsRepository settingsRepository,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
IPasswordValidator<ApplicationUser> passwordValidator,
|
IPasswordValidator<ApplicationUser> passwordValidator,
|
||||||
RateLimitService throttleService,
|
RateLimitService throttleService,
|
||||||
@ -48,7 +47,6 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
CssThemeManager themeManager)
|
CssThemeManager themeManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_btcPayServerOptions = btcPayServerOptions;
|
|
||||||
_roleManager = roleManager;
|
_roleManager = roleManager;
|
||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
|
@ -474,6 +474,8 @@ namespace BTCPayServer.Controllers
|
|||||||
{BTCPayServer.Client.Policies.CanModifyServerSettings, ("Manage your server", "The app will have total control on the server settings of your server")},
|
{BTCPayServer.Client.Policies.CanModifyServerSettings, ("Manage your server", "The app will have total control on the server settings of your server")},
|
||||||
{BTCPayServer.Client.Policies.CanViewProfile, ("View your profile", "The app will be able to view your user profile.")},
|
{BTCPayServer.Client.Policies.CanViewProfile, ("View your profile", "The app will be able to view your user profile.")},
|
||||||
{BTCPayServer.Client.Policies.CanModifyProfile, ("Manage your profile", "The app will be able to view and modify your user profile.")},
|
{BTCPayServer.Client.Policies.CanModifyProfile, ("Manage your profile", "The app will be able to view and modify your user profile.")},
|
||||||
|
{BTCPayServer.Client.Policies.CanManageNotificationsForUser, ("Manage your notifications", "The app will be able to view and modify your user notifications.")},
|
||||||
|
{BTCPayServer.Client.Policies.CanViewNotificationsForUser, ("View your notifications", "The app will be able to view your user notifications.")},
|
||||||
{BTCPayServer.Client.Policies.CanCreateInvoice, ("Create an invoice", "The app will be able to create new invoices.")},
|
{BTCPayServer.Client.Policies.CanCreateInvoice, ("Create an invoice", "The app will be able to create new invoices.")},
|
||||||
{$"{BTCPayServer.Client.Policies.CanCreateInvoice}:", ("Create an invoice", "The app will be able to create new invoices on the selected stores.")},
|
{$"{BTCPayServer.Client.Policies.CanCreateInvoice}:", ("Create an invoice", "The app will be able to create new invoices on the selected stores.")},
|
||||||
{BTCPayServer.Client.Policies.CanViewInvoices, ("View invoices", "The app will be able to view invoices.")},
|
{BTCPayServer.Client.Policies.CanViewInvoices, ("View invoices", "The app will be able to view invoices.")},
|
||||||
|
@ -22,21 +22,18 @@ namespace BTCPayServer.Controllers
|
|||||||
public class NotificationsController : Controller
|
public class NotificationsController : Controller
|
||||||
{
|
{
|
||||||
private readonly BTCPayServerEnvironment _env;
|
private readonly BTCPayServerEnvironment _env;
|
||||||
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;
|
private readonly NotificationManager _notificationManager;
|
||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
|
|
||||||
public NotificationsController(BTCPayServerEnvironment env,
|
public NotificationsController(BTCPayServerEnvironment env,
|
||||||
ApplicationDbContext db,
|
|
||||||
NotificationSender notificationSender,
|
NotificationSender notificationSender,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
NotificationManager notificationManager,
|
NotificationManager notificationManager,
|
||||||
EventAggregator eventAggregator)
|
EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_env = env;
|
_env = env;
|
||||||
_db = db;
|
|
||||||
_notificationSender = notificationSender;
|
_notificationSender = notificationSender;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_notificationManager = notificationManager;
|
_notificationManager = notificationManager;
|
||||||
@ -57,6 +54,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
var websocketHelper = new WebSocketHelper(websocket);
|
var websocketHelper = new WebSocketHelper(websocket);
|
||||||
@ -90,34 +88,30 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GenerateJunk(int x = 100, bool admin=true)
|
public async Task<IActionResult> GenerateJunk(int x = 100, bool admin = true)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < x; i++)
|
for (int i = 0; i < x; i++)
|
||||||
{
|
{
|
||||||
await _notificationSender.SendNotification(admin? (NotificationScope) new AdminScope(): new UserScope(_userManager.GetUserId(User)), new JunkNotification());
|
await _notificationSender.SendNotification(
|
||||||
|
admin ? (NotificationScope)new AdminScope() : new UserScope(_userManager.GetUserId(User)),
|
||||||
|
new JunkNotification());
|
||||||
}
|
}
|
||||||
|
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult Index(int skip = 0, int count = 50, int timezoneOffset = 0)
|
public async Task<IActionResult> Index(int skip = 0, int count = 50, int timezoneOffset = 0)
|
||||||
{
|
{
|
||||||
if (!ValidUserClaim(out var userId))
|
if (!ValidUserClaim(out var userId))
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Index", "Home");
|
||||||
|
|
||||||
var model = new IndexViewModel()
|
var res = await _notificationManager.GetNotifications(new NotificationsQuery()
|
||||||
{
|
{
|
||||||
Skip = skip,
|
Skip = skip, Take = count, UserId = userId
|
||||||
Count = count,
|
});
|
||||||
Items = _db.Notifications
|
|
||||||
.Where(a => a.ApplicationUserId == userId)
|
var model = new IndexViewModel() {Skip = skip, Count = count, Items = res.Items, Total = res.Count};
|
||||||
.OrderByDescending(a => a.Created)
|
|
||||||
.Skip(skip).Take(count)
|
|
||||||
.Select(a => _notificationManager.ToViewModel(a))
|
|
||||||
.ToList(),
|
|
||||||
Total = _db.Notifications.Count(a => a.ApplicationUserId == userId)
|
|
||||||
};
|
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
@ -136,10 +130,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (ValidUserClaim(out var userId))
|
if (ValidUserClaim(out var userId))
|
||||||
{
|
{
|
||||||
var notif = _db.Notifications.Single(a => a.Id == id && a.ApplicationUserId == userId);
|
await _notificationManager.ToggleSeen(new NotificationsQuery() {Ids = new[] {id}, UserId = userId}, null);
|
||||||
notif.Seen = !notif.Seen;
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
_notificationManager.InvalidateNotificationCache(userId);
|
|
||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,21 +142,19 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (ValidUserClaim(out var userId))
|
if (ValidUserClaim(out var userId))
|
||||||
{
|
{
|
||||||
var notif = _db.Notifications.Single(a => a.Id == id && a.ApplicationUserId == userId);
|
var items = await
|
||||||
if (!notif.Seen)
|
_notificationManager.ToggleSeen(new NotificationsQuery()
|
||||||
{
|
{
|
||||||
notif.Seen = !notif.Seen;
|
Ids = new[] {id}, UserId = userId
|
||||||
await _db.SaveChangesAsync();
|
}, true);
|
||||||
_notificationManager.InvalidateNotificationCache(userId);
|
|
||||||
}
|
var link = items.FirstOrDefault()?.ActionLink ?? "";
|
||||||
|
if (string.IsNullOrEmpty(link))
|
||||||
var vm = _notificationManager.ToViewModel(notif);
|
|
||||||
if (string.IsNullOrEmpty(vm.ActionLink))
|
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect(vm.ActionLink);
|
return Redirect(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@ -188,31 +177,29 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
if (selectedItems != null)
|
if (selectedItems != null)
|
||||||
{
|
{
|
||||||
var items = _db.Notifications.Where(a => a.ApplicationUserId == userId && selectedItems.Contains(a.Id));
|
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
case "delete":
|
case "delete":
|
||||||
_db.Notifications.RemoveRange(items);
|
await _notificationManager.Remove(new NotificationsQuery()
|
||||||
|
{
|
||||||
|
UserId = userId, Ids = selectedItems
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "mark-seen":
|
case "mark-seen":
|
||||||
foreach (NotificationData notificationData in items)
|
await _notificationManager.ToggleSeen(new NotificationsQuery()
|
||||||
{
|
{
|
||||||
notificationData.Seen = true;
|
UserId = userId, Ids = selectedItems, Seen = false
|
||||||
}
|
}, true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "mark-unseen":
|
case "mark-unseen":
|
||||||
foreach (NotificationData notificationData in items)
|
await _notificationManager.ToggleSeen(new NotificationsQuery()
|
||||||
{
|
{
|
||||||
notificationData.Seen = false;
|
UserId = userId, Ids = selectedItems, Seen = true
|
||||||
}
|
}, false);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
_notificationManager.InvalidateNotificationCache(userId);
|
|
||||||
return RedirectToAction(nameof(Index));
|
return RedirectToAction(nameof(Index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,8 @@ namespace BTCPayServer.Security.GreenField
|
|||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Policies.CanManageNotificationsForUser:
|
||||||
|
case Policies.CanViewNotificationsForUser:
|
||||||
case Policies.CanModifyProfile:
|
case Policies.CanModifyProfile:
|
||||||
case Policies.CanViewProfile:
|
case Policies.CanViewProfile:
|
||||||
case Policies.Unrestricted:
|
case Policies.Unrestricted:
|
||||||
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
|||||||
using BTCPayServer.Abstractions.Contracts;
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Components.NotificationsDropdown;
|
using BTCPayServer.Components.NotificationsDropdown;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
@ -38,21 +37,27 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
{
|
{
|
||||||
var userId = _userManager.GetUserId(user);
|
var userId = _userManager.GetUserId(user);
|
||||||
var cacheKey = GetNotificationsCacheId(userId);
|
var cacheKey = GetNotificationsCacheId(userId);
|
||||||
if (_memoryCache.TryGetValue<NotificationSummaryViewModel>(cacheKey, out var obj))
|
|
||||||
return obj;
|
|
||||||
|
|
||||||
var resp = await FetchNotificationsFromDb(userId);
|
return await _memoryCache.GetOrCreateAsync(cacheKey, async entry =>
|
||||||
_memoryCache.Set(cacheKey, resp,
|
{
|
||||||
new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMilliseconds(_cacheExpiryMs)));
|
var resp = await GetNotifications(new NotificationsQuery()
|
||||||
|
{
|
||||||
return resp;
|
Seen = false, Skip = 0, Take = 5, UserId = userId
|
||||||
|
});
|
||||||
|
entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(_cacheExpiryMs));
|
||||||
|
var res = new NotificationSummaryViewModel() {Last5 = resp.Items, UnseenCount = resp.Count};
|
||||||
|
entry.Value = res;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateNotificationCache(string userId)
|
public void InvalidateNotificationCache(params string[] userIds)
|
||||||
{
|
{
|
||||||
_memoryCache.Remove(GetNotificationsCacheId(userId));
|
foreach (var userId in userIds)
|
||||||
|
{
|
||||||
_eventAggregator.Publish(new UserNotificationsUpdatedEvent() { UserId = userId });
|
_memoryCache.Remove(GetNotificationsCacheId(userId));
|
||||||
|
_eventAggregator.Publish(new UserNotificationsUpdatedEvent() {UserId = userId});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetNotificationsCacheId(string userId)
|
private static string GetNotificationsCacheId(string userId)
|
||||||
@ -60,52 +65,87 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
return $"notifications-{userId}";
|
return $"notifications-{userId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<NotificationSummaryViewModel> FetchNotificationsFromDb(string userId)
|
public async Task<(List<NotificationViewModel> Items, int Count)> GetNotifications(NotificationsQuery query)
|
||||||
{
|
{
|
||||||
var resp = new NotificationSummaryViewModel();
|
await using var dbContext = _factory.CreateContext();
|
||||||
using (var _db = _factory.CreateContext())
|
|
||||||
{
|
|
||||||
resp.UnseenCount = _db.Notifications
|
|
||||||
.Where(a => a.ApplicationUserId == userId && !a.Seen)
|
|
||||||
.Count();
|
|
||||||
|
|
||||||
if (resp.UnseenCount > 0)
|
var queryables = GetNotificationsQueryable(dbContext, query);
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
resp.Last5 = (await _db.Notifications
|
|
||||||
.Where(a => a.ApplicationUserId == userId && !a.Seen)
|
|
||||||
.OrderByDescending(a => a.Created)
|
|
||||||
.Take(5)
|
|
||||||
.ToListAsync())
|
|
||||||
.Select(a => ToViewModel(a))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
catch (System.IO.InvalidDataException)
|
|
||||||
{
|
|
||||||
// invalid notifications that are not pkuzipable, burn them all
|
|
||||||
var notif = _db.Notifications.Where(a => a.ApplicationUserId == userId);
|
|
||||||
_db.Notifications.RemoveRange(notif);
|
|
||||||
_db.SaveChanges();
|
|
||||||
|
|
||||||
resp.UnseenCount = 0;
|
return (Items: (await queryables.withPaging.ToListAsync()).Select(ToViewModel).ToList(),
|
||||||
resp.Last5 = new List<NotificationViewModel>();
|
Count: await queryables.withoutPaging.CountAsync());
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resp.Last5 = new List<NotificationViewModel>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public NotificationViewModel ToViewModel(NotificationData data)
|
private ( IQueryable<NotificationData> withoutPaging, IQueryable<NotificationData> withPaging)
|
||||||
|
GetNotificationsQueryable(ApplicationDbContext dbContext, NotificationsQuery query)
|
||||||
|
{
|
||||||
|
var queryable = dbContext.Notifications.AsQueryable();
|
||||||
|
if (query.Ids?.Any() is true)
|
||||||
|
{
|
||||||
|
queryable = queryable.Where(data => query.Ids.Contains(data.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(query.UserId))
|
||||||
|
{
|
||||||
|
queryable = queryable.Where(data => data.ApplicationUserId == query.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.Seen.HasValue)
|
||||||
|
{
|
||||||
|
queryable = queryable.Where(data => data.Seen == query.Seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryable = queryable.OrderByDescending(a => a.Created);
|
||||||
|
|
||||||
|
var queryable2 = queryable;
|
||||||
|
if (query.Skip.HasValue)
|
||||||
|
{
|
||||||
|
queryable2 = queryable.Skip(query.Skip.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.Take.HasValue)
|
||||||
|
{
|
||||||
|
queryable2 = queryable.Take(query.Take.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (queryable, queryable2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<NotificationViewModel>> ToggleSeen(NotificationsQuery notificationsQuery, bool? setSeen)
|
||||||
|
{
|
||||||
|
await using var dbContext = _factory.CreateContext();
|
||||||
|
|
||||||
|
var queryables = GetNotificationsQueryable(dbContext, notificationsQuery);
|
||||||
|
var items = await queryables.withPaging.ToListAsync();
|
||||||
|
var userIds = items.Select(data => data.ApplicationUserId).Distinct();
|
||||||
|
foreach (var notificationData in items)
|
||||||
|
{
|
||||||
|
notificationData.Seen = setSeen.GetValueOrDefault(!notificationData.Seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
InvalidateNotificationCache(userIds.ToArray());
|
||||||
|
return items.Select(ToViewModel).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Remove(NotificationsQuery notificationsQuery)
|
||||||
|
{
|
||||||
|
await using var dbContext = _factory.CreateContext();
|
||||||
|
|
||||||
|
var queryables = GetNotificationsQueryable(dbContext, notificationsQuery);
|
||||||
|
dbContext.RemoveRange(queryables.withPaging);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(notificationsQuery.UserId))
|
||||||
|
InvalidateNotificationCache(notificationsQuery.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private NotificationViewModel ToViewModel(NotificationData data)
|
||||||
{
|
{
|
||||||
var handler = GetHandler(data.NotificationType);
|
var handler = GetHandler(data.NotificationType);
|
||||||
var notification = JsonConvert.DeserializeObject(ZipUtils.Unzip(data.Blob), handler.NotificationBlobType);
|
var notification = JsonConvert.DeserializeObject(ZipUtils.Unzip(data.Blob), handler.NotificationBlobType);
|
||||||
var obj = new NotificationViewModel { Id = data.Id, Created = data.Created, Seen = data.Seen };
|
var obj = new NotificationViewModel {Id = data.Id, Created = data.Created, Seen = data.Seen};
|
||||||
handler.FillViewModel(notification, obj);
|
handler.FillViewModel(notification, obj);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -117,4 +157,13 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
throw new InvalidOperationException($"No INotificationHandler found for {notificationId}");
|
throw new InvalidOperationException($"No INotificationHandler found for {notificationId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NotificationsQuery
|
||||||
|
{
|
||||||
|
public string[] Ids { get; set; }
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public int? Skip { get; set; }
|
||||||
|
public int? Take { get; set; }
|
||||||
|
public bool? Seen { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
if (notification == null)
|
if (notification == null)
|
||||||
throw new ArgumentNullException(nameof(notification));
|
throw new ArgumentNullException(nameof(notification));
|
||||||
var users = await GetUsers(scope, notification.Identifier);
|
var users = await GetUsers(scope, notification.Identifier);
|
||||||
using (var db = _contextFactory.CreateContext())
|
await using (var db = _contextFactory.CreateContext())
|
||||||
{
|
{
|
||||||
foreach (var uid in users)
|
foreach (var uid in users)
|
||||||
{
|
{
|
||||||
@ -48,7 +48,7 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
Blob = ZipUtils.Zip(obj),
|
Blob = ZipUtils.Zip(obj),
|
||||||
Seen = false
|
Seen = false
|
||||||
};
|
};
|
||||||
db.Notifications.Add(data);
|
await db.Notifications.AddAsync(data);
|
||||||
}
|
}
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
"securitySchemes": {
|
"securitySchemes": {
|
||||||
"API Key": {
|
"API Key": {
|
||||||
"type": "apiKey",
|
"type": "apiKey",
|
||||||
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\n",
|
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\n",
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"scheme": "token"
|
"scheme": "token"
|
||||||
|
@ -0,0 +1,238 @@
|
|||||||
|
{
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/users/me/notifications": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Notifications (Current User)"
|
||||||
|
],
|
||||||
|
"summary": "Get notifications",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "seen",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"description": "filter by seen notifications",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "View current user's notifications",
|
||||||
|
"operationId": "Notifications_GetNotifications",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "list of notifications",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotificationData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API Key": [
|
||||||
|
"btcpay.user.canmanagenotificationsforuser",
|
||||||
|
"btcpay.user.canviewnotificationsforuser"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/users/me/notifications/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Notifications (Current User)"
|
||||||
|
],
|
||||||
|
"summary": "Get notification",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The notification to fetch",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "View information about the specified notification",
|
||||||
|
"operationId": "Notifications_GetInvoice",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "specified notification",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotificationData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "If you are authenticated but forbidden to view the specified notification"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The key is not found for this notification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API Key": [
|
||||||
|
"btcpay.user.canmanagenotificationsforuser",
|
||||||
|
"btcpay.user.canviewnotificationsforuser"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Notifications (Current User)"
|
||||||
|
],
|
||||||
|
"summary": "Update notification",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The notification to update",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Updates the notification",
|
||||||
|
"operationId": "Notifications_UpdateNotification",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "updated notification",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotificationData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "If you are authenticated but forbidden to update the specified notification"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The key is not found for this notification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API Key": [
|
||||||
|
"btcpay.user.canmanagenotificationsforuser"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdateNotification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Notifications (Current User)"
|
||||||
|
],
|
||||||
|
"summary": "Remove Notification",
|
||||||
|
"description": "Removes the specified notification.",
|
||||||
|
"operationId": "Notifications_DeleteNotification",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The notification to remove",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The notification has been deleted"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "If you are authenticated but forbidden to remove the specified notification"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The key is not found for this notification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API Key": [
|
||||||
|
"btcpay.user.canmanagenotificationsforuser"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"UpdateNotification": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"seen": {
|
||||||
|
"type": "boolean",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Sets the notification as seen/unseen. If left null, sets it to the opposite value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NotificationData": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The identifier of the notification"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "html",
|
||||||
|
"description": "The html body of the notifications"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "The link of the notification"
|
||||||
|
},
|
||||||
|
"createdTime": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "The creation time of the notification"
|
||||||
|
},
|
||||||
|
"seen": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If the notification has been seen by the user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Notifications (Current User)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user