Add updated image upload support on Crowdfund plugin (#6254)

* Add updated image upload support on Crowdfund plugin

* Refactor crowdfund image upload fix

* update crowdfund url for greenfield api

* Resolve integration test assertion

* Remove superfluous and unused command argument

* Fix missing validation error

* Minor API controller update

* Property and usage fixes

* Fix test after merge

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
Chukwuleta Tobechi
2024-10-03 02:39:41 +01:00
committed by GitHub
parent 8f062f918b
commit 3a71c45a89
10 changed files with 168 additions and 28 deletions

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using BTCPayServer.Abstractions.Models;
namespace BTCPayServer.Abstractions.Contracts; namespace BTCPayServer.Abstractions.Contracts;
@@ -14,4 +15,5 @@ public interface IFileService
Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry, Task<string?> GetTemporaryFileUrl(Uri baseUri, string fileId, DateTimeOffset expiry,
bool isDownload); bool isDownload);
Task RemoveFile(string fileId, string userId); Task RemoveFile(string fileId, string userId);
Task<UploadImageResultModel> UploadImage(IFormFile file, string userId, long maxFileSizeInBytes = 1_000_000);
} }

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Abstractions.Models;
public class UploadImageResultModel
{
public bool Success { get; set; }
public string Response { get; set; } = string.Empty;
public IStoredFile? StoredFile { get; set; }
}

View File

@@ -81,7 +81,7 @@ namespace BTCPayServer.Tests
Assert.False(app.Archived); Assert.False(app.Archived);
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>(); var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
Assert.IsType<ViewResult>(await crowdfund.ViewCrowdfund(app.Id)); Assert.IsType<ViewResult>(await crowdfund.ViewCrowdfund(app.Id));
// Delete // Delete
Assert.IsType<NotFoundResult>(apps2.DeleteApp(app.Id)); Assert.IsType<NotFoundResult>(apps2.DeleteApp(app.Id));
@@ -121,7 +121,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.Enabled = false; crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null; crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>(); var anonAppPubsController = tester.PayTester.GetController<UICrowdfundController>();
var crowdfundController = user.GetController<UICrowdfundController>(); var crowdfundController = user.GetController<UICrowdfundController>();
@@ -146,7 +146,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2); crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
@@ -157,7 +157,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1); crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
@@ -170,7 +170,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1; crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true; crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(1.01) Amount = new decimal(1.01)
@@ -214,7 +214,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true; crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true; crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
var publicApps = user.GetController<UICrowdfundController>(); var publicApps = user.GetController<UICrowdfundController>();
@@ -268,7 +268,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags); Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false; crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged"); TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
@@ -287,7 +287,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted"); TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false; crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true; crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{ {
Buyer = new Buyer { email = "test@fwf.com" }, Buyer = new Buyer { email = "test@fwf.com" },
@@ -356,7 +356,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.FormId = lstForms[0].Id; crowdfundViewModel.FormId = lstForms[0].Id;
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01).AssertViewModelAsync<FormViewModel>(); var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01).AssertViewModelAsync<FormViewModel>();
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "", vm2); var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "", vm2);
@@ -411,7 +411,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = true; crowdfundViewModel.Enabled = true;
crowdfundViewModel.PerksTemplate = "[{\"id\": \"xxx\",\"title\": \"Perk 1\",\"priceType\": \"Fixed\",\"price\": \"0.001\",\"image\": \"\",\"description\": \"\",\"categories\": [],\"disabled\": false}]"; crowdfundViewModel.PerksTemplate = "[{\"id\": \"xxx\",\"title\": \"Perk 1\",\"priceType\": \"Fixed\",\"price\": \"0.001\",\"image\": \"\",\"description\": \"\",\"categories\": [],\"disabled\": false}]";
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel).Result);
var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01, "xxx").AssertViewModelAsync<FormViewModel>(); var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01, "xxx").AssertViewModelAsync<FormViewModel>();
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "xxx", vm2); var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "xxx", vm2);

View File

