This commit is contained in:
Kieran 2023-05-30 11:24:13 +01:00
parent b214783ac3
commit bfc133bdc9
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
15 changed files with 1141 additions and 86 deletions

View File

@ -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<DownloadController> _logger;
public DownloadController(FileStoreFactory storage, ILogger<DownloadController> 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<bool> IsOrderPaid(string orderId)
private async ValueTask<bool> 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);

View File

@ -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<PaywallCurrency>(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();
}

View File

@ -23,6 +23,8 @@ public class PaywallConfiguration : IEntityTypeConfiguration<Paywall>
builder.HasOne(a => a.File)
.WithOne(a => a.Paywall)
.HasForeignKey<Paywall>();
.HasForeignKey<Paywall>(a => a.FileId);
builder.Property(a => a.Upstream);
}
}

View File

@ -1,19 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace VoidCat.Database.Configurations;
public class PaywallStrikeConfiguration : IEntityTypeConfiguration<PaywallStrike>
{
public void Configure(EntityTypeBuilder<PaywallStrike> 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<PaywallStrike>();
}
}

View File

@ -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!;
/// <summary>
/// Upstream identifier, handle or lnurl
/// </summary>
public string? Upstream { get; init; }
}

View File

@ -0,0 +1,474 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ApiKey", (string)null);
});
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("Code")
.HasColumnType("uuid");
b.Property<DateTime>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("EmailVerification", (string)null);
});
modelBuilder.Entity("VoidCat.Database.File", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("Digest")
.HasColumnType("text");
b.Property<Guid>("EditSecret")
.HasColumnType("uuid");
b.Property<string>("EncryptionParams")
.HasColumnType("text");
b.Property<DateTime?>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<string>("MagnetLink")
.HasColumnType("text");
b.Property<string>("MimeType")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("application/octet-stream");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<decimal>("Size")
.HasColumnType("numeric(20,0)");
b.Property<string>("Storage")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("local-disk");
b.Property<DateTime>("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<Guid>("Id")
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<byte>("Currency")
.HasColumnType("smallint");
b.Property<bool>("Required")
.HasColumnType("boolean");
b.Property<int>("Service")
.HasColumnType("integer");
b.Property<string>("Upstream")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Payment", (string)null);
});
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<byte>("Currency")
.HasColumnType("smallint");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<int>("Service")
.HasColumnType("integer");
b.Property<byte>("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<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime>("Expire")
.HasColumnType("timestamp with time zone");
b.Property<string>("Invoice")
.IsRequired()
.HasColumnType("text");
b.HasKey("OrderId");
b.ToTable("PaymentOrderLightning", (string)null);
});
modelBuilder.Entity("VoidCat.Database.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AuthType")
.HasColumnType("integer");
b.Property<string>("Avatar")
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<string>("DisplayName")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("void user");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Flags")
.HasColumnType("integer");
b.Property<DateTime?>("LastLogin")
.HasColumnType("timestamp with time zone");
b.Property<string>("Password")
.HasColumnType("text");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<string>("IdToken")
.HasColumnType("text");
b.Property<string>("Provider")
.IsRequired()
.HasColumnType("text");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Scope")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TokenType")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UsersAuthToken", (string)null);
});
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.HasKey("UserId", "FileId");
b.HasIndex("FileId")
.IsUnique();
b.ToTable("UserFiles", (string)null);
});
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<string>("Role")
.HasColumnType("text");
b.HasKey("UserId", "Role");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<string>("Names")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("ScanTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Scanner")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("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
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace VoidCat.Migrations
{
/// <inheritdoc />
public partial class SimplifyPaymentOrder : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PaymentStrike");
migrationBuilder.AddColumn<string>(
name: "Upstream",
table: "Payment",
type: "text",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Upstream",
table: "Payment");
migrationBuilder.CreateTable(
name: "PaymentStrike",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Handle = table.Column<string>(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);
});
}
}
}

View File

