diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs index 87f0bfa..0552a79 100644 --- a/VoidCat/Controllers/DownloadController.cs +++ b/VoidCat/Controllers/DownloadController.cs @@ -100,7 +100,7 @@ public class DownloadController : Controller } // check payment order - if (meta.Payment != default && meta.Payment.Service != PaymentServices.None) + if (meta.Payment != default && meta.Payment.Service != PaymentServices.None && meta.Payment.Required) { var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"]; if (!await IsOrderPaid(orderId)) diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index f981e85..4ccf29c 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -15,7 +15,7 @@ namespace VoidCat.Controllers { private readonly FileStoreFactory _storage; private readonly IFileMetadataStore _metadata; - private readonly IPaymentStore _payment; + private readonly IPaymentStore _paymentStore; private readonly IPaymentFactory _paymentFactory; private readonly FileInfoManager _fileInfo; private readonly IUserUploadsStore _userUploads; @@ -29,7 +29,7 @@ namespace VoidCat.Controllers { _storage = storage; _metadata = metadata; - _payment = payment; + _paymentStore = payment; _paymentFactory = paymentFactory; _fileInfo = fileInfo; _userUploads = userUploads; @@ -214,7 +214,7 @@ namespace VoidCat.Controllers { var gid = id.FromBase58Guid(); var file = await _fileInfo.Get(gid); - var config = await _payment.Get(gid); + var config = await _paymentStore.Get(gid); var provider = await _paymentFactory.CreateProvider(config!.Service); return await provider.CreateOrder(file!.Payment!); @@ -231,7 +231,7 @@ namespace VoidCat.Controllers public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute] Guid order) { var gid = id.FromBase58Guid(); - var config = await _payment.Get(gid); + var config = await _paymentStore.Get(gid); var provider = await _paymentFactory.CreateProvider(config!.Service); return await provider.GetOrderStatus(order); @@ -254,18 +254,19 @@ namespace VoidCat.Controllers if (req.Strike != default) { - await _payment.Add(gid, new StrikePaymentConfig() + await _paymentStore.Add(gid, new StrikePaymentConfig() { Service = PaymentServices.Strike, Handle = req.Strike.Handle, - Cost = req.Strike.Cost + Cost = req.Strike.Cost, + Required = req.Required }); return Ok(); } // if none set, delete config - await _payment.Delete(gid); + await _paymentStore.Delete(gid); return Ok(); } @@ -341,5 +342,7 @@ namespace VoidCat.Controllers public Guid EditSecret { get; init; } public StrikePaymentConfig? Strike { get; init; } + + public bool Required { get; init; } } } diff --git a/VoidCat/Services/Migrations/Database/04-OptionalPayments.cs b/VoidCat/Services/Migrations/Database/04-OptionalPayments.cs new file mode 100644 index 0000000..554f482 --- /dev/null +++ b/VoidCat/Services/Migrations/Database/04-OptionalPayments.cs @@ -0,0 +1,20 @@ +using FluentMigrator; + +namespace VoidCat.Services.Migrations.Database; + +[Migration(20220908_1602)] +public class OptionalPayments : Migration{ + public override void Up() + { + Create.Column("Required") + .OnTable("Payment") + .AsBoolean() + .WithDefaultValue(true); + } + + public override void Down() + { + Delete.Column("Required") + .FromTable("Payment"); + } +} \ No newline at end of file diff --git a/VoidCat/Services/Payment/PostgresPaymentStore.cs b/VoidCat/Services/Payment/PostgresPaymentStore.cs index 75a4793..2715437 100644 --- a/VoidCat/Services/Payment/PostgresPaymentStore.cs +++ b/VoidCat/Services/Payment/PostgresPaymentStore.cs @@ -18,11 +18,11 @@ public sealed class PostgresPaymentStore : IPaymentStore public async ValueTask Get(Guid id) { await using var conn = await _connection.Get(); - var svc = await conn.QuerySingleOrDefaultAsync( + var dto = await conn.QuerySingleOrDefaultAsync( @"select * from ""Payment"" where ""File"" = :file", new {file = id}); - if (svc != default) + if (dto != default) { - switch (svc.Service) + switch (dto.Service) { case PaymentServices.Strike: { @@ -31,10 +31,11 @@ public sealed class PostgresPaymentStore : IPaymentStore @"select ""Handle"" from ""PaymentStrike"" where ""File"" = :file", new {file = id}); return new StrikePaymentConfig { - Cost = new(svc.Amount, svc.Currency), - File = svc.File, + Cost = new(dto.Amount, dto.Currency), + File = dto.File, Handle = handle, - Service = PaymentServices.Strike + Service = PaymentServices.Strike, + Required = dto.Required }; } } @@ -55,14 +56,15 @@ public sealed class PostgresPaymentStore : IPaymentStore await using var conn = await _connection.Get(); await using var txn = await conn.BeginTransactionAsync(); await conn.ExecuteAsync( - @"insert into ""Payment""(""File"", ""Service"", ""Amount"", ""Currency"") values(:file, :service, :amount, :currency) -on conflict(""File"") do update set ""Service"" = :service, ""Amount"" = :amount, ""Currency"" = :currency", + @"insert into ""Payment""(""File"", ""Service"", ""Amount"", ""Currency"", ""Required"") values(:file, :service, :amount, :currency, :required) +on conflict(""File"") do update set ""Service"" = :service, ""Amount"" = :amount, ""Currency"" = :currency, ""Required"" = :required", new { file = id, service = (int)obj.Service, amount = obj.Cost.Amount, - currency = obj.Cost.Currency + currency = obj.Cost.Currency, + required = obj.Required }); if (obj is StrikePaymentConfig sc) diff --git a/VoidCat/spa/src/FileEdit.js b/VoidCat/spa/src/FileEdit.js index d3657cd..6386711 100644 --- a/VoidCat/spa/src/FileEdit.js +++ b/VoidCat/spa/src/FileEdit.js @@ -7,6 +7,7 @@ import "./FileEdit.css"; import {useSelector} from "react-redux"; import {VoidButton} from "./VoidButton"; import moment from "moment"; +import {PaymentServices} from "./Const"; export function FileEdit(props) { const {Api} = useApi(); @@ -42,10 +43,10 @@ export function FileEdit(props) { function renderPaymentConfig() { switch (payment) { - case 0: { + case PaymentServices.None: { return ; } - case 1: { + case PaymentServices.Strike: { return } } diff --git a/VoidCat/spa/src/FilePayment.js b/VoidCat/spa/src/FilePayment.js index 981d249..aad70a5 100644 --- a/VoidCat/spa/src/FilePayment.js +++ b/VoidCat/spa/src/FilePayment.js @@ -34,14 +34,20 @@ export function FilePayment(props) { } if (!order) { - return ( -
-