@@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Plugins.Crowdfund; using BTCPayServer.Plugins.Crowdfund;
using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
@@ -28,12 +29,14 @@ namespace BTCPayServer.Controllers.Greenfield
public class GreenfieldAppsController : ControllerBase public class GreenfieldAppsController : ControllerBase
{ {
private readonly AppService _appService; private readonly AppService _appService;
private readonly UriResolver _uriResolver;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly CurrencyNameTable _currencies; private readonly CurrencyNameTable _currencies;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
public GreenfieldAppsController( public GreenfieldAppsController(
AppService appService, AppService appService,
UriResolver uriResolver,
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
CurrencyNameTable currencies, CurrencyNameTable currencies,
@@ -41,6 +44,7 @@ namespace BTCPayServer.Controllers.Greenfield
) )
{ {
_appService = appService; _appService = appService;
_uriResolver = uriResolver;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_currencies = currencies; _currencies = currencies;
_userManager = userManager; _userManager = userManager;
@@ -72,12 +76,12 @@ namespace BTCPayServer.Controllers.Greenfield
Archived = request.Archived ?? false Archived = request.Archived ?? false
}; };
var settings = ToCrowdfundSettings(request, new CrowdfundSettings { Title = request.Title ?? request.AppName }); var settings = ToCrowdfundSettings(request);
appData.SetSettings(settings); appData.SetSettings(settings);
await _appService.UpdateOrCreateApp(appData); await _appService.UpdateOrCreateApp(appData);
var model = await ToCrowdfundModel(appData);
return Ok(ToCrowdfundModel(appData)); return Ok(model);
} }
[HttpPost("~/api/v1/stores/{storeId}/apps/pos")] [HttpPost("~/api/v1/stores/{storeId}/apps/pos")]
@@ -208,7 +212,8 @@ namespace BTCPayServer.Controllers.Greenfield
return AppNotFound(); return AppNotFound();
} }
return Ok(ToCrowdfundModel(app)); var model = await ToCrowdfundModel(app);
return Ok(model);
} }
[HttpDelete("~/api/v1/apps/{appId}")] [HttpDelete("~/api/v1/apps/{appId}")]
@@ -255,7 +260,7 @@ namespace BTCPayServer.Controllers.Greenfield
return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found"); return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found");
} }
private CrowdfundSettings ToCrowdfundSettings(CrowdfundAppRequest request, CrowdfundSettings settings) private CrowdfundSettings ToCrowdfundSettings(CrowdfundAppRequest request)
{ {
var parsedSounds = ValidateStringArray(request.Sounds); var parsedSounds = ValidateStringArray(request.Sounds);
var parsedColors = ValidateStringArray(request.AnimationColors); var parsedColors = ValidateStringArray(request.AnimationColors);
@@ -271,7 +276,7 @@ namespace BTCPayServer.Controllers.Greenfield
Description = request.Description?.Trim(), Description = request.Description?.Trim(),
EndDate = request.EndDate?.UtcDateTime, EndDate = request.EndDate?.UtcDateTime,
TargetAmount = request.TargetAmount, TargetAmount = request.TargetAmount,
MainImageUrl = request.MainImageUrl?.Trim(), MainImageUrl = request.MainImageUrl == null ? null : UnresolvedUri.Create(request.MainImageUrl),
NotificationUrl = request.NotificationUrl?.Trim(), NotificationUrl = request.NotificationUrl?.Trim(),
Tagline = request.Tagline?.Trim(), Tagline = request.Tagline?.Trim(),
PerksTemplate = request.PerksTemplate is not null ? AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate.Trim())) : null, PerksTemplate = request.PerksTemplate is not null ? AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate.Trim())) : null,
@@ -411,7 +416,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
} }
private CrowdfundAppData ToCrowdfundModel(AppData appData) private async Task<CrowdfundAppData> ToCrowdfundModel(AppData appData)
{ {
var settings = appData.GetSettings<CrowdfundSettings>(); var settings = appData.GetSettings<CrowdfundSettings>();
Enum.TryParse<CrowdfundResetEvery>(settings.ResetEvery.ToString(), true, out var resetEvery); Enum.TryParse<CrowdfundResetEvery>(settings.ResetEvery.ToString(), true, out var resetEvery);
@@ -432,7 +437,7 @@ namespace BTCPayServer.Controllers.Greenfield
Description = settings.Description, Description = settings.Description,
EndDate = settings.EndDate, EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount, TargetAmount = settings.TargetAmount,
MainImageUrl = settings.MainImageUrl, MainImageUrl = settings.MainImageUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), settings.MainImageUrl),
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
Tagline = settings.Tagline, Tagline = settings.Tagline,
DisqusEnabled = settings.DisqusEnabled, DisqusEnabled = settings.DisqusEnabled,