@ -0,0 +1,481 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("Expiry")
.HasColumnType("timestamp with time zone");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ApiKey", (string)null);
});
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("Code")
.HasColumnType("uuid");
b.Property<DateTime>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("EmailVerification", (string)null);
});
modelBuilder.Entity("VoidCat.Database.File", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("Digest")
.HasColumnType("text");
b.Property<Guid>("EditSecret")
.HasColumnType("uuid");
b.Property<string>("EncryptionParams")
.HasColumnType("text");
b.Property<DateTime?>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<string>("MagnetLink")
.HasColumnType("text");
b.Property<string>("MimeType")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("application/octet-stream");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<decimal>("Size")
.HasColumnType("numeric(20,0)");
b.Property<string>("Storage")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("local-disk");
b.Property<DateTime>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<byte>("Currency")
.HasColumnType("smallint");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<bool>("Required")
.HasColumnType("boolean");
b.Property<int>("Service")
.HasColumnType("integer");
b.Property<string>("Upstream")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("FileId")
.IsUnique();
b.ToTable("Payment", (string)null);
});
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<byte>("Currency")
.HasColumnType("smallint");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<int>("Service")
.HasColumnType("integer");
b.Property<byte>("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<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime>("Expire")
.HasColumnType("timestamp with time zone");
b.Property<string>("Invoice")
.IsRequired()
.HasColumnType("text");
b.HasKey("OrderId");
b.ToTable("PaymentOrderLightning", (string)null);
});
modelBuilder.Entity("VoidCat.Database.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AuthType")
.HasColumnType("integer");
b.Property<string>("Avatar")
.HasColumnType("text");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<string>("DisplayName")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("void user");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Flags")
.HasColumnType("integer");
b.Property<DateTime?>("LastLogin")
.HasColumnType("timestamp with time zone");
b.Property<string>("Password")
.HasColumnType("text");
b.Property<string>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("Expires")
.HasColumnType("timestamp with time zone");
b.Property<string>("IdToken")
.HasColumnType("text");
b.Property<string>("Provider")
.IsRequired()
.HasColumnType("text");
b.Property<string>("RefreshToken")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Scope")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TokenType")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UsersAuthToken", (string)null);
});
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.HasKey("UserId", "FileId");
b.HasIndex("FileId")
.IsUnique();
b.ToTable("UserFiles", (string)null);
});
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<string>("Role")
.HasColumnType("text");
b.HasKey("UserId", "Role");
b.ToTable("UserRoles", (string)null);
});
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<string>("Names")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("ScanTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Scanner")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("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
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace VoidCat.Migrations
{
/// <inheritdoc />
public partial class AddFileId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Payment_Files_Id",
table: "Payment");
migrationBuilder.AddColumn<Guid>(
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);
}
/// <inheritdoc />
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);
}
}
}

View File

@ -125,6 +125,7 @@ namespace VoidCat.Migrations
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
@ -133,14 +134,23 @@ namespace VoidCat.Migrations
b.Property<byte>("Currency")
.HasColumnType("smallint");
b.Property<Guid>("FileId")
.HasColumnType("uuid");
b.Property<bool>("Required")
.HasColumnType("boolean");
b.Property<int>("Service")
.HasColumnType("integer");
b.Property<string>("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<Guid>("Id")
.HasColumnType("uuid");
b.Property<string>("Handle")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("PaymentStrike", (string)null);
});
modelBuilder.Entity("VoidCat.Database.User", b =>
{
b.Property<Guid>("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");

View File

@ -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()

View File

@ -19,7 +19,6 @@ public sealed class PostgresPaymentStore : IPaymentStore
{
return await _db.Paywalls
.AsNoTracking()
.Include(a => a.PaywallStrike)
.SingleOrDefaultAsync(a => a.Id == id);
}

View File

@ -23,7 +23,7 @@ public class StrikePaymentProvider : IPaymentProvider
/// <inheritdoc />
public async ValueTask<PaywallOrder?> 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),

View File

@ -41,11 +41,7 @@ export function Admin() {
]}/>
<h2>Files</h2>
<ImageGrid loadPage={r => AdminApi.adminListFiles(r)} actions={(i) => {
return <td>
<VoidButton onClick={() => deleteFile(i.id)}>Delete</VoidButton>
</td>
}}/>
<ImageGrid loadPage={r => AdminApi.adminListFiles(r)}/>
{editUser &&
<VoidModal title="Edit user">

View File

@ -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<PagedResponse<any>>
}
export default function ImageGrid(props: ImageGridProps) {
const navigate = useNavigate();
const loadPage = props.loadPage;
const actions = props.actions;
const dispatch = useDispatch();
const [files, setFiles] = useState<PagedResponse<VoidFileResponse>>();
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 <h3>Access Denied</h3>
}
return <>
<div className="image-grid">
{files?.results.map(v => <div key={v.id} onClick={() => navigate(`/${v.id}`)}>