Skip to content

Commit

Permalink
wip triggers
Browse files Browse the repository at this point in the history
  • Loading branch information
Kukks committed Jul 9, 2024
1 parent 5243039 commit 701d5d4
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 85 deletions.
2 changes: 2 additions & 0 deletions BTCPayApp.Core/BTCPayApp.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

<ItemGroup>

<PackageReference Include="Laraue.EfCoreTriggers.SqlLite" Version="8.0.3" />

<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />

<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.6" />
Expand Down
152 changes: 115 additions & 37 deletions BTCPayApp.Core/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using BTCPayApp.CommonServer.Models;
using BTCPayApp.Core.JsonConverters;
using BTCPayApp.Core.LDK;
using BTCPayServer.Lightning;
using Laraue.EfCoreTriggers.Common.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Hosting;
using NBitcoin;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace BTCPayApp.Core.Data;

Expand All @@ -24,60 +20,142 @@ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)

public DbSet<Channel> LightningChannels { get; set; }
public DbSet<AppLightningPayment> LightningPayments { get; set; }
// public DbSet<SpendableCoin> SpendableCoins { get; set; }
public DbSet<Outbox> OutboxItems { get; set; }


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.PaymentRequest)
.HasConversion(
request => request.ToString(),
request => request.ToString(),
str => NetworkHelper.Try(network => BOLT11PaymentRequest.Parse(str, network)));

modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.Secret)
.HasConversion(
request => request.ToString(),
str =>uint256.Parse(str));
request => request.ToString(),
str => uint256.Parse(str));

modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.PaymentHash)
.HasConversion(
request => request.ToString(),
str =>uint256.Parse(str));
request => request.ToString(),
str => uint256.Parse(str));

modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.Value)
.HasConversion(
request => request.MilliSatoshi,
request => request.MilliSatoshi,
str => new LightMoney(str));

modelBuilder.Entity<AppLightningPayment>().Property(payment => payment.AdditionalData).HasJsonConversion();
modelBuilder.Entity<AppLightningPayment>()
.HasKey(w => new {w.PaymentHash, w.Inbound, w.PaymentId});


//handling versioned data
modelBuilder.Entity<Channel>().AfterDelete(trigger => trigger.Action(group => group.Insert<Outbox>(
@ref => new Outbox()
{
Version = @ref.Old.Version,
Key = "Channel-" + @ref.Old.Id,
ActionType = "delete"
})));

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if(typeof(VersionedData).IsAssignableFrom(entityType.ClrType))
{
var builder = modelBuilder.Entity(entityType.ClrType);

builder.Property<ulong>("Version").IsConcurrencyToken().HasDefaultValue(0);
}
}

modelBuilder.Entity<Channel>()
.BeforeUpdate(trigger => trigger
.Action(action => action
.Condition(refs => refs.Old.Id == refs.New.Id)
.Update<Channel>(
(tableRefs, entity) => tableRefs.Old.Id == entity.Id,
(tableRefs, oldChannel) => new Channel() {Version = oldChannel.Version + 1})
.Insert<Outbox>(insert => new Outbox()
{
Key = "Channel-" + insert.New.Id,
Version = insert.New.Version,
ActionType = "update",
Timestamp = DateTimeOffset.UtcNow
}))
.Action(action => action
.Condition(refs => refs.Old.Id != refs.New.Id)
.Insert<Outbox>(insert => new Outbox()
{
Key = "Channel-" + insert.Old.Id,
Version = insert.Old.Version,
ActionType = "delete",
Timestamp = DateTimeOffset.UtcNow
})
.Insert<Outbox>(insert => new Outbox()
{
Key = "Channel-" + insert.New.Id,
Version = insert.New.Version,
ActionType = "update",
Timestamp = DateTimeOffset.UtcNow
})))
.AfterInsert(trigger => trigger
.Action(action => action
.Insert<Outbox>(insert => new Outbox()
{
Key = "Channel-" + insert.New.Id,
Version = insert.New.Version,
ActionType = "insert",
Timestamp = DateTimeOffset.UtcNow
}))).AfterDelete(trigger => trigger
.Action(action => action
.Insert<Outbox>(insert => new Outbox()
{
Key = "Channel-" + insert.Old.Id,
Version = insert.Old.Version,
ActionType = "delete",
Timestamp = DateTimeOffset.UtcNow
})));

base.OnModelCreating(modelBuilder);
}
}

public static class ValueConversionExtensions
public class Outbox
{
public DateTimeOffset Timestamp { get; set; }
public string ActionType { get; set; }
public string Key { get; set; }
public ulong Version { get; set; }
}

