From bfc133bdc92794473f51aa1f5b4dc316edde64ea Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 30 May 2023 11:24:13 +0100 Subject: [PATCH] L402 --- VoidCat/Controllers/DownloadController.cs | 33 +- VoidCat/Controllers/UploadController.cs | 19 +- .../Configurations/PaywallConfiguration.cs | 4 +- .../PaywallStrikeConfiguration.cs | 19 - VoidCat/Database/Paywall.cs | 13 +- ...529211008_SimplifyPaymentOrder.Designer.cs | 474 +++++++++++++++++ .../20230529211008_SimplifyPaymentOrder.cs | 50 ++ .../20230529211453_AddFileId.Designer.cs | 481 ++++++++++++++++++ .../Migrations/20230529211453_AddFileId.cs | 64 +++ .../Migrations/VoidContextModelSnapshot.cs | 42 +- VoidCat/Services/Files/FileInfoManager.cs | 3 +- .../Services/Payment/PostgresPaymentStore.cs | 1 - .../Services/Payment/StrikePaymentProvider.cs | 6 +- VoidCat/spa/src/app/src/Admin/Admin.tsx | 6 +- .../app/src/Components/Shared/ImageGrid.tsx | 12 +- 15 files changed, 1141 insertions(+), 86 deletions(-) delete mode 100644 VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs create mode 100644 VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.Designer.cs create mode 100644 VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.cs create mode 100644 VoidCat/Migrations/20230529211453_AddFileId.Designer.cs create mode 100644 VoidCat/Migrations/20230529211453_AddFileId.cs diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs index 7c40045..f68d249 100644 --- a/VoidCat/Controllers/DownloadController.cs +++ b/VoidCat/Controllers/DownloadController.cs @@ -15,16 +15,18 @@ public class DownloadController : Controller private readonly FileStoreFactory _storage; private readonly FileInfoManager _fileInfo; private readonly IPaymentOrderStore _paymentOrders; + private readonly IPaymentFactory _paymentFactory; private readonly ILogger _logger; public DownloadController(FileStoreFactory storage, ILogger logger, FileInfoManager fileInfo, - IPaymentOrderStore paymentOrderStore, VoidSettings settings) + IPaymentOrderStore paymentOrderStore, VoidSettings settings, IPaymentFactory paymentFactory) { _storage = storage; _logger = logger; _fileInfo = fileInfo; _paymentOrders = paymentOrderStore; _settings = settings; + _paymentFactory = paymentFactory; } [HttpOptions] @@ -119,10 +121,30 @@ public class DownloadController : Controller // check payment order if (meta.Payment != default && meta.Payment.Service != PaywallService.None && meta.Payment.Required) { - var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"]; + var h402 = Request.Headers.FirstOrDefault(a => a.Key.Equals("Authorization", StringComparison.InvariantCultureIgnoreCase)) + .Value.FirstOrDefault(a => a?.StartsWith("L402") ?? false); + + var orderId = Request.Headers.GetHeader("V-OrderId") ?? h402 ?? Request.Query["orderId"]; if (!await IsOrderPaid(orderId!)) { + Response.Headers.CacheControl = "no-cache"; Response.StatusCode = (int)HttpStatusCode.PaymentRequired; + if (meta.Payment.Service is PaywallService.Strike or PaywallService.LnProxy) + { + var accept = Request.Headers.GetHeader("accept"); + if (accept == "L402") + { + var provider = await _paymentFactory.CreateProvider(meta.Payment.Service); + var order = await provider.CreateOrder(meta.Payment!); + if (order != default) + { + Response.Headers.Add("access-control-expose-headers", "www-authenticate"); + Response.Headers.Add("www-authenticate", + $"L402 macaroon=\"{Convert.ToBase64String(order.Id.ToByteArray())}\", invoice=\"{order!.OrderLightning!.Invoice}\""); + } + } + } + return default; } } @@ -146,8 +168,13 @@ public class DownloadController : Controller return meta; } - private async ValueTask IsOrderPaid(string orderId) + private async ValueTask IsOrderPaid(string? orderId) { + if (orderId?.StartsWith("L402") ?? false) + { + orderId = new Guid(Convert.FromBase64String(orderId.Substring(5).Split(":")[0])).ToString(); + } + if (Guid.TryParse(orderId, out var oid)) { var order = await _paymentOrders.Get(oid); diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 2b9ebd7..c79e242 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -282,15 +282,15 @@ namespace VoidCat.Controllers if (req.StrikeHandle != default) { - await _paymentStore.Delete(gid); + if (meta.Paywall?.Id != default) + { + await _paymentStore.Delete(meta.Paywall.Id); + } await _paymentStore.Add(gid, new Paywall { - File = meta, + FileId = meta.Id, Service = PaywallService.Strike, - PaywallStrike = new() - { - Handle = req.StrikeHandle - }, + Upstream = req.StrikeHandle, Amount = req.Amount, Currency = Enum.Parse(req.Currency), Required = req.Required @@ -299,8 +299,11 @@ namespace VoidCat.Controllers return Ok(); } - // if none set, delete config - await _paymentStore.Delete(gid); + // if none set, delete existing config + if (meta.Paywall?.Id != default) + { + await _paymentStore.Delete(meta.Paywall.Id); + } return Ok(); } diff --git a/VoidCat/Database/Configurations/PaywallConfiguration.cs b/VoidCat/Database/Configurations/PaywallConfiguration.cs index 4aeae84..e3aee55 100644 --- a/VoidCat/Database/Configurations/PaywallConfiguration.cs +++ b/VoidCat/Database/Configurations/PaywallConfiguration.cs @@ -23,6 +23,8 @@ public class PaywallConfiguration : IEntityTypeConfiguration builder.HasOne(a => a.File) .WithOne(a => a.Paywall) - .HasForeignKey(); + .HasForeignKey(a => a.FileId); + + builder.Property(a => a.Upstream); } } diff --git a/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs b/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs deleted file mode 100644 index 4ec465f..0000000 --- a/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace VoidCat.Database.Configurations; - -public class PaywallStrikeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("PaymentStrike"); - builder.HasKey(a => a.Id); - builder.Property(a => a.Handle) - .IsRequired(); - - builder.HasOne(a => a.Paywall) - .WithOne(a => a.PaywallStrike) - .HasForeignKey(); - } -} diff --git a/VoidCat/Database/Paywall.cs b/VoidCat/Database/Paywall.cs index b696acf..5626077 100644 --- a/VoidCat/Database/Paywall.cs +++ b/VoidCat/Database/Paywall.cs @@ -29,6 +29,7 @@ public enum PaywallService public class Paywall { public Guid Id { get; init; } = Guid.NewGuid(); + public Guid FileId { get; init; } public File File { get; init; } = null!; public PaywallService Service { get; init; } public PaywallCurrency Currency { get; init; } @@ -36,12 +37,8 @@ public class Paywall public bool Required { get; init; } = true; - public PaywallStrike? PaywallStrike { get; init; } -} - -public class PaywallStrike -{ - public Guid Id { get; init; } = Guid.NewGuid(); - public Paywall Paywall { get; init; } = null!; - public string Handle { get; init; } = null!; + /// + /// Upstream identifier, handle or lnurl + /// + public string? Upstream { get; init; } } \ No newline at end of file diff --git a/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.Designer.cs b/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.Designer.cs new file mode 100644 index 0000000..cfd46b3 --- /dev/null +++ b/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.Designer.cs @@ -0,0 +1,474 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using VoidCat.Services; + +#nullable disable + +namespace VoidCat.Migrations +{ + [DbContext(typeof(VoidContext))] + [Migration("20230529211008_SimplifyPaymentOrder")] + partial class SimplifyPaymentOrder + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("VoidCat.Database.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.EmailVerification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .HasColumnType("uuid"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("EmailVerification", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Digest") + .HasColumnType("text"); + + b.Property("EditSecret") + .HasColumnType("uuid"); + + b.Property("EncryptionParams") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("MagnetLink") + .HasColumnType("text"); + + b.Property("MimeType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("application/octet-stream"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("numeric(20,0)"); + + b.Property("Storage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("local-disk"); + + b.Property("Uploaded") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Uploaded"); + + b.ToTable("Files", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.Paywall", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("Currency") + .HasColumnType("smallint"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("Service") + .HasColumnType("integer"); + + b.Property("Upstream") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Payment", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("Currency") + .HasColumnType("smallint"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Service") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("Status"); + + b.ToTable("PaymentOrder", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b => + { + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Expire") + .HasColumnType("timestamp with time zone"); + + b.Property("Invoice") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("OrderId"); + + b.ToTable("PaymentOrderLightning", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthType") + .HasColumnType("integer"); + + b.Property("Avatar") + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("void user"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Flags") + .HasColumnType("integer"); + + b.Property("LastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("Password") + .HasColumnType("text"); + + b.Property("Storage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("local-disk"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserAuthToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("IdToken") + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UsersAuthToken", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserFile", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "FileId"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("UserFiles", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("text"); + + b.HasKey("UserId", "Role"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.VirusScanResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Names") + .IsRequired() + .HasColumnType("text"); + + b.Property("ScanTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Scanner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Score") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.ToTable("VirusScanResult", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.ApiKey", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.EmailVerification", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.Paywall", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithOne("Paywall") + .HasForeignKey("VoidCat.Database.Paywall", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b => + { + b.HasOne("VoidCat.Database.PaywallOrder", "Order") + .WithOne("OrderLightning") + .HasForeignKey("VoidCat.Database.PaywallOrderLightning", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("VoidCat.Database.UserAuthToken", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.UserFile", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithOne() + .HasForeignKey("VoidCat.Database.UserFile", "FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("VoidCat.Database.User", "User") + .WithMany("UserFiles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.UserRole", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.VirusScanResult", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.File", b => + { + b.Navigation("Paywall"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.Navigation("OrderLightning"); + }); + + modelBuilder.Entity("VoidCat.Database.User", b => + { + b.Navigation("Roles"); + + b.Navigation("UserFiles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.cs b/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.cs new file mode 100644 index 0000000..a4dd33d --- /dev/null +++ b/VoidCat/Migrations/20230529211008_SimplifyPaymentOrder.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace VoidCat.Migrations +{ + /// + public partial class SimplifyPaymentOrder : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PaymentStrike"); + + migrationBuilder.AddColumn( + name: "Upstream", + table: "Payment", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Upstream", + table: "Payment"); + + migrationBuilder.CreateTable( + name: "PaymentStrike", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Handle = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PaymentStrike", x => x.Id); + table.ForeignKey( + name: "FK_PaymentStrike_Payment_Id", + column: x => x.Id, + principalTable: "Payment", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + } +} diff --git a/VoidCat/Migrations/20230529211453_AddFileId.Designer.cs b/VoidCat/Migrations/20230529211453_AddFileId.Designer.cs new file mode 100644 index 0000000..fb5e8d7 --- /dev/null +++ b/VoidCat/Migrations/20230529211453_AddFileId.Designer.cs @@ -0,0 +1,481 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using VoidCat.Services; + +#nullable disable + +namespace VoidCat.Migrations +{ + [DbContext(typeof(VoidContext))] + [Migration("20230529211453_AddFileId")] + partial class AddFileId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("VoidCat.Database.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.EmailVerification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .HasColumnType("uuid"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("EmailVerification", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Digest") + .HasColumnType("text"); + + b.Property("EditSecret") + .HasColumnType("uuid"); + + b.Property("EncryptionParams") + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("MagnetLink") + .HasColumnType("text"); + + b.Property("MimeType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("application/octet-stream"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("numeric(20,0)"); + + b.Property("Storage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("local-disk"); + + b.Property("Uploaded") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Uploaded"); + + b.ToTable("Files", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.Paywall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("Currency") + .HasColumnType("smallint"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("Service") + .HasColumnType("integer"); + + b.Property("Upstream") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Payment", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("Currency") + .HasColumnType("smallint"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Service") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("Status"); + + b.ToTable("PaymentOrder", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b => + { + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Expire") + .HasColumnType("timestamp with time zone"); + + b.Property("Invoice") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("OrderId"); + + b.ToTable("PaymentOrderLightning", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthType") + .HasColumnType("integer"); + + b.Property("Avatar") + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("void user"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Flags") + .HasColumnType("integer"); + + b.Property("LastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("Password") + .HasColumnType("text"); + + b.Property("Storage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("local-disk"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserAuthToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("IdToken") + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UsersAuthToken", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserFile", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "FileId"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("UserFiles", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.UserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("text"); + + b.HasKey("UserId", "Role"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.VirusScanResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Names") + .IsRequired() + .HasColumnType("text"); + + b.Property("ScanTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Scanner") + .IsRequired() + .HasColumnType("text"); + + b.Property("Score") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.ToTable("VirusScanResult", (string)null); + }); + + modelBuilder.Entity("VoidCat.Database.ApiKey", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.EmailVerification", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.Paywall", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithOne("Paywall") + .HasForeignKey("VoidCat.Database.Paywall", "FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b => + { + b.HasOne("VoidCat.Database.PaywallOrder", "Order") + .WithOne("OrderLightning") + .HasForeignKey("VoidCat.Database.PaywallOrderLightning", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("VoidCat.Database.UserAuthToken", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.UserFile", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithOne() + .HasForeignKey("VoidCat.Database.UserFile", "FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("VoidCat.Database.User", "User") + .WithMany("UserFiles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.UserRole", b => + { + b.HasOne("VoidCat.Database.User", "User") + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("VoidCat.Database.VirusScanResult", b => + { + b.HasOne("VoidCat.Database.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("File"); + }); + + modelBuilder.Entity("VoidCat.Database.File", b => + { + b.Navigation("Paywall"); + }); + + modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => + { + b.Navigation("OrderLightning"); + }); + + modelBuilder.Entity("VoidCat.Database.User", b => + { + b.Navigation("Roles"); + + b.Navigation("UserFiles"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/VoidCat/Migrations/20230529211453_AddFileId.cs b/VoidCat/Migrations/20230529211453_AddFileId.cs new file mode 100644 index 0000000..bb9860b --- /dev/null +++ b/VoidCat/Migrations/20230529211453_AddFileId.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace VoidCat.Migrations +{ + /// + public partial class AddFileId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Payment_Files_Id", + table: "Payment"); + + migrationBuilder.AddColumn( + name: "FileId", + table: "Payment", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_Payment_FileId", + table: "Payment", + column: "FileId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Payment_Files_FileId", + table: "Payment", + column: "FileId", + principalTable: "Files", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Payment_Files_FileId", + table: "Payment"); + + migrationBuilder.DropIndex( + name: "IX_Payment_FileId", + table: "Payment"); + + migrationBuilder.DropColumn( + name: "FileId", + table: "Payment"); + + migrationBuilder.AddForeignKey( + name: "FK_Payment_Files_Id", + table: "Payment", + column: "Id", + principalTable: "Files", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/VoidCat/Migrations/VoidContextModelSnapshot.cs b/VoidCat/Migrations/VoidContextModelSnapshot.cs index 32e384f..c0d2a6e 100644 --- a/VoidCat/Migrations/VoidContextModelSnapshot.cs +++ b/VoidCat/Migrations/VoidContextModelSnapshot.cs @@ -125,6 +125,7 @@ namespace VoidCat.Migrations modelBuilder.Entity("VoidCat.Database.Paywall", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uuid"); b.Property("Amount") @@ -133,14 +134,23 @@ namespace VoidCat.Migrations b.Property("Currency") .HasColumnType("smallint"); + b.Property("FileId") + .HasColumnType("uuid"); + b.Property("Required") .HasColumnType("boolean"); b.Property("Service") .HasColumnType("integer"); + b.Property("Upstream") + .HasColumnType("text"); + b.HasKey("Id"); + b.HasIndex("FileId") + .IsUnique(); + b.ToTable("Payment", (string)null); }); @@ -191,20 +201,6 @@ namespace VoidCat.Migrations b.ToTable("PaymentOrderLightning", (string)null); }); - modelBuilder.Entity("VoidCat.Database.PaywallStrike", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Handle") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PaymentStrike", (string)null); - }); - modelBuilder.Entity("VoidCat.Database.User", b => { b.Property("Id") @@ -379,7 +375,7 @@ namespace VoidCat.Migrations { b.HasOne("VoidCat.Database.File", "File") .WithOne("Paywall") - .HasForeignKey("VoidCat.Database.Paywall", "Id") + .HasForeignKey("VoidCat.Database.Paywall", "FileId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -408,17 +404,6 @@ namespace VoidCat.Migrations b.Navigation("Order"); }); - modelBuilder.Entity("VoidCat.Database.PaywallStrike", b => - { - b.HasOne("VoidCat.Database.Paywall", "Paywall") - .WithOne("PaywallStrike") - .HasForeignKey("VoidCat.Database.PaywallStrike", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Paywall"); - }); - modelBuilder.Entity("VoidCat.Database.UserAuthToken", b => { b.HasOne("VoidCat.Database.User", "User") @@ -476,11 +461,6 @@ namespace VoidCat.Migrations b.Navigation("Paywall"); }); - modelBuilder.Entity("VoidCat.Database.Paywall", b => - { - b.Navigation("PaywallStrike"); - }); - modelBuilder.Entity("VoidCat.Database.PaywallOrder", b => { b.Navigation("OrderLightning"); diff --git a/VoidCat/Services/Files/FileInfoManager.cs b/VoidCat/Services/Files/FileInfoManager.cs index 48eefe1..143a866 100644 --- a/VoidCat/Services/Files/FileInfoManager.cs +++ b/VoidCat/Services/Files/FileInfoManager.cs @@ -40,7 +40,6 @@ public sealed class FileInfoManager var meta = await _metadataStore.Get(id); if (meta == default) return default; - var payment = await _paymentStore.Get(id); var bandwidth = await _statsReporter.GetBandwidth(id); var virusScan = await _virusScanStore.GetByFile(id); var uploader = await _userUploadsStore.Uploader(id); @@ -51,7 +50,7 @@ public sealed class FileInfoManager { Id = id, Metadata = meta.ToMeta(withEditSecret), - Payment = payment, + Payment = meta.Paywall, Bandwidth = bandwidth, Uploader = user?.Flags.HasFlag(UserFlags.PublicProfile) == true || withEditSecret ? user?.ToApiUser(false) : null, VirusScan = virusScan?.ToVirusStatus() diff --git a/VoidCat/Services/Payment/PostgresPaymentStore.cs b/VoidCat/Services/Payment/PostgresPaymentStore.cs index 5cae360..2f1d9de 100644 --- a/VoidCat/Services/Payment/PostgresPaymentStore.cs +++ b/VoidCat/Services/Payment/PostgresPaymentStore.cs @@ -19,7 +19,6 @@ public sealed class PostgresPaymentStore : IPaymentStore { return await _db.Paywalls .AsNoTracking() - .Include(a => a.PaywallStrike) .SingleOrDefaultAsync(a => a.Id == id); } diff --git a/VoidCat/Services/Payment/StrikePaymentProvider.cs b/VoidCat/Services/Payment/StrikePaymentProvider.cs index 668194e..27ef0cd 100644 --- a/VoidCat/Services/Payment/StrikePaymentProvider.cs +++ b/VoidCat/Services/Payment/StrikePaymentProvider.cs @@ -23,7 +23,7 @@ public class StrikePaymentProvider : IPaymentProvider /// public async ValueTask CreateOrder(Paywall config) { - if (config.Service != PaywallService.Strike || config.PaywallStrike == default) + if (config.Service != PaywallService.Strike || config.Upstream == default) { throw new InvalidOperationException("Paywall config is not Strike"); } @@ -34,7 +34,7 @@ public class StrikePaymentProvider : IPaymentProvider if (currency == Currencies.USD) { // map USD to USDT if USD is not available and USDT is - var profile = await _strike.GetProfile(config.PaywallStrike!.Handle); + var profile = await _strike.GetProfile(config.Upstream); if (profile != default) { var usd = profile.Currencies.FirstOrDefault(a => a.Currency == Currencies.USD); @@ -48,7 +48,7 @@ public class StrikePaymentProvider : IPaymentProvider var invoice = await _strike.GenerateInvoice(new() { - Handle = config.PaywallStrike.Handle, + Handle = config.Upstream, Amount = new() { Amount = config.Amount.ToString(CultureInfo.InvariantCulture), diff --git a/VoidCat/spa/src/app/src/Admin/Admin.tsx b/VoidCat/spa/src/app/src/Admin/Admin.tsx index 112583a..2661da9 100644 --- a/VoidCat/spa/src/app/src/Admin/Admin.tsx +++ b/VoidCat/spa/src/app/src/Admin/Admin.tsx @@ -41,11 +41,7 @@ export function Admin() { ]}/>