- You must pay {FormatCurrency(pw.cost.amount, pw.cost.currency)} to view this file. -

- Pay -
- ); + if (pw.required) { + return ( +
+

+ You must pay {FormatCurrency(pw.cost.amount, pw.cost.currency)} to view this file. +

+ Pay +
+ ); + } else { + return ( + Tip {FormatCurrency(pw.cost.amount, pw.cost.currency)} + ); + } } else { switch (pw.service) { case PaymentServices.Strike: { diff --git a/VoidCat/spa/src/FilePreview.js b/VoidCat/spa/src/FilePreview.js index 4405661..cb59fdd 100644 --- a/VoidCat/spa/src/FilePreview.js +++ b/VoidCat/spa/src/FilePreview.js @@ -26,13 +26,24 @@ export function FilePreview() { } } - function renderTypes() { + function canAccessFile() { + if (info?.payment?.required === true && !order) { + return false; + } + return true; + } + + function renderPayment() { if (info.payment && info.payment.service !== 0) { if (!order) { return ; } } + return null; + } + + function renderPreview() { if (info.metadata) { switch (info.metadata.mimeType) { case "image/avif": @@ -109,7 +120,7 @@ export function FilePreview() { } function renderVirusWarning() { - if(info.virusScan && info.virusScan.isVirus === true) { + if (info.virusScan && info.virusScan.isVirus === true) { let scanResult = info.virusScan; return (
@@ -122,9 +133,9 @@ export function FilePreview() {
); - } + } } - + useEffect(() => { loadInfo(); }, []); @@ -149,7 +160,8 @@ export function FilePreview() { void.cat - {info.metadata?.name ?? info.id} - {info.metadata?.description ? : null} + {info.metadata?.description ? + : null} {renderOpenGraphTags()} {renderVirusWarning()} @@ -158,10 +170,13 @@ export function FilePreview() { {info.uploader ? : null}
- Download + {canAccessFile() ? + Download : null}
- {renderTypes()} + {renderPayment()} + {canAccessFile() ? renderPreview() : null}
diff --git a/VoidCat/spa/src/StrikePaymentConfig.js b/VoidCat/spa/src/StrikePaymentConfig.js index f2f016c..5cda2f2 100644 --- a/VoidCat/spa/src/StrikePaymentConfig.js +++ b/VoidCat/spa/src/StrikePaymentConfig.js @@ -13,6 +13,7 @@ export function StrikePaymentConfig(props) { const [username, setUsername] = useState(payment?.handle ?? "hrf"); const [currency, setCurrency] = useState(payment?.cost.currency ?? PaymentCurrencies.USD); const [price, setPrice] = useState(payment?.cost.amount ?? 1); + const [required, setRequired] = useState(payment?.required); const [saveStatus, setSaveStatus] = useState(); async function saveStrikeConfig(e) { @@ -24,7 +25,8 @@ export function StrikePaymentConfig(props) { currency: currency, amount: price } - } + }, + required }; if (typeof onSaveConfig === "function") { @@ -53,6 +55,8 @@ export function StrikePaymentConfig(props) {
Price:
setPrice(parseFloat(e.target.value))}/>
+
Required:
+
setRequired(e.target.checked)}/>
Save {saveStatus ? : null} diff --git a/VoidCat/spa/src/index.css b/VoidCat/spa/src/index.css index 99ae936..d048818 100644 --- a/VoidCat/spa/src/index.css +++ b/VoidCat/spa/src/index.css @@ -60,7 +60,7 @@ a:hover { align-items: center; } -input[type="text"], input[type="number"], input[type="password"], input[type="datetime-local"], select { +input[type="text"], input[type="number"], input[type="password"], input[type="datetime-local"], input[type="checkbox"], select { display: inline-block; line-height: 1.1; border-radius: 10px; @@ -69,6 +69,11 @@ input[type="text"], input[type="number"], input[type="password"], input[type="da border: 0; } +input[type="checkbox"] { + width: 20px; + height: 20px; +} + table { width: 100%; word-break: keep-all;