From 31144cffa377342fe25c0378251d52451675ec85 Mon Sep 17 00:00:00 2001 From: Kieran Date: Thu, 31 Aug 2023 14:20:01 +0100 Subject: [PATCH] Insert credit payments --- .../Configuration/PaymentsConfiguration.cs | 3 +- NostrStreamer/Database/Payment.cs | 5 +- .../20230831130835_Credits.Designer.cs | 315 ++++++++++++++++++ .../Migrations/20230831130835_Credits.cs | 36 ++ .../Migrations/20230831134200_AddCredits.cs | 22 ++ .../StreamerContextModelSnapshot.cs | 1 - .../Services/Background/LndInvoiceStream.cs | 2 +- NostrStreamer/Services/UserService.cs | 11 + 8 files changed, 389 insertions(+), 6 deletions(-) create mode 100644 NostrStreamer/Migrations/20230831130835_Credits.Designer.cs create mode 100644 NostrStreamer/Migrations/20230831130835_Credits.cs create mode 100644 NostrStreamer/Migrations/20230831134200_AddCredits.cs diff --git a/NostrStreamer/Database/Configuration/PaymentsConfiguration.cs b/NostrStreamer/Database/Configuration/PaymentsConfiguration.cs index 5037b4f..bfb9f0f 100644 --- a/NostrStreamer/Database/Configuration/PaymentsConfiguration.cs +++ b/NostrStreamer/Database/Configuration/PaymentsConfiguration.cs @@ -8,8 +8,7 @@ public class PaymentsConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(a => a.PaymentHash); - builder.Property(a => a.Invoice) - .IsRequired(); + builder.Property(a => a.Invoice); builder.Property(a => a.IsPaid) .IsRequired(); diff --git a/NostrStreamer/Database/Payment.cs b/NostrStreamer/Database/Payment.cs index 1bb7431..1fa75ae 100644 --- a/NostrStreamer/Database/Payment.cs +++ b/NostrStreamer/Database/Payment.cs @@ -7,7 +7,7 @@ public class Payment public string PubKey { get; init; } = null!; public User User { get; init; } = null!; - public string Invoice { get; init; } = null!; + public string? Invoice { get; init; } public bool IsPaid { get; set; } @@ -23,5 +23,6 @@ public class Payment public enum PaymentType { Topup = 0, - Zap = 1 + Zap = 1, + Credit = 2 } \ No newline at end of file diff --git a/NostrStreamer/Migrations/20230831130835_Credits.Designer.cs b/NostrStreamer/Migrations/20230831130835_Credits.Designer.cs new file mode 100644 index 0000000..a30a6ec --- /dev/null +++ b/NostrStreamer/Migrations/20230831130835_Credits.Designer.cs @@ -0,0 +1,315 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NostrStreamer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace NostrStreamer.Migrations +{ + [DbContext(typeof(StreamerContext))] + [Migration("20230831130835_Credits")] + partial class Credits + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("NostrStreamer.Database.IngestEndpoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("App") + .IsRequired() + .HasColumnType("text"); + + b.Property>("Capabilities") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("Cost") + .HasColumnType("integer"); + + b.Property("Forward") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("App") + .IsUnique(); + + b.ToTable("Endpoints"); + }); + + modelBuilder.Entity("NostrStreamer.Database.Payment", b => + { + b.Property("PaymentHash") + .HasColumnType("text"); + + b.Property("Amount") + .HasColumnType("numeric(20,0)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Invoice") + .HasColumnType("text"); + + b.Property("IsPaid") + .HasColumnType("boolean"); + + b.Property("Nostr") + .HasColumnType("text"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("PaymentHash"); + + b.HasIndex("PubKey"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Property("PubKey") + .HasColumnType("text"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("ContentWarning") + .HasColumnType("text"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("StreamKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Summary") + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TosAccepted") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("PubKey"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EdgeIp") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndpointId") + .HasColumnType("uuid"); + + b.Property("Ends") + .HasColumnType("timestamp with time zone"); + + b.Property("Event") + .IsRequired() + .HasColumnType("text"); + + b.Property("ForwardClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastSegment") + .HasColumnType("timestamp with time zone"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Starts") + .HasColumnType("timestamp with time zone"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("StreamId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Thumbnail") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EndpointId"); + + b.HasIndex("PubKey"); + + b.ToTable("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PubKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("Relay") + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("Sig") + .HasColumnType("text"); + + b.Property("StreamId") + .HasColumnType("uuid"); + + b.Property("ZapSplit") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("StreamId", "PubKey") + .IsUnique(); + + b.ToTable("Guests"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Duration") + .HasColumnType("double precision"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserStreamId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserStreamId"); + + b.ToTable("Recordings"); + }); + + modelBuilder.Entity("NostrStreamer.Database.Payment", b => + { + b.HasOne("NostrStreamer.Database.User", "User") + .WithMany("Payments") + .HasForeignKey("PubKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.HasOne("NostrStreamer.Database.IngestEndpoint", "Endpoint") + .WithMany() + .HasForeignKey("EndpointId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NostrStreamer.Database.User", "User") + .WithMany("Streams") + .HasForeignKey("PubKey") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Endpoint"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b => + { + b.HasOne("NostrStreamer.Database.UserStream", "Stream") + .WithMany("Guests") + .HasForeignKey("StreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Stream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b => + { + b.HasOne("NostrStreamer.Database.UserStream", "Stream") + .WithMany() + .HasForeignKey("UserStreamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Stream"); + }); + + modelBuilder.Entity("NostrStreamer.Database.User", b => + { + b.Navigation("Payments"); + + b.Navigation("Streams"); + }); + + modelBuilder.Entity("NostrStreamer.Database.UserStream", b => + { + b.Navigation("Guests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/NostrStreamer/Migrations/20230831130835_Credits.cs b/NostrStreamer/Migrations/20230831130835_Credits.cs new file mode 100644 index 0000000..234e0fc --- /dev/null +++ b/NostrStreamer/Migrations/20230831130835_Credits.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NostrStreamer.Migrations +{ + /// + public partial class Credits : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Invoice", + table: "Payments", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Invoice", + table: "Payments", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + } + } +} diff --git a/NostrStreamer/Migrations/20230831134200_AddCredits.cs b/NostrStreamer/Migrations/20230831134200_AddCredits.cs new file mode 100644 index 0000000..28f42d5 --- /dev/null +++ b/NostrStreamer/Migrations/20230831134200_AddCredits.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using NostrStreamer.Database; + +namespace NostrStreamer.Migrations; + +[DbContext(typeof(StreamerContext))] +[Migration("20230831134200_AddCredits")] +public class AddCredits : Migration { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("create extension if not exists pgcrypto"); + migrationBuilder.Sql(@"insert into ""Payments"" +select ""PubKey"", null, true, encode(digest(concat(""PubKey"", now()::text), 'sha256'), 'hex'), 1000 * 1000, now(), null, 2 from ""Users"""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("delete from \"Payments\" where \"Type\" = 2"); + } +} diff --git a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs index d7fdbfd..a0f4011 100644 --- a/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs +++ b/NostrStreamer/Migrations/StreamerContextModelSnapshot.cs @@ -68,7 +68,6 @@ namespace NostrStreamer.Migrations .HasColumnType("timestamp with time zone"); b.Property("Invoice") - .IsRequired() .HasColumnType("text"); b.Property("IsPaid") diff --git a/NostrStreamer/Services/Background/LndInvoiceStream.cs b/NostrStreamer/Services/Background/LndInvoiceStream.cs index e6886fb..d06b006 100644 --- a/NostrStreamer/Services/Background/LndInvoiceStream.cs +++ b/NostrStreamer/Services/Background/LndInvoiceStream.cs @@ -57,7 +57,7 @@ public class LndInvoicesStream : BackgroundService payment.IsPaid = true; payment.User.Balance += (long)(payment.Amount * 1000L); await db.SaveChangesAsync(stoppingToken); - if (!string.IsNullOrEmpty(payment.Nostr)) + if (!string.IsNullOrEmpty(payment.Nostr) && !string.IsNullOrEmpty(payment.Invoice)) { zapService.HandlePaid(payment.Invoice, payment.Nostr); } diff --git a/NostrStreamer/Services/UserService.cs b/NostrStreamer/Services/UserService.cs index be21dad..0da33a8 100644 --- a/NostrStreamer/Services/UserService.cs +++ b/NostrStreamer/Services/UserService.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; +using System.Text; using Microsoft.EntityFrameworkCore; using Nostr.Client.Utils; using NostrStreamer.ApiModel; @@ -31,6 +33,15 @@ public class UserService }; _db.Users.Add(user); + _db.Payments.Add(new Payment() + { + PubKey = pubkey, + Type = PaymentType.Credit, + IsPaid = true, + Amount = (ulong)user.Balance, + PaymentHash = SHA256.HashData(Encoding.UTF8.GetBytes($"{pubkey}-init-credit")).ToHex() + }); + await _db.SaveChangesAsync(); return user; }