Files

- AdminApi.adminListFiles(r)} actions={(i) => { - return - deleteFile(i.id)}>Delete - - }}/> + AdminApi.adminListFiles(r)}/> {editUser && diff --git a/VoidCat/spa/src/app/src/Components/Shared/ImageGrid.tsx b/VoidCat/spa/src/app/src/Components/Shared/ImageGrid.tsx index 1eda509..112b151 100644 --- a/VoidCat/spa/src/app/src/Components/Shared/ImageGrid.tsx +++ b/VoidCat/spa/src/app/src/Components/Shared/ImageGrid.tsx @@ -1,21 +1,19 @@ import "./ImageGrid.css"; import {ApiError, PagedRequest, PagedResponse, PagedSortBy, PageSortOrder, VoidFileResponse} from "@void-cat/api"; -import {ReactNode, useEffect, useState} from "react"; +import {useEffect, useState} from "react"; import {useDispatch} from "react-redux"; import {logout} from "../../LoginState"; import {PageSelector} from "./PageSelector"; import {useNavigate} from "react-router-dom"; interface ImageGridProps { - actions?: (f: VoidFileResponse) => ReactNode loadPage: (req: PagedRequest) => Promise> } export default function ImageGrid(props: ImageGridProps) { const navigate = useNavigate(); const loadPage = props.loadPage; - const actions = props.actions; const dispatch = useDispatch(); const [files, setFiles] = useState>(); const [page, setPage] = useState(0); @@ -50,7 +48,7 @@ export default function ImageGrid(props: ImageGridProps) { function renderPreview(info: VoidFileResponse) { const link = `/d/${info.id}`; - + if (info.metadata) { switch (info.metadata.mimeType) { case "image/avif": @@ -90,7 +88,11 @@ export default function ImageGrid(props: ImageGridProps) { } } } - + + if (accessDenied) { + return

Access Denied

+ } + return <>
{files?.results.map(v =>
navigate(`/${v.id}`)}>