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("/stores/{storeId}/invoices/${invoiceId}")]
|
||||
[HttpGet("/stores/{storeId}/invoices/{invoiceId}")]
|
||||
[Authorize(Policy = Policies.CanViewInvoices, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
public async Task<IActionResult> Invoice(string invoiceId)
|
||||
{
|
||||
@@ -837,13 +837,7 @@ namespace BTCPayServer.Controllers
|
||||
lang ??= storeBlob.DefaultLang;
|
||||
|
||||
var receiptEnabled = InvoiceDataBase.ReceiptOptions.Merge(storeBlob.ReceiptOptions, invoice.ReceiptOptions).Enabled is true;
|
||||
var receiptUrl = receiptEnabled ? _linkGenerator.GetUriByAction(
|
||||
nameof(InvoiceReceipt),
|
||||
"UIInvoice",
|
||||
new { invoiceId },
|
||||
Request.Scheme,
|
||||
Request.Host,
|
||||
Request.PathBase) : null;
|
||||
var receiptUrl = receiptEnabled ? _linkGenerator.ReceiptLink(invoiceId, Request.GetRequestBaseUrl()) : null;
|
||||
|
||||
var orderId = invoice.Metadata.OrderId;
|
||||
var supportUrl = !string.IsNullOrEmpty(storeBlob.StoreSupportUrl)
|
||||
|
||||
@@ -14,16 +14,19 @@ namespace BTCPayServer
|
||||
{
|
||||
void Unsubscribe();
|
||||
}
|
||||
|
||||
public class EventAggregator : IDisposable
|
||||
{
|
||||
public EventAggregator(Logs logs)
|
||||
{
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
class Subscription : IEventAggregatorSubscription
|
||||
{
|
||||
private readonly EventAggregator aggregator;
|
||||
readonly Type t;
|
||||
|
||||
public Subscription(EventAggregator aggregator, Type t)
|
||||
{
|
||||
this.aggregator = aggregator;
|
||||
@@ -31,21 +34,24 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public Action<Object> Act { get; set; }
|
||||
public bool Any { get; set; }
|
||||
|
||||
bool _Disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_Disposed)
|
||||
return;
|
||||
_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.Count == 0)
|
||||
this.aggregator._Subscriptions.Remove(t);
|
||||
dict.Remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,18 +62,25 @@ namespace BTCPayServer
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<T> WaitNext<T>(CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
return WaitNext<T>(o => true, cancellation);
|
||||
}
|
||||
|
||||
public async Task<T> WaitNext<T>(Func<T, bool> predicate, CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
|
||||
using var subscription = Subscribe<T>((a, b) => { if (predicate(b)) { tcs.TrySetResult(b); a.Unsubscribe(); } });
|
||||
using (cancellation.Register(() => { tcs.TrySetCanceled(); }))
|
||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using var subscription = Subscribe<T>((a, b) =>
|
||||
{
|
||||
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
|
||||
@@ -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))
|
||||
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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
var eventType = typeof(T);
|
||||
@@ -111,6 +139,20 @@ namespace BTCPayServer
|
||||
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)
|
||||
{
|
||||
var s = new Subscription(this, eventType);
|
||||
@@ -120,20 +162,26 @@ namespace BTCPayServer
|
||||
|
||||
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>>();
|
||||
_Subscriptions.Add(eventType, actions);
|
||||
subscriptions.Add(eventType, actions);
|
||||
}
|
||||
|
||||
actions.Add(subscription, subscription.Act);
|
||||
}
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
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 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)));
|
||||
}
|
||||
|
||||
class ChannelSubscription<T> : IEventAggregatorSubscription
|
||||
{
|
||||
private Channel<T> _evts;
|
||||
@@ -187,23 +236,35 @@ namespace BTCPayServer
|
||||
_evts.Writer.TryComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription SubscribeAsync<T>(Func<T, Task> subscription)
|
||||
{
|
||||
Channel<T> evts = Channel.CreateUnbounded<T>();
|
||||
var innerSubscription = Subscribe(new Action<IEventAggregatorSubscription, T>((sub, t) => evts.Writer.TryWrite(t)));
|
||||
return new ChannelSubscription<T>(evts, innerSubscription, subscription, Logs);
|
||||
}
|
||||
|
||||
public IEventAggregatorSubscription Subscribe<T>(Action<T> subscription)
|
||||
{
|
||||
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()
|
||||
{
|
||||
lock (_Subscriptions)
|
||||
{
|
||||
_Subscriptions.Clear();
|
||||
}
|
||||
|
||||
lock (_SubscriptionsAny)
|
||||
{
|
||||
_SubscriptionsAny.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
using System;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
@@ -83,7 +83,32 @@ namespace Microsoft.AspNetCore.Mvc
|
||||
values: new { invoiceId },
|
||||
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)
|
||||
{
|
||||
WalletId.TryParse(walletIdOrStoreId, out var wallet);
|
||||
|
||||
@@ -36,14 +36,36 @@ namespace BTCPayServer.HostedServices
|
||||
readonly Channel<object> _Events = Channel.CreateUnbounded<object>();
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw;
|
||||
@@ -71,12 +93,24 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
SubscribeToEvents();
|
||||
@@ -87,6 +121,7 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Events.Writer.TryComplete();
|
||||
_Subscriptions.ForEach(subscription => subscription.Dispose());
|
||||
_Cts.Cancel();
|
||||
try
|
||||
|
||||
@@ -168,6 +168,12 @@ namespace BTCPayServer.Hosting
|
||||
// /Components/{View Component Name}/{View Name}.cshtml
|
||||
o.ViewLocationFormats.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()
|
||||
.AddPlugins(services, Configuration, LoggerFactory, bootstrapServiceProvider)
|
||||
|
||||
Reference in New Issue
Block a user