View File

@@ -4,8 +4,10 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Form; using BTCPayServer.Abstractions.Form;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
@@ -44,6 +46,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
EventAggregator eventAggregator, EventAggregator eventAggregator,
UriResolver uriResolver, UriResolver uriResolver,
StoreRepository storeRepository, StoreRepository storeRepository,
IFileService fileService,
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
FormDataService formDataService, FormDataService formDataService,
@@ -53,6 +56,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
_appService = appService; _appService = appService;
_userManager = userManager; _userManager = userManager;
_app = app; _app = app;
_fileService = fileService;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_uriResolver = uriResolver; _uriResolver = uriResolver;
@@ -61,6 +65,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
} }
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly IFileService _fileService;
private readonly UriResolver _uriResolver; private readonly UriResolver _uriResolver;
private readonly CurrencyNameTable _currencies; private readonly CurrencyNameTable _currencies;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
@@ -393,6 +398,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
var settings = app.GetSettings<CrowdfundSettings>(); var settings = app.GetSettings<CrowdfundSettings>();
var resetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery); var resetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery);
var vm = new UpdateCrowdfundViewModel var vm = new UpdateCrowdfundViewModel
{ {
Title = settings.Title, Title = settings.Title,
@@ -405,8 +411,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
EnforceTargetAmount = settings.EnforceTargetAmount, EnforceTargetAmount = settings.EnforceTargetAmount,
StartDate = settings.StartDate, StartDate = settings.StartDate,
TargetCurrency = settings.TargetCurrency, TargetCurrency = settings.TargetCurrency,
MainImageUrl = settings.MainImageUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), settings.MainImageUrl),
Description = settings.Description, Description = settings.Description,
MainImageUrl = settings.MainImageUrl,
EndDate = settings.EndDate, EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount, TargetAmount = settings.TargetAmount,
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
@@ -434,8 +440,13 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("{appId}/settings/crowdfund")] [HttpPost("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command) public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm,
[FromForm] bool RemoveLogoFile = false)
{ {
var userId = GetUserId();
if (userId is null)
return NotFound();
var app = GetCurrentApp(); var app = GetCurrentApp();
if (app == null) if (app == null)
return NotFound(); return NotFound();
@@ -503,6 +514,16 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
parsedAnimationColors = new CrowdfundSettings().AnimationColors; parsedAnimationColors = new CrowdfundSettings().AnimationColors;
} }
UploadImageResultModel imageUpload = null;
if (vm.MainImageFile != null)
{
imageUpload = await _fileService.UploadImage(vm.MainImageFile, userId);
if (!imageUpload.Success)
{
ModelState.AddModelError(nameof(vm.MainImageFile), imageUpload.Response);
}
}
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return View("Crowdfund/UpdateCrowdfund", vm); return View("Crowdfund/UpdateCrowdfund", vm);
@@ -520,7 +541,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
Description = vm.Description, Description = vm.Description,
EndDate = vm.EndDate?.ToUniversalTime(), EndDate = vm.EndDate?.ToUniversalTime(),
TargetAmount = vm.TargetAmount, TargetAmount = vm.TargetAmount,
MainImageUrl = vm.MainImageUrl, MainImageUrl = app.GetSettings<CrowdfundSettings>()?.MainImageUrl,
NotificationUrl = vm.NotificationUrl, NotificationUrl = vm.NotificationUrl,
Tagline = vm.Tagline, Tagline = vm.Tagline,
PerksTemplate = vm.PerksTemplate, PerksTemplate = vm.PerksTemplate,
@@ -538,6 +559,17 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
FormId = vm.FormId FormId = vm.FormId
}; };
if (imageUpload?.Success is true)
{
newSettings.MainImageUrl = new UnresolvedUri.FileIdUri(imageUpload.StoredFile.Id);
}
else if (RemoveLogoFile)
{
newSettings.MainImageUrl = null;
vm.MainImageUrl = null;
vm.MainImageFile = null;
}
app.TagAllInvoices = vm.UseAllStoreInvoices; app.TagAllInvoices = vm.UseAllStoreInvoices;
app.SetSettings(newSettings); app.SetSettings(newSettings);

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services; using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
@@ -49,22 +50,28 @@ namespace BTCPayServer.Plugins.Crowdfund
private readonly IOptions<BTCPayServerOptions> _options; private readonly IOptions<BTCPayServerOptions> _options;
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
private readonly CurrencyNameTable _currencyNameTable; private readonly CurrencyNameTable _currencyNameTable;
private readonly UriResolver _uriResolver;
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly PrettyNameProvider _prettyNameProvider; private readonly PrettyNameProvider _prettyNameProvider;
public const string AppType = "Crowdfund"; public const string AppType = "Crowdfund";
public CrowdfundAppType( public CrowdfundAppType(
LinkGenerator linkGenerator, LinkGenerator linkGenerator,
IOptions<BTCPayServerOptions> options, IOptions<BTCPayServerOptions> options,
UriResolver uriResolver,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
PrettyNameProvider prettyNameProvider, PrettyNameProvider prettyNameProvider,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
IHttpContextAccessor httpContextAccessor,
CurrencyNameTable currencyNameTable) CurrencyNameTable currencyNameTable)
{ {
Description = Type = AppType; Description = Type = AppType;
_linkGenerator = linkGenerator; _linkGenerator = linkGenerator;
_options = options; _options = options;
_uriResolver = uriResolver;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
_httpContextAccessor = httpContextAccessor;
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_prettyNameProvider = prettyNameProvider; _prettyNameProvider = prettyNameProvider;
@@ -186,12 +193,11 @@ namespace BTCPayServer.Plugins.Crowdfund
? _linkGenerator.GetPathByAction(nameof(UICrowdfundController.CrowdfundForm), "UICrowdfund", ? _linkGenerator.GetPathByAction(nameof(UICrowdfundController.CrowdfundForm), "UICrowdfund",
new { appId = appData.Id }, _options.Value.RootPath) new { appId = appData.Id }, _options.Value.RootPath)
: null; : null;
return new ViewCrowdfundViewModel var vm = new ViewCrowdfundViewModel
{ {
Title = settings.Title, Title = settings.Title,
Tagline = settings.Tagline, Tagline = settings.Tagline,
Description = settings.Description, Description = settings.Description,
MainImageUrl = settings.MainImageUrl,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreId = appData.StoreDataId, StoreId = appData.StoreDataId,
AppId = appData.Id, AppId = appData.Id,
@@ -230,6 +236,12 @@ namespace BTCPayServer.Plugins.Crowdfund
CurrentAmount = currentPayments.TotalCurrency CurrentAmount = currentPayments.TotalCurrency
} }
}; };
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null && settings.MainImageUrl != null)
{
vm.MainImageUrl = await _uriResolver.Resolve(httpContext.Request.GetAbsoluteRootUri(), settings.MainImageUrl);
}
return vm;
} }
private Dictionary<string, PaymentStat> GetPaymentStats(InvoiceStatistics stats) private Dictionary<string, PaymentStat> GetPaymentStats(InvoiceStatistics stats)