public class OutboxProcessor : IHostedService
{
public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;

public OutboxProcessor(IDbContextFactory<AppDbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}

private async Task ProcessOutbox(CancellationToken cancellationToken = default)
{
await using var db =
new AppDbContext(new DbContextOptionsBuilder<AppDbContext>().UseSqlite("Data Source=outbox.db").Options);
var outbox = db.Set<Outbox>();
var outboxItems = await outbox.ToListAsync();
foreach (var outboxItem in outboxItems)
{
// Process outbox item
}
}

public async Task StartAsync(CancellationToken cancellationToken)
{
}

public async Task StopAsync(CancellationToken cancellationToken)
{
var converter = new ValueConverter<T, string>
(
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
v => JsonSerializer.Deserialize<T>(v, JsonSerializerOptions.Default) ?? new T()
);

var comparer = new ValueComparer<T>
(
(l, r) => JsonSerializer.Serialize(l,JsonSerializerOptions.Default) == JsonSerializer.Serialize(r,JsonSerializerOptions.Default),
v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(),
v => JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(v,JsonSerializerOptions.Default), JsonSerializerOptions.Default)!
);

propertyBuilder.HasConversion(converter);
propertyBuilder.Metadata.SetValueConverter(converter);
propertyBuilder.Metadata.SetValueComparer(comparer);
propertyBuilder.HasColumnType("jsonb");

return propertyBuilder;
throw new NotImplementedException();
}
}
37 changes: 37 additions & 0 deletions BTCPayApp.Core/Data/AppLightningPayment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using BTCPayApp.Core.JsonConverters;
using BTCPayServer.Lightning;
using NBitcoin;

namespace BTCPayApp.Core.Data;

public class AppLightningPayment : VersionedData
{
[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 PaymentHash { get; set; }

public string PaymentId { get; set; }
public string? Preimage { get; set; }

[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 Secret { get; set; }

public bool Inbound { get; set; }

[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
public DateTimeOffset Timestamp { get; set; }

[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney Value { get; set; }

[JsonConverter(typeof(JsonStringEnumConverter))]
public LightningPaymentStatus Status { get; set; }

[JsonConverter(typeof(BOLT11PaymentRequestJsonConverter))]
public BOLT11PaymentRequest PaymentRequest { get; set; }

[JsonExtensionData] public Dictionary<string, JsonElement> AdditionalData { get; set; } = new();

public override string Entity => "LightningPayment";
}
7 changes: 4 additions & 3 deletions BTCPayApp.Core/Data/Channel.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
namespace BTCPayApp.Core.Data;

public class Channel
public class Channel:VersionedData
{
public string Id { get; set; }
public List<string> Aliases { get; set; }
public byte[] Data { get; set; }




public override string Entity => "Channel";
}
7 changes: 0 additions & 7 deletions BTCPayApp.Core/Data/LightningConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,4 @@ public byte[] RGB
public Dictionary<string, PeerInfo> Peers { get; set; } = new();

public bool AcceptInboundConnection{ get; set; }
}

public record PeerInfo
{
public string Endpoint { get; set; }
public bool Persistent { get; set; }
public bool Trusted { get; set; }
}
8 changes: 8 additions & 0 deletions BTCPayApp.Core/Data/PeerInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BTCPayApp.Core.Data;

public record PeerInfo
{
public string Endpoint { get; set; }
public bool Persistent { get; set; }
public bool Trusted { get; set; }
}
4 changes: 3 additions & 1 deletion BTCPayApp.Core/Data/Setting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace BTCPayApp.Core.Data;

public class Setting
public class Setting:VersionedData
{
[Key]
public string Key { get; set; }
public byte[] Value { get; set; }

public override string Entity => "Setting";
}
36 changes: 36 additions & 0 deletions BTCPayApp.Core/Data/ValueConversionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace BTCPayApp.Core.Data;

public static class ValueConversionExtensions
{
public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder)
where T : class, new()
{
var converter = new ValueConverter<T, string>
(
v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
v => JsonSerializer.Deserialize<T>(v, JsonSerializerOptions.Default) ?? new T()
);

var comparer = new ValueComparer<T>
(
(l, r) => JsonSerializer.Serialize(l, JsonSerializerOptions.Default) ==
JsonSerializer.Serialize(r, JsonSerializerOptions.Default),
v => v == null ? 0 : JsonSerializer.Serialize(v, JsonSerializerOptions.Default).GetHashCode(),
v => JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(v, JsonSerializerOptions.Default),
JsonSerializerOptions.Default)!
);

propertyBuilder.HasConversion(converter);
propertyBuilder.Metadata.SetValueConverter(converter);
propertyBuilder.Metadata.SetValueComparer(comparer);
propertyBuilder.HasColumnType("jsonb");

return propertyBuilder;
}
}
10 changes: 10 additions & 0 deletions BTCPayApp.Core/Data/VersionedData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace BTCPayApp.Core.Data;

public abstract class VersionedData
{
public ulong Version { get; set; } = 0;
[NotMapped]
public abstract string Entity { get; }
}
8 changes: 4 additions & 4 deletions BTCPayApp.Core/Helpers/ChannelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ async Task OnEvent(object? sender, TEvent evt)
await channel.Writer.WriteAsync(evt, cancellationToken);
}

add(new AsyncEventHandler<TEvent>(OnEvent));
_ = ProcessChannel(channel, processor, cancellationToken);
add(OnEvent);
_ = channel.ProcessChannel(processor, cancellationToken);

return new DisposableWrapper(async () =>
{
remove(new AsyncEventHandler<TEvent>(OnEvent));
remove(OnEvent);
channel.Writer.Complete();
});
}

private static async Task ProcessChannel<TEvent>(Channel<TEvent> channel, Func<TEvent, CancellationToken, Task> processor, CancellationToken cancellationToken)
public static async Task ProcessChannel<TEvent>(this Channel<TEvent> channel, Func<TEvent, CancellationToken, Task> processor, CancellationToken cancellationToken)
{
while (await channel.Reader.WaitToReadAsync(cancellationToken))
{
Expand Down
Loading

0 comments on commit 701d5d4

Please sign in to comment.