update migration

This commit is contained in:
Kukks 2024-07-26 14:00:28 +02:00
parent 3821795ec5
commit 5ea1af44d3
No known key found for this signature in database
GPG Key ID: 8E5530D9D1C93097
6 changed files with 85 additions and 1599 deletions

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240724102749_AppStuff")]
[Migration("20240726113051_AppStuff")]
partial class AppStuff
{
/// <inheritdoc />
@ -31,19 +31,22 @@ namespace BTCPayServer.Migrations
b.Property<string>("Key")
.HasColumnType("text");
b.Property<long>("Version")
.HasColumnType("bigint");
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<byte[]>("Value")
.HasColumnType("bytea");
b.Property<decimal>("Version")
.HasColumnType("numeric(20,0)");
b.HasKey("Key", "UserId");
b.HasKey("Key", "Version", "UserId");
b.HasIndex("UserId");
b.HasIndex("Key", "UserId")
.IsUnique();
b.ToTable("AppStorageItems", t =>
{
t.HasTrigger("LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA");

View File

@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
/// <inheritdoc />
public partial class AppStuff : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AppStorageItems",
columns: table => new
{
Key = table.Column<string>(type: "text", nullable: false),
Version = table.Column<long>(type: "bigint", nullable: false),
UserId = table.Column<string>(type: "text", nullable: false),
Value = table.Column<byte[]>(type: "bytea", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AppStorageItems", x => new { x.Key, x.Version, x.UserId });
table.ForeignKey(
name: "FK_AppStorageItems_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AppStorageItems_Key_UserId",
table: "AppStorageItems",
columns: new[] { "Key", "UserId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AppStorageItems_UserId",
table: "AppStorageItems",
column: "UserId");
migrationBuilder.Sql("CREATE FUNCTION \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"() RETURNS trigger as $LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$\r\nBEGIN\r\n DELETE FROM \"AppStorageItems\"\r\n WHERE NEW.\"UserId\" = \"AppStorageItems\".\"UserId\" AND NEW.\"Key\" = \"AppStorageItems\".\"Key\" AND NEW.\"Version\" > \"AppStorageItems\".\"Version\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA BEFORE INSERT\r\nON \"AppStorageItems\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"();");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DROP FUNCTION \"LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA\"() CASCADE;");
migrationBuilder.DropTable(
name: "AppStorageItems");
}
}
}

View File

@ -28,19 +28,22 @@ namespace BTCPayServer.Migrations
b.Property<string>("Key")
.HasColumnType("text");
b.Property<long>("Version")
.HasColumnType("bigint");
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<byte[]>("Value")
.HasColumnType("bytea");
b.Property<decimal>("Version")
.HasColumnType("numeric(20,0)");
b.HasKey("Key", "UserId");
b.HasKey("Key", "Version", "UserId");
b.HasIndex("UserId");
b.HasIndex("Key", "UserId")
.IsUnique();
b.ToTable("AppStorageItems", t =>
{
t.HasTrigger("LC_TRIGGER_BEFORE_INSERT_APPSTORAGEITEMDATA");

View File

@ -2,6 +2,7 @@
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayApp.VSS;
using BTCPayServer.Abstractions.Constants;
@ -40,13 +41,13 @@ public class VSSController : Controller, IVSSAPI
[HttpPost(HttpVSSAPIClient.GET_OBJECT)]
[MediaTypeConstraint("application/octet-stream")]
public async Task<GetObjectResponse> GetObjectAsync(GetObjectRequest request)
public async Task<GetObjectResponse> GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken)
{
var userId = _userManager.GetUserId(User);
await using var dbContext = _dbContextFactory.CreateContext();
var store = await dbContext.AppStorageItems.SingleOrDefaultAsync(data =>
data.Key == request.Key && data.UserId == userId);
data.Key == request.Key && data.UserId == userId, cancellationToken: cancellationToken);
if (store == null)
{
return SetResult<GetObjectResponse>(
@ -91,7 +92,7 @@ public class VSSController : Controller, IVSSAPI
[HttpPost(HttpVSSAPIClient.PUT_OBJECTS)]
[MediaTypeConstraint("application/octet-stream")]
public async Task<PutObjectResponse> PutObjectAsync(PutObjectRequest request)
public async Task<PutObjectResponse> PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken)
{
if (!VerifyGlobalVersion(request.GlobalVersion))
@ -104,7 +105,7 @@ public class VSSController : Controller, IVSSAPI
await using var dbContext = _dbContextFactory.CreateContext();
await using var dbContextTransaction = await dbContext.Database.BeginTransactionAsync();
await using var dbContextTransaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
try
{
if (request.TransactionItems.Any())
@ -113,7 +114,7 @@ public class VSSController : Controller, IVSSAPI
{
Key = data.Key, Value = data.Value.ToByteArray(), UserId = userId, Version = data.Version
});
await dbContext.AppStorageItems.AddRangeAsync(items);
await dbContext.AppStorageItems.AddRangeAsync(items, cancellationToken);
}
if (request.DeleteItems.Any())
@ -121,15 +122,15 @@ public class VSSController : Controller, IVSSAPI
var deleteQuery = request.DeleteItems.Aggregate(
dbContext.AppStorageItems.Where(data => data.UserId == userId),
(current, key) => current.Where(data => data.Key == key.Key && data.Version == key.Version));
await deleteQuery.ExecuteDeleteAsync();
await deleteQuery.ExecuteDeleteAsync(cancellationToken: cancellationToken);
}
await dbContext.SaveChangesAsync();
await dbContextTransaction.CommitAsync();
await dbContext.SaveChangesAsync(cancellationToken);
await dbContextTransaction.CommitAsync(cancellationToken);
}
catch (Exception e)
{
await dbContextTransaction.RollbackAsync();
await dbContextTransaction.RollbackAsync(cancellationToken);
return SetResult<PutObjectResponse>(BadRequest(new ErrorResponse()
{
ErrorCode = ErrorCode.ConflictException, Message = e.Message
@ -142,7 +143,7 @@ public class VSSController : Controller, IVSSAPI
[HttpPost(HttpVSSAPIClient.DELETE_OBJECT)]
[MediaTypeConstraint("application/octet-stream")]
public async Task<DeleteObjectResponse> DeleteObjectAsync(DeleteObjectRequest request)
public async Task<DeleteObjectResponse> DeleteObjectAsync(DeleteObjectRequest request, CancellationToken cancellationToken)
{
@ -150,7 +151,7 @@ public class VSSController : Controller, IVSSAPI
await using var dbContext = _dbContextFactory.CreateContext();
var store = await dbContext.AppStorageItems
.Where(data => data.Key == request.KeyValue.Key && data.UserId == userId &&
data.Version == request.KeyValue.Version).ExecuteDeleteAsync();
data.Version == request.KeyValue.Version).ExecuteDeleteAsync(cancellationToken: cancellationToken);
return store == 0
? SetResult<DeleteObjectResponse>(
new NotFoundObjectResult(new ErrorResponse()
@ -161,13 +162,13 @@ public class VSSController : Controller, IVSSAPI
}
[HttpPost(HttpVSSAPIClient.LIST_KEY_VERSIONS)]
public async Task<ListKeyVersionsResponse> ListKeyVersionsAsync(ListKeyVersionsRequest request)
public async Task<ListKeyVersionsResponse> ListKeyVersionsAsync(ListKeyVersionsRequest request, CancellationToken cancellationToken)
{
var userId = _userManager.GetUserId(User);
await using var dbContext = _dbContextFactory.CreateContext();
var items = await dbContext.AppStorageItems
.Where(data => data.UserId == userId)
.Select(data => new KeyValue() {Key = data.Key, Version = data.Version}).ToListAsync();
.Select(data => new KeyValue() {Key = data.Key, Version = data.Version}).ToListAsync(cancellationToken: cancellationToken);
return new ListKeyVersionsResponse {KeyVersions = {items}};
}
}

View File

@ -1,263 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using BTCPayApp.CommonServer;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Lightning;
using Microsoft.AspNetCore.SignalR;
using NBitcoin;
using LightningPayment = BTCPayApp.CommonServer.Models.LightningPayment;
namespace BTCPayServer.App;
public class BTCPayAppLightningConnectionStringHandler:ILightningConnectionStringHandler
{
private readonly IHubContext<BTCPayAppHub, IBTCPayAppHubClient> _hubContext;
private readonly BTCPayAppState _appState;
private readonly DefaultHubLifetimeManager<BTCPayAppHub> _lifetimeManager;
public BTCPayAppLightningConnectionStringHandler(IHubContext<BTCPayAppHub, IBTCPayAppHubClient> hubContext, BTCPayAppState appState)
{
_hubContext = hubContext;
_appState = appState;
}
public ILightningClient Create(string connectionString, Network network, [UnscopedRef] out string error)
{
var kv = LightningConnectionStringHelper.ExtractValues(connectionString, out var type);
if (type != "app")
{
error = null;
return null;
}
if (!kv.TryGetValue("group", out var key))
{
error = $"The key 'group' is mandatory for app connection strings";
return null;
}
if (!_appState.GroupToConnectionId.TryGetValue(key, out var connectionId))
{
error = $"The group {key} is not connected";
return null;
}
error = null;
return new BTCPayAppLightningClient(_hubContext, _appState, key, network );
}
}
public class BTCPayAppLightningClient:ILightningClient
{
private readonly IHubContext<BTCPayAppHub, IBTCPayAppHubClient> _hubContext;
private readonly BTCPayAppState _appState;
private readonly string _key;
private readonly Network _network;
public BTCPayAppLightningClient(IHubContext<BTCPayAppHub, IBTCPayAppHubClient> hubContext, BTCPayAppState appState, string key, Network network)
{
_hubContext = hubContext;
_appState = appState;
_key = key;
_network = network;
}
public override string ToString()
{
return $"type=app;group={_key}".ToLower();
}
public IBTCPayAppHubClient HubClient => _appState.GroupToConnectionId.TryGetValue(_key, out var connId) ? _hubContext.Clients.Client(connId) : throw new InvalidOperationException("Connection not found");
public async Task<LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken())
{
return await GetInvoice(uint256.Parse(invoiceId), cancellation);
}
public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, CancellationToken cancellation = new CancellationToken())
{
var lp = await HubClient.GetLightningInvoice(paymentHash.ToString());
return lp is null ? null : ToLightningInvoice(lp, _network);
}
public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = new CancellationToken())
{
return await ListInvoices(new ListInvoicesParams(), cancellation);
}
public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request, CancellationToken cancellation = new CancellationToken())
{
var invs = await HubClient.GetLightningInvoices(request);
return invs.Select(i => ToLightningInvoice(i, _network)).ToArray();
}
public async Task<Lightning.LightningPayment> GetPayment(string paymentHash, CancellationToken cancellation = new CancellationToken())
{
return ToLightningPayment(await HubClient.GetLightningPayment(paymentHash));
}
private static Lightning.LightningPayment ToLightningPayment(LightningPayment lightningPayment)
{
return new Lightning.LightningPayment()
{
Id = lightningPayment.PaymentHash,
Amount = LightMoney.MilliSatoshis(lightningPayment.Value),
PaymentHash = lightningPayment.PaymentHash,
Preimage = lightningPayment.Preimage,
BOLT11 = lightningPayment.PaymentRequests.FirstOrDefault(),
Status = lightningPayment.Status
};
}
public async Task<Lightning.LightningPayment[]> ListPayments(CancellationToken cancellation = new CancellationToken())
{
return await ListPayments(new ListPaymentsParams(), cancellation);
}
public async Task<Lightning.LightningPayment[]> ListPayments(ListPaymentsParams request, CancellationToken cancellation = new CancellationToken())
{
var invs = await HubClient.GetLightningPayments(request);
return invs.Select(ToLightningPayment).ToArray();
}
public async Task<LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry,
CancellationToken cancellation = new CancellationToken())
{
return await CreateInvoice(new CreateInvoiceParams(amount, description, expiry), cancellation);
}
public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = new CancellationToken())
{
var lp = await HubClient.CreateInvoice(new CreateLightningInvoiceRequest(createInvoiceRequest.Amount, createInvoiceRequest.Description, createInvoiceRequest.Expiry)
{
DescriptionHashOnly = createInvoiceRequest.DescriptionHashOnly,
PrivateRouteHints = createInvoiceRequest.PrivateRouteHints,
});
return ToLightningInvoice(lp, _network);
}
private static LightningInvoice ToLightningInvoice(LightningPayment lightningPayment, Network _network)
{
var paymenRequest = BOLT11PaymentRequest.Parse(lightningPayment.PaymentRequests.First(), _network);
return new LightningInvoice()
{
Id = lightningPayment.PaymentHash,
Amount = LightMoney.MilliSatoshis(lightningPayment.Value),
PaymentHash = lightningPayment.PaymentHash,
Preimage = lightningPayment.Preimage,
PaidAt = lightningPayment.Status == LightningPaymentStatus.Complete? DateTimeOffset.UtcNow: null, //TODO: store these in ln payment
BOLT11 = lightningPayment.PaymentRequests.FirstOrDefault(),
Status = lightningPayment.Status == LightningPaymentStatus.Complete? LightningInvoiceStatus.Paid: paymenRequest.ExpiryDate < DateTimeOffset.UtcNow? LightningInvoiceStatus.Expired: LightningInvoiceStatus.Unpaid
};
}
public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = new CancellationToken())
{
return new Listener(_appState, _network, _key);
}
public class Listener:ILightningInvoiceListener
{
private readonly BTCPayAppState _btcPayAppState;
private readonly Network _network;
private readonly string _key;
private readonly Channel<LightningPayment> _channel = Channel.CreateUnbounded<LightningPayment>();
private readonly CancellationTokenSource _cts;
public Listener(BTCPayAppState btcPayAppState, Network network, string key)
{
_btcPayAppState = btcPayAppState;
btcPayAppState.GroupRemoved += BtcPayAppStateOnGroupRemoved;
_network = network;
_key = key;
_cts = new CancellationTokenSource();
_btcPayAppState.OnPaymentUpdate += BtcPayAppStateOnOnPaymentUpdate;
}
private void BtcPayAppStateOnGroupRemoved(object sender, string e)
{
if(e == _key)
_channel.Writer.Complete();
}
private void BtcPayAppStateOnOnPaymentUpdate(object sender, (string, LightningPayment) e)
{
if (e.Item1.Equals(_key, StringComparison.InvariantCultureIgnoreCase))
_channel.Writer.TryWrite(e.Item2);
}
public void Dispose()
{
_cts?.Cancel();
_btcPayAppState.OnPaymentUpdate -= BtcPayAppStateOnOnPaymentUpdate;
_btcPayAppState.GroupRemoved -= BtcPayAppStateOnGroupRemoved;
_channel.Writer.TryComplete();
}
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
{
return ToLightningInvoice(await _channel.Reader.ReadAsync( CancellationTokenSource.CreateLinkedTokenSource(cancellation, _cts.Token).Token), _network);
}
}
public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task<LightningNodeBalance> GetBalance(CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
public async Task<PayResponse> Pay(PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken())
{
return await Pay(null, payParams, cancellation);
}
public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams, CancellationToken cancellation = new CancellationToken())
{
return await HubClient.PayInvoice(bolt11, payParams.Amount?.MilliSatoshi);
}
public async Task<PayResponse> Pay(string bolt11, CancellationToken cancellation = new CancellationToken())
{
return await Pay(bolt11, new PayInvoiceParams(), cancellation);
}
public async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
public async Task<BitcoinAddress> GetDepositAddress(CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
public async Task<ConnectionResult> ConnectTo(NodeInfo nodeInfo, CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
public async Task<LightningChannel[]> ListChannels(CancellationToken cancellation = new CancellationToken())
{
throw new NotImplementedException();
}
}