View File

@@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Validation; using BTCPayServer.Validation;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace BTCPayServer.Plugins.Crowdfund.Models namespace BTCPayServer.Plugins.Crowdfund.Models
{ {
@@ -32,6 +34,10 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
[Display(Name = "Featured Image URL")] [Display(Name = "Featured Image URL")]
public string MainImageUrl { get; set; } public string MainImageUrl { get; set; }
[Display(Name = "Featured Image URL")]
[JsonIgnore]
public IFormFile MainImageFile { get; set; }
[Display(Name = "Callback Notification URL")] [Display(Name = "Callback Notification URL")]
[Uri] [Uri]
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }

View File

@@ -1,4 +1,6 @@
using System; using System;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Apps namespace BTCPayServer.Services.Apps
{ {
@@ -27,7 +29,8 @@ namespace BTCPayServer.Services.Apps
} }
public bool EnforceTargetAmount { get; set; } public bool EnforceTargetAmount { get; set; }
public string MainImageUrl { get; set; } [JsonConverter(typeof(UnresolvedUriJsonConverter))]
public UnresolvedUri MainImageUrl { get; set; }
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }
public string Tagline { get; set; } public string Tagline { get; set; }
public string PerksTemplate { get; set; } public string PerksTemplate { get; set; }

View File

@@ -6,10 +6,12 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Storage.Models; using BTCPayServer.Storage.Models;
@@ -47,6 +49,44 @@ namespace BTCPayServer.Storage.Services
return settings is not null; return settings is not null;
} }
public async Task<UploadImageResultModel> UploadImage(IFormFile file, string userId, long maxFileSizeInBytes = 1_000_000)
{
var result = new UploadImageResultModel();
if (file.Length > maxFileSizeInBytes)
{
result.Success = false;
result.Response = $"The uploaded image file should be less than {maxFileSizeInBytes / 1_000_000}MB";
return result;
}
if (!file.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
{
result.Success = false;
result.Response = "The uploaded file needs to be an image (based on content type)";
return result;
}
var formFile = await file.Bufferize();
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
{
result.Success = false;
result.Response = "The uploaded file needs to be an image (based on file content)";
return result;
}
try
{
result.StoredFile = await AddFile(formFile, userId);
result.Success = true;
result.Response = "Image uploaded successfully";
}
catch (Exception e)
{
result.Success = false;
result.Response = $"Could not save image: {e.Message}";
}
return result;
}
public async Task<IStoredFile> AddFile(IFormFile file, string userId) public async Task<IStoredFile> AddFile(IFormFile file, string userId)
{ {
var settings = await _settingsRepository.GetSettingAsync<StorageSettings>(); var settings = await _settingsRepository.GetSettingAsync<StorageSettings>();

View File

@@ -1,4 +1,5 @@
@using System.Globalization @using System.Globalization
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Models @using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client @using BTCPayServer.Client
@using BTCPayServer.TagHelpers @using BTCPayServer.TagHelpers
@@ -6,11 +7,13 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Forms @using BTCPayServer.Forms
@inject FormDataService FormDataService @inject FormDataService FormDataService
@inject IFileService FileService
@inject BTCPayServer.Security.ContentSecurityPolicies Csp @inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel @model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
@{ @{
ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId); ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId);
Csp.UnsafeEval(); Csp.UnsafeEval();
var canUpload = await FileService.IsAvailable();
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId); var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
} }
@@ -25,7 +28,7 @@
<script src="~/crowdfund/admin.js" asp-append-version="true"></script> <script src="~/crowdfund/admin.js" asp-append-version="true"></script>
} }
<form method="post" permissioned="@Policies.CanModifyStoreSettings"> <form method="post" enctype="multipart/form-data" permissioned="@Policies.CanModifyStoreSettings">
<div class="sticky-header"> <div class="sticky-header">
<h2 text-translate="true">@ViewData["Title"]</h2> <h2 text-translate="true">@ViewData["Title"]</h2>
<div> <div>
@@ -74,9 +77,31 @@
<span asp-validation-for="Tagline" class="text-danger"></span> <span asp-validation-for="Tagline" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="MainImageUrl" class="form-label"></label> <div class="d-flex align-items-center justify-content-between gap-2">
<input asp-for="MainImageUrl" class="form-control" /> <label asp-for="MainImageFile" class="form-label"></label>
<span asp-validation-for="MainImageUrl" class="text-danger"></span> @if (!string.IsNullOrEmpty(Model.MainImageUrl))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true">
<vc:icon symbol="cross" /> Remove
</button>
}
</div>
@if (canUpload)
{
<div class="d-flex align-items-center gap-3">
<input asp-for="MainImageFile" type="file" class="form-control flex-grow">
@if (!string.IsNullOrEmpty(Model.MainImageUrl))
{
<img src="@Model.MainImageUrl" alt="Logo" style="height:2.1rem;max-width:10.5rem;" />
}
</div>
<span asp-validation-for="MainImageFile" class="text-danger"></span>
}
else
{
<input asp-for="MainImageFile" type="file" class="form-control" disabled>
<div class="form-text">In order to upload an image, a <a asp-controller="UIServer" asp-action="Files">file storage</a> must be configured.</div>
}
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">