mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 09:54:30 +01:00
Wallet Transactions Export: Add BIP-329 support (#4799)
* Wallet Transactions Export: Add BIP-329 support * Adjust wording * Export one line per label * Join labels, fix type * Rewrite the ProcessBip329 function to be more performant * Add nullable on all TransactionsExport --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
18c78192ec
commit
c53d5272d6
@ -1570,9 +1570,20 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("\"Amount\": \"3.00000000\"", s.Driver.PageSource);
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// BIP-329 export
|
||||
s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("ExportBIP329")).Click();
|
||||
Thread.Sleep(1000);
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Contains(s.WalletId.ToString(), s.Driver.Url);
|
||||
Assert.EndsWith("export?format=bip329", s.Driver.Url);
|
||||
Assert.Contains("{\"type\":\"tx\",\"ref\":\"", s.Driver.PageSource);
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// CSV export
|
||||
s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("ExportCSV")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -14,6 +14,7 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using ExchangeSharp;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
@ -250,7 +251,6 @@ retry:
|
||||
{
|
||||
// 1. Generate an API Token on https://www.transifex.com/user/settings/api/
|
||||
// 2. Run "dotnet user-secrets set TransifexAPIToken <youapitoken>"
|
||||
|
||||
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV1);
|
||||
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV2);
|
||||
|
||||
|
@ -1317,22 +1317,34 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
|
||||
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, null, null);
|
||||
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation);
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
var export = new TransactionsExport(wallet, walletTransactionsInfo);
|
||||
var res = export.Process(input, format);
|
||||
|
||||
var fileType = format switch
|
||||
{
|
||||
"csv" => "csv",
|
||||
"json" => "json",
|
||||
"bip329" => "jsonl",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||
};
|
||||
var mimeType = format switch
|
||||
{
|
||||
"csv" => "text/csv",
|
||||
"json" => "application/json",
|
||||
"bip329" => "text/jsonl", // https://stackoverflow.com/questions/59938644/what-is-the-mime-type-of-jsonl-files
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||
};
|
||||
var cd = new ContentDisposition
|
||||
{
|
||||
FileName = $"btcpay-{walletId}-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{format}",
|
||||
FileName = $"btcpay-{walletId}-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{fileType}",
|
||||
Inline = true
|
||||
};
|
||||
Response.Headers.Add("Content-Disposition", cd.ToString());
|
||||
Response.Headers.Add("X-Content-Type-Options", "nosniff");
|
||||
return Content(res, "application/" + format);
|
||||
return Content(res, mimeType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UpdateLabelsRequest
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
|
@ -1,16 +1,13 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Client.Models;
|
||||
using System.Text;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using CsvHelper.Configuration;
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Wallets.Export
|
||||
@ -41,18 +38,46 @@ namespace BTCPayServer.Services.Wallets.Export
|
||||
|
||||
if (_walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
{
|
||||
model.Labels = transactionInfo.LabelColors?.Select(l => l.Key).ToList();
|
||||
model.Labels = transactionInfo.LabelColors.Select(l => l.Key).ToList();
|
||||
model.Comment = transactionInfo.Comment;
|
||||
}
|
||||
|
||||
return model;
|
||||
}).ToList();
|
||||
|
||||
return fileFormat switch
|
||||
{
|
||||
"bip329" => ProcessBip329(list),
|
||||
"json" => ProcessJson(list),
|
||||
"csv" => ProcessCsv(list),
|
||||
_ => throw new Exception("Export format not supported")
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase))
|
||||
return ProcessJson(list);
|
||||
if (string.Equals(fileFormat, "csv", StringComparison.OrdinalIgnoreCase))
|
||||
return ProcessCsv(list);
|
||||
throw new Exception("Export format not supported");
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki
|
||||
private static string ProcessBip329(List<ExportTransaction> txs)
|
||||
{
|
||||
var sw = new StringWriter();
|
||||
var jsonw = new JsonTextWriter(sw);
|
||||
foreach (var tx in txs)
|
||||
{
|
||||
if (tx.Labels is null)
|
||||
continue;
|
||||
foreach (var label in tx.Labels)
|
||||
{
|
||||
jsonw.WriteStartObject();
|
||||
jsonw.WritePropertyName("type");
|
||||
jsonw.WriteValue("tx");
|
||||
jsonw.WritePropertyName("ref");
|
||||
jsonw.WriteValue(tx.TransactionId);
|
||||
jsonw.WritePropertyName("label");
|
||||
jsonw.WriteValue(label);
|
||||
jsonw.WriteEndObject();
|
||||
jsonw.WriteWhitespace("\n");
|
||||
}
|
||||
}
|
||||
jsonw.Flush();
|
||||
return sw.ToString();
|
||||
}
|
||||
|
||||
private static string ProcessJson(List<ExportTransaction> invoices)
|
||||
@ -87,14 +112,14 @@ namespace BTCPayServer.Services.Wallets.Export
|
||||
public class ExportTransaction
|
||||
{
|
||||
[Name("Transaction Id")]
|
||||
public string TransactionId { get; set; }
|
||||
public string TransactionId { get; set; } = string.Empty;
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public string Amount { get; set; } = string.Empty;
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
|
||||
[Name("Is Confirmed")]
|
||||
public bool IsConfirmed { get; set; }
|
||||
public string Comment { get; set; }
|
||||
public List<string> Labels { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public List<string>? Labels { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,7 @@
|
||||
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
|
||||
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="csv" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportCSV">CSV</a>
|
||||
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="json" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportJSON">JSON</a>
|
||||
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="bip329" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportBIP329">Wallet Labels (BIP-329)</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user