mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Add helpers methods (#6941)
This commit is contained in:
@@ -95,7 +95,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("invoices/{invoiceId}")]
|
[HttpGet("invoices/{invoiceId}")]
|
||||||
[HttpGet("/stores/{storeId}/invoices/${invoiceId}")]
|
[HttpGet("/stores/{storeId}/invoices/{invoiceId}")]
|
||||||
[Authorize(Policy = Policies.CanViewInvoices, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanViewInvoices, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public async Task<IActionResult> Invoice(string invoiceId)
|
public async Task<IActionResult> Invoice(string invoiceId)
|
||||||
{
|
{
|
||||||
@@ -837,13 +837,7 @@ namespace BTCPayServer.Controllers
|
|||||||
lang ??= storeBlob.DefaultLang;
|
lang ??= storeBlob.DefaultLang;
|
||||||
|
|
||||||
var receiptEnabled = InvoiceDataBase.ReceiptOptions.Merge(storeBlob.ReceiptOptions, invoice.ReceiptOptions).Enabled is true;
|
var receiptEnabled = InvoiceDataBase.ReceiptOptions.Merge(storeBlob.ReceiptOptions, invoice.ReceiptOptions).Enabled is true;
|
||||||
var receiptUrl = receiptEnabled ? _linkGenerator.GetUriByAction(
|
var receiptUrl = receiptEnabled ? _linkGenerator.ReceiptLink(invoiceId, Request.GetRequestBaseUrl()) : null;
|
||||||
nameof(InvoiceReceipt),
|
|
||||||
"UIInvoice",
|
|
||||||
new { invoiceId },
|
|
||||||
Request.Scheme,
|
|
||||||
Request.Host,
|
|
||||||
Request.PathBase) : null;
|
|
||||||
|
|
||||||
var orderId = invoice.Metadata.OrderId;
|
var orderId = invoice.Metadata.OrderId;
|
||||||
var supportUrl = !string.IsNullOrEmpty(storeBlob.StoreSupportUrl)
|
var supportUrl = !string.IsNullOrEmpty(storeBlob.StoreSupportUrl)
|
||||||
|
|||||||
@@ -14,16 +14,19 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
void Unsubscribe();
|
void Unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EventAggregator : IDisposable
|
public class EventAggregator : IDisposable
|
||||||
{
|
{
|
||||||
public EventAggregator(Logs logs)
|
public EventAggregator(Logs logs)
|
||||||
{
|
{
|
||||||
Logs = logs;
|
Logs = logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Subscription : IEventAggregatorSubscription
|
class Subscription : IEventAggregatorSubscription
|
||||||
{
|
{
|
||||||
private readonly EventAggregator aggregator;
|
private readonly EventAggregator aggregator;
|
||||||
readonly Type t;
|
readonly Type t;
|
||||||
|
|
||||||
public Subscription(EventAggregator aggregator, Type t)
|
public Subscription(EventAggregator aggregator, Type t)
|
||||||
{
|
{
|
||||||
this.aggregator = aggregator;
|
this.aggregator = aggregator;
|
||||||
@@ -31,21 +34,24 @@ namespace BTCPayServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Action<Object> Act { get; set; }
|
public Action<Object> Act { get; set; }
|
||||||
|
public bool Any { get; set; }
|
||||||
|
|
||||||
bool _Disposed;
|
bool _Disposed;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_Disposed)
|
if (_Disposed)
|
||||||
return;
|
return;
|
||||||
_Disposed = true;
|
_Disposed = true;
|
||||||
lock (this.aggregator._Subscriptions)
|
var dict = Any ? aggregator._SubscriptionsAny : aggregator._Subscriptions;
|
||||||
|
lock (dict)
|
||||||
{
|
{
|
||||||
if (this.aggregator._Subscriptions.TryGetValue(t, out Dictionary<Subscription, Action<object>> actions))
|
if (dict.TryGetValue(t, out Dictionary<Subscription, Action<object>> actions))
|
||||||
{
|
{
|
||||||
if (actions.Remove(this))
|
if (actions.Remove(this))
|
||||||
{
|
{
|
||||||
if (actions.Count == 0)
|
if (actions.Count == 0)
|
||||||
this.aggregator._Subscriptions.Remove(t);
|
dict.Remove(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,18 +62,25 @@ namespace BTCPayServer
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<T> WaitNext<T>(CancellationToken cancellation = default(CancellationToken))
|
public Task<T> WaitNext<T>(CancellationToken cancellation = default(CancellationToken))
|
||||||
{
|
{
|
||||||
return WaitNext<T>(o => true, cancellation);
|
return WaitNext<T>(o => true, cancellation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> WaitNext<T>(Func<T, bool> predicate, CancellationToken cancellation = default(CancellationToken))
|
public async Task<T> WaitNext<T>(Func<T, bool> predicate, CancellationToken cancellation = default(CancellationToken))
|
||||||
{
|
{
|
||||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
|
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
using var subscription = Subscribe<T>((a, b) => { if (predicate(b)) { tcs.TrySetResult(b); a.Unsubscribe(); } });
|
using var subscription = Subscribe<T>((a, b) =>
|
||||||
using (cancellation.Register(() => { tcs.TrySetCanceled(); }))
|
|
||||||
{
|
{
|
||||||
return await tcs.Task.ConfigureAwait(false);
|
if (predicate(b))
|
||||||
}
|
{
|
||||||
|
tcs.TrySetResult(b);
|
||||||
|
a.Unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await using var reg = cancellation.Register(() => tcs.TrySetCanceled(cancellation));
|
||||||
|
return await tcs.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Publish<T>(T evt) where T : class
|
public void Publish<T>(T evt) where T : class
|
||||||
@@ -87,8 +100,17 @@ namespace BTCPayServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock (_SubscriptionsAny)
|
||||||
|
{
|
||||||
|
foreach (var kv in _SubscriptionsAny)
|
||||||
|
{
|
||||||
|
if (kv.Key.IsAssignableFrom(evtType))
|
||||||
|
actionList.AddRange(kv.Value.Values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Logs.Events.IsEnabled(LogLevel.Information))
|
if (Logs.Events.IsEnabled(LogLevel.Information))
|
||||||
Logs.Events.LogInformation("Event published {0}", string.IsNullOrEmpty(evt?.ToString()) ? evtType.GetType().Name : evt.ToString());
|
Logs.Events.LogInformation("{0}", string.IsNullOrEmpty(evt?.ToString()) ? evtType.Name : evt.ToString());
|
||||||
|
|
||||||
foreach (var sub in actionList)
|
foreach (var sub in actionList)
|
||||||
{
|
{
|
||||||
@@ -103,6 +125,12 @@ namespace BTCPayServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to any event of exactly type T
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subscription"></param>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
public IEventAggregatorSubscription Subscribe<T>(Action<IEventAggregatorSubscription, T> subscription)
|
public IEventAggregatorSubscription Subscribe<T>(Action<IEventAggregatorSubscription, T> subscription)
|
||||||
{
|
{
|
||||||
var eventType = typeof(T);
|
var eventType = typeof(T);
|
||||||
@@ -111,6 +139,20 @@ namespace BTCPayServer
|
|||||||
return Subscribe(eventType, s);
|
return Subscribe(eventType, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to any event of type T or any of its derived type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subscription"></param>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEventAggregatorSubscription SubscribeAny<T>(Action<IEventAggregatorSubscription, T> subscription)
|
||||||
|
{
|
||||||
|
var eventType = typeof(T);
|
||||||
|
var s = new Subscription(this, eventType) { Any = true };
|
||||||
|
s.Act = (o) => subscription(s, (T)o);
|
||||||
|
return Subscribe(eventType, s);
|
||||||
|
}
|
||||||
|
|
||||||
public IEventAggregatorSubscription Subscribe(Type eventType, Action<IEventAggregatorSubscription, object> subscription)
|
public IEventAggregatorSubscription Subscribe(Type eventType, Action<IEventAggregatorSubscription, object> subscription)
|
||||||
{
|
{
|
||||||
var s = new Subscription(this, eventType);
|
var s = new Subscription(this, eventType);
|
||||||
@@ -120,20 +162,26 @@ namespace BTCPayServer
|
|||||||
|
|
||||||
private IEventAggregatorSubscription Subscribe(Type eventType, Subscription subscription)
|
private IEventAggregatorSubscription Subscribe(Type eventType, Subscription subscription)
|
||||||
{
|
{
|
||||||
lock (_Subscriptions)
|
var subscriptions = subscription.Any ? _SubscriptionsAny : _Subscriptions;
|
||||||
|
lock (subscriptions)
|
||||||
{
|
{
|
||||||
if (!_Subscriptions.TryGetValue(eventType, out Dictionary<Subscription, Action<object>> actions))
|
if (!subscriptions.TryGetValue(eventType, out Dictionary<Subscription, Action<object>> actions))
|
||||||
{
|
{
|
||||||
actions = new Dictionary<Subscription, Action<object>>();
|
actions = new Dictionary<Subscription, Action<object>>();
|
||||||
_Subscriptions.Add(eventType, actions);
|
subscriptions.Add(eventType, actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.Add(subscription, subscription.Act);
|
actions.Add(subscription, subscription.Act);
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly Dictionary<Type, Dictionary<Subscription, Action<object>>> _Subscriptions = new Dictionary<Type, Dictionary<Subscription, Action<object>>>();
|
readonly Dictionary<Type, Dictionary<Subscription, Action<object>>> _Subscriptions = new Dictionary<Type, Dictionary<Subscription, Action<object>>>();
|
||||||
|
|
||||||
|
readonly Dictionary<Type, Dictionary<Subscription, Action<object>>>
|
||||||
|
_SubscriptionsAny = new Dictionary<Type, Dictionary<Subscription, Action<object>>>();
|
||||||
|
|
||||||
public Logs Logs { get; }
|
public Logs Logs { get; }
|
||||||
|
|
||||||
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<T, TReturn> subscription)
|
public IEventAggregatorSubscription Subscribe<T, TReturn>(Func<T, TReturn> subscription)
|
||||||
@@ -145,6 +193,7 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(sub, t)));
|
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(sub, t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelSubscription<T> : IEventAggregatorSubscription
|
class ChannelSubscription<T> : IEventAggregatorSubscription
|
||||||
{
|
{
|
||||||
private Channel<T> _evts;
|
private Channel<T> _evts;
|
||||||
@@ -187,23 +236,35 @@ namespace BTCPayServer
|
|||||||
_evts.Writer.TryComplete();
|
_evts.Writer.TryComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEventAggregatorSubscription SubscribeAsync<T>(Func<T, Task> subscription)
|
public IEventAggregatorSubscription SubscribeAsync<T>(Func<T, Task> subscription)
|
||||||
{
|
{
|
||||||
Channel<T> evts = Channel.CreateUnbounded<T>();
|
Channel<T> evts = Channel.CreateUnbounded<T>();
|
||||||
var innerSubscription = Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => evts.Writer.TryWrite(t)));
|
var innerSubscription = Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => evts.Writer.TryWrite(t)));
|
||||||
return new ChannelSubscription<T>(evts, innerSubscription, subscription, Logs);
|
return new ChannelSubscription<T>(evts, innerSubscription, subscription, Logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEventAggregatorSubscription Subscribe<T>(Action<T> subscription)
|
public IEventAggregatorSubscription Subscribe<T>(Action<T> subscription)
|
||||||
{
|
{
|
||||||
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(t)));
|
return Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEventAggregatorSubscription SubscribeAny<T>(Action<T> subscription)
|
||||||
|
{
|
||||||
|
return SubscribeAny(new Action<IEventAggregatorSubscription, T>((sub, t) => subscription(t)));
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (_Subscriptions)
|
lock (_Subscriptions)
|
||||||
{
|
{
|
||||||
_Subscriptions.Clear();
|
_Subscriptions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock (_SubscriptionsAny)
|
||||||
|
{
|
||||||
|
_SubscriptionsAny.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BTCPayServer;
|
using BTCPayServer;
|
||||||
|
using BTCPayServer.Abstractions;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
@@ -83,7 +83,32 @@ namespace Microsoft.AspNetCore.Mvc
|
|||||||
values: new { invoiceId },
|
values: new { invoiceId },
|
||||||
scheme, host, pathbase);
|
scheme, host, pathbase);
|
||||||
}
|
}
|
||||||
|
#nullable enable
|
||||||
|
public static string ReceiptLink(this LinkGenerator urlHelper, string invoiceId, RequestBaseUrl baseUrl)
|
||||||
|
=> urlHelper.GetUriByAction(
|
||||||
|
action: nameof(UIInvoiceController.InvoiceReceipt),
|
||||||
|
controller: "UIInvoice",
|
||||||
|
values: new { invoiceId },
|
||||||
|
baseUrl);
|
||||||
|
|
||||||
|
|
||||||
|
public static string InvoiceCheckoutLink(this LinkGenerator urlHelper, string invoiceId, RequestBaseUrl baseUrl)
|
||||||
|
=> urlHelper.GetUriByAction(
|
||||||
|
action: nameof(UIInvoiceController.Checkout),
|
||||||
|
controller: "UIInvoice",
|
||||||
|
values: new { invoiceId },
|
||||||
|
baseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
public static string GetUriByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
object? values,
|
||||||
|
RequestBaseUrl requestBaseUrl,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions? options = null) => generator.GetUriByAction(action, controller, values, requestBaseUrl.Scheme, requestBaseUrl.Host, requestBaseUrl.PathBase, fragment, options) ?? throw new InvalidOperationException($"Bug, unable to generate link for {controller}.{action}");
|
||||||
|
#nullable restore
|
||||||
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId, string pullPaymentId, PayoutState payoutState, string scheme, HostString host, string pathbase)
|
public static string PayoutLink(this LinkGenerator urlHelper, string walletIdOrStoreId, string pullPaymentId, PayoutState payoutState, string scheme, HostString host, string pathbase)
|
||||||
{
|
{
|
||||||
WalletId.TryParse(walletIdOrStoreId, out var wallet);
|
WalletId.TryParse(walletIdOrStoreId, out var wallet);
|
||||||
|
|||||||
@@ -36,13 +36,35 @@ namespace BTCPayServer.HostedServices
|
|||||||
readonly Channel<object> _Events = Channel.CreateUnbounded<object>();
|
readonly Channel<object> _Events = Channel.CreateUnbounded<object>();
|
||||||
public async Task ProcessEvents(CancellationToken cancellationToken)
|
public async Task ProcessEvents(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (await _Events.Reader.WaitToReadAsync(cancellationToken))
|
// We want current job to finish before exiting
|
||||||
|
// ReSharper disable once MethodSupportsCancellation
|
||||||
|
while (await _Events.Reader.WaitToReadAsync())
|
||||||
{
|
{
|
||||||
if (_Events.Reader.TryRead(out var evt))
|
while (_Events.Reader.TryRead(out var evt))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessEvent(evt, cancellationToken);
|
if (evt is ExecutingEvent e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, e.CancellationToken);
|
||||||
|
await ProcessEvent(e.Event, linkedCts.Token);
|
||||||
|
e.Tcs.TrySetResult();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException pce) when (e.CancellationToken.IsCancellationRequested || cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
e.Tcs.TrySetCanceled(pce.CancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
e.Tcs.TrySetException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ProcessEvent(evt, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch when (cancellationToken.IsCancellationRequested)
|
catch when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -71,12 +93,24 @@ namespace BTCPayServer.HostedServices
|
|||||||
{
|
{
|
||||||
_Subscriptions.Add(_EventAggregator.Subscribe<T>(e => _Events.Writer.TryWrite(e!)));
|
_Subscriptions.Add(_EventAggregator.Subscribe<T>(e => _Events.Writer.TryWrite(e!)));
|
||||||
}
|
}
|
||||||
|
protected void SubscribeAny<T>()
|
||||||
|
{
|
||||||
|
_Subscriptions.Add(_EventAggregator.SubscribeAny<T>(e => _Events.Writer.TryWrite(e!)));
|
||||||
|
}
|
||||||
|
|
||||||
protected void PushEvent(object obj)
|
protected void PushEvent(object obj)
|
||||||
{
|
{
|
||||||
_Events.Writer.TryWrite(obj);
|
_Events.Writer.TryWrite(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record ExecutingEvent(object Event, TaskCompletionSource Tcs, CancellationToken CancellationToken);
|
||||||
|
protected Task RunEvent(object obj, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
_Events.Writer.TryWrite(new ExecutingEvent(obj, tcs, cancellationToken));
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
SubscribeToEvents();
|
SubscribeToEvents();
|
||||||
@@ -87,6 +121,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
|
|
||||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
_Events.Writer.TryComplete();
|
||||||
_Subscriptions.ForEach(subscription => subscription.Dispose());
|
_Subscriptions.ForEach(subscription => subscription.Dispose());
|
||||||
_Cts.Cancel();
|
_Cts.Cancel();
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -168,6 +168,12 @@ namespace BTCPayServer.Hosting
|
|||||||
// /Components/{View Component Name}/{View Name}.cshtml
|
// /Components/{View Component Name}/{View Name}.cshtml
|
||||||
o.ViewLocationFormats.Add("/{0}.cshtml");
|
o.ViewLocationFormats.Add("/{0}.cshtml");
|
||||||
o.PageViewLocationFormats.Add("/{0}.cshtml");
|
o.PageViewLocationFormats.Add("/{0}.cshtml");
|
||||||
|
|
||||||
|
// Allows the use of Area for plugins
|
||||||
|
o.AreaViewLocationFormats.Add("/Plugins/{2}/Views/{1}/{0}.cshtml");
|
||||||
|
o.AreaViewLocationFormats.Add("/Plugins/{2}/Views/{0}.cshtml");
|
||||||
|
o.AreaViewLocationFormats.Add("/Plugins/{2}/Views/Shared/{0}.cshtml");
|
||||||
|
o.AreaViewLocationFormats.Add("/{0}.cshtml");
|
||||||
})
|
})
|
||||||
.AddNewtonsoftJson()
|
.AddNewtonsoftJson()
|
||||||
.AddPlugins(services, Configuration, LoggerFactory, bootstrapServiceProvider)
|
.AddPlugins(services, Configuration, LoggerFactory, bootstrapServiceProvider)
|
||||||
|
|||||||
Reference in New Issue
Block a user