diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index 7bc6f7938..a1825faae 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -623,7 +623,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var pos = user.GetController(); var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); - var appType = PointOfSaleApp.AppType; + var appType = PointOfSaleAppType.AppType; vm.AppName = "test"; vm.SelectedAppType = appType; var redirect = Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); diff --git a/BTCPayServer.Tests/CrowdfundTests.cs b/BTCPayServer.Tests/CrowdfundTests.cs index f84cbf1c6..121cfd8e0 100644 --- a/BTCPayServer.Tests/CrowdfundTests.cs +++ b/BTCPayServer.Tests/CrowdfundTests.cs @@ -37,7 +37,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var apps2 = user2.GetController(); var crowdfund = user.GetController(); - var appType = CrowdfundApp.AppType; + var appType = CrowdfundAppType.AppType; var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId, appType)).Model); Assert.Equal(appType, vm.SelectedAppType); Assert.Null(vm.AppName); @@ -77,7 +77,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var crowdfund = user.GetController(); var vm = apps.CreateApp(user.StoreId).AssertViewModel(); - var appType = CrowdfundApp.AppType; + var appType = CrowdfundAppType.AppType; vm.AppName = "test"; vm.SelectedAppType = appType; var redirect = Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); @@ -169,7 +169,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var crowdfund = user.GetController(); var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); - var appType = CrowdfundApp.AppType; + var appType = CrowdfundAppType.AppType; vm.AppName = "test"; vm.SelectedAppType = appType; Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); diff --git a/BTCPayServer.Tests/POSTests.cs b/BTCPayServer.Tests/POSTests.cs index 0ba186197..961a9fec2 100644 --- a/BTCPayServer.Tests/POSTests.cs +++ b/BTCPayServer.Tests/POSTests.cs @@ -31,7 +31,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var pos = user.GetController(); var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId)).Model); - var appType = PointOfSaleApp.AppType; + var appType = PointOfSaleAppType.AppType; vm.AppName = "test"; vm.SelectedAppType = appType; var redirect = Assert.IsType(apps.CreateApp(user.StoreId, vm).Result); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index bfc7150b8..7ebfabf66 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1955,7 +1955,7 @@ namespace BTCPayServer.Tests var apps = user.GetController(); var apps2 = user2.GetController(); var pos = user.GetController(); - var appType = PointOfSaleApp.AppType; + var appType = PointOfSaleAppType.AppType; var vm = Assert.IsType(Assert.IsType(apps.CreateApp(user.StoreId, appType)).Model); Assert.Equal(appType, vm.SelectedAppType); Assert.Null(vm.AppName); diff --git a/BTCPayServer/Components/AppSales/AppSales.cs b/BTCPayServer/Components/AppSales/AppSales.cs index 39faeb9b9..f7f9427e5 100644 --- a/BTCPayServer/Components/AppSales/AppSales.cs +++ b/BTCPayServer/Components/AppSales/AppSales.cs @@ -7,6 +7,8 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace BTCPayServer.Components.AppSales; @@ -27,6 +29,9 @@ public class AppSales : ViewComponent public async Task InvokeAsync(string appId, string appType) { + var type = _appService.GetAppType(appType); + if (type is not IHasSaleStatsAppType salesAppType || type is not AppBaseType appBaseType) + return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty)); var vm = new AppSalesViewModel { Id = appId, @@ -42,7 +47,8 @@ public class AppSales : ViewComponent vm.SalesCount = stats.SalesCount; vm.Series = stats.Series; vm.AppType = app.AppType; - vm.AppUrl = await _appService.ConfigureLink(app, app.AppType); + vm.AppUrl = await appBaseType.ConfigureLink(app); + vm.Name = app.Name; return View(vm); } diff --git a/BTCPayServer/Components/AppSales/AppSalesViewModel.cs b/BTCPayServer/Components/AppSales/AppSalesViewModel.cs index c0c1e1bb0..af1029c53 100644 --- a/BTCPayServer/Components/AppSales/AppSalesViewModel.cs +++ b/BTCPayServer/Components/AppSales/AppSalesViewModel.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using BTCPayServer.Data; using BTCPayServer.Services.Apps; namespace BTCPayServer.Components.AppSales; diff --git a/BTCPayServer/Components/AppSales/Default.cshtml b/BTCPayServer/Components/AppSales/Default.cshtml index 5f7326259..294de8b93 100644 --- a/BTCPayServer/Components/AppSales/Default.cshtml +++ b/BTCPayServer/Components/AppSales/Default.cshtml @@ -3,7 +3,7 @@ @using BTCPayServer.Plugins.Crowdfund @model BTCPayServer.Components.AppSales.AppSalesViewModel @{ - var label = Model.AppType == CrowdfundApp.AppType ? "Contributions" : "Sales"; + var label = Model.AppType == CrowdfundAppType.AppType ? "Contributions" : "Sales"; }
diff --git a/BTCPayServer/Components/AppTopItems/AppTopItems.cs b/BTCPayServer/Components/AppTopItems/AppTopItems.cs index 44dce7bd2..719304c67 100644 --- a/BTCPayServer/Components/AppTopItems/AppTopItems.cs +++ b/BTCPayServer/Components/AppTopItems/AppTopItems.cs @@ -7,6 +7,8 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace BTCPayServer.Components.AppTopItems; @@ -19,8 +21,12 @@ public class AppTopItems : ViewComponent _appService = appService; } - public async Task InvokeAsync(string appId, string appType = null) + public async Task InvokeAsync(string appId, string appType) { + var type = _appService.GetAppType(appType); + if (type is not IHasItemStatsAppType salesAppType || type is not AppBaseType appBaseType) + return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty)); + var vm = new AppTopItemsViewModel { Id = appId, @@ -36,7 +42,8 @@ public class AppTopItems : ViewComponent vm.SalesCount = entries.Select(e => e.SalesCount).ToList(); vm.Entries = entries.ToList(); vm.AppType = app.AppType; - vm.AppUrl = await _appService.ConfigureLink(app, app.AppType); + vm.AppUrl = await appBaseType.ConfigureLink(app); + vm.Name = app.Name; return View(vm); } diff --git a/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs b/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs index 16cf6f2a2..3ee1839ad 100644 --- a/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs +++ b/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using BTCPayServer.Data; using BTCPayServer.Services.Apps; namespace BTCPayServer.Components.AppTopItems; diff --git a/BTCPayServer/Components/AppTopItems/Default.cshtml b/BTCPayServer/Components/AppTopItems/Default.cshtml index b0b209174..8d9049c50 100644 --- a/BTCPayServer/Components/AppTopItems/Default.cshtml +++ b/BTCPayServer/Components/AppTopItems/Default.cshtml @@ -1,12 +1,12 @@ @using BTCPayServer.Plugins.Crowdfund @model BTCPayServer.Components.AppTopItems.AppTopItemsViewModel @{ - var label = Model.AppType == CrowdfundApp.AppType ? "contribution" : "sale"; + var label = Model.AppType == CrowdfundAppType.AppType ? "contribution" : "sale"; }
-

Top @(Model.AppType == CrowdfundApp.AppType ? "Perks" : "Items")

+

Top @(Model.AppType == CrowdfundAppType.AppType ? "Perks" : "Items")

@if (!string.IsNullOrEmpty(Model.AppUrl)) { View All diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs index 35d27af76..b7dd27015 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs @@ -66,7 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield { StoreDataId = storeId, Name = request.AppName, - AppType = CrowdfundApp.AppType + AppType = CrowdfundAppType.AppType }; appData.SetSettings(ToCrowdfundSettings(request)); @@ -97,7 +97,7 @@ namespace BTCPayServer.Controllers.Greenfield { StoreDataId = storeId, Name = request.AppName, - AppType = PointOfSaleApp.AppType + AppType = PointOfSaleAppType.AppType }; appData.SetSettings(ToPointOfSaleSettings(request)); @@ -111,7 +111,7 @@ namespace BTCPayServer.Controllers.Greenfield [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task UpdatePointOfSaleApp(string appId, CreatePointOfSaleAppRequest request) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (app == null) { return AppNotFound(); @@ -184,7 +184,7 @@ namespace BTCPayServer.Controllers.Greenfield [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task GetPosApp(string appId) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (app == null) { return AppNotFound(); @@ -197,7 +197,7 @@ namespace BTCPayServer.Controllers.Greenfield [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task GetCrowdfundApp(string appId) { - var app = await _appService.GetApp(appId, CrowdfundApp.AppType); + var app = await _appService.GetApp(appId, CrowdfundAppType.AppType); if (app == null) { return AppNotFound(); @@ -245,7 +245,7 @@ namespace BTCPayServer.Controllers.Greenfield EmbeddedCSS = request.EmbeddedCSS?.Trim(), NotificationUrl = request.NotificationUrl?.Trim(), Tagline = request.Tagline?.Trim(), - PerksTemplate = request.PerksTemplate != null ? _appService.SerializeTemplate(_appService.Parse(request.PerksTemplate?.Trim(), request.TargetCurrency)) : null, + PerksTemplate = request.PerksTemplate is not null ? _appService.SerializeTemplate(_appService.Parse(request.PerksTemplate.Trim(), request.TargetCurrency!)) : null, // If Disqus shortname is not null or empty we assume that Disqus should be enabled DisqusEnabled = !string.IsNullOrEmpty(request.DisqusShortname?.Trim()), DisqusShortname = request.DisqusShortname?.Trim(), @@ -363,7 +363,8 @@ namespace BTCPayServer.Controllers.Greenfield { try { - _appService.SerializeTemplate(_appService.Parse(request.Template, request.Currency)); + // Just checking if we can serialize, we don't care about the currency + _appService.SerializeTemplate(_appService.Parse(request.Template, "USD")); } catch { @@ -452,7 +453,8 @@ namespace BTCPayServer.Controllers.Greenfield try { - _appService.SerializeTemplate(_appService.Parse(request.PerksTemplate, request.TargetCurrency)); + // Just checking if we can serialize, we don't care about the currency + _appService.SerializeTemplate(_appService.Parse(request.PerksTemplate, "USD")); } catch { diff --git a/BTCPayServer/Controllers/UIAppsController.Dashboard.cs b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs index b4a7d5299..3f3d5d9a4 100644 --- a/BTCPayServer/Controllers/UIAppsController.Dashboard.cs +++ b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs @@ -21,7 +21,7 @@ namespace BTCPayServer.Controllers app.StoreData = GetCurrentStore(); - return ViewComponent("AppTopItems", new { appId = app.Id }); + return ViewComponent("AppTopItems", new { appId = app.Id, appType = app.AppType }); } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] @@ -33,7 +33,7 @@ namespace BTCPayServer.Controllers return NotFound(); app.StoreData = GetCurrentStore(); - return ViewComponent("AppSales", new { appId = app.Id }); + return ViewComponent("AppSales", new { appId = app.Id, appType = app.AppType }); } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] diff --git a/BTCPayServer/Controllers/UIAppsController.cs b/BTCPayServer/Controllers/UIAppsController.cs index 9e65c5f57..7df81dd9e 100644 --- a/BTCPayServer/Controllers/UIAppsController.cs +++ b/BTCPayServer/Controllers/UIAppsController.cs @@ -124,8 +124,8 @@ namespace BTCPayServer.Controllers { var store = GetCurrentStore(); vm.StoreId = store.Id; - var types = _appService.GetAvailableAppTypes(); - if (!types.ContainsKey(vm.SelectedAppType)) + var type = _appService.GetAppType(vm.SelectedAppType); + if (type is null) ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type"); if (!ModelState.IsValid) @@ -147,7 +147,8 @@ namespace BTCPayServer.Controllers TempData[WellKnownTempData.SuccessMessage] = "App successfully created"; CreatedAppId = appData.Id; - var url = await _appService.ConfigureLink(appData, vm.SelectedAppType); + + var url = await type.ConfigureLink(appData); return Redirect(url); } diff --git a/BTCPayServer/Controllers/UILNURLController.cs b/BTCPayServer/Controllers/UILNURLController.cs index 0091f2079..cca9eea42 100644 --- a/BTCPayServer/Controllers/UILNURLController.cs +++ b/BTCPayServer/Controllers/UILNURLController.cs @@ -256,12 +256,12 @@ namespace BTCPayServer PointOfSaleSettings posS = null; switch (app.AppType) { - case CrowdfundApp.AppType: + case CrowdfundAppType.AppType: var cfS = app.GetSettings(); currencyCode = cfS.TargetCurrency; items = _appService.Parse(cfS.PerksTemplate, cfS.TargetCurrency); break; - case PointOfSaleApp.AppType: + case PointOfSaleAppType.AppType: posS = app.GetSettings(); currencyCode = posS.Currency; items = _appService.Parse(posS.Template, posS.Currency); @@ -287,7 +287,7 @@ namespace BTCPayServer return NotFound(); } } - else if (app.AppType == PointOfSaleApp.AppType && posS?.ShowCustomAmount is not true) + else if (app.AppType == PointOfSaleAppType.AppType && posS?.ShowCustomAmount is not true) { return NotFound(); } @@ -400,7 +400,7 @@ namespace BTCPayServer var redirectUrl = app?.AppType switch { - PointOfSaleApp.AppType => app.GetSettings().RedirectUrl ?? + PointOfSaleAppType.AppType => app.GetSettings().RedirectUrl ?? HttpContext.Request.GetAbsoluteUri($"/apps/{app.Id}/pos"), _ => null }; diff --git a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs index b2030e9f7..0ad491f07 100644 --- a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs +++ b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs @@ -42,6 +42,8 @@ namespace BTCPayServer.Controllers return View(vm); var userId = GetUserId(); + if (userId is null) + return NotFound(); var apps = await _appService.GetAllApps(userId, false, store.Id); foreach (var app in apps) { diff --git a/BTCPayServer/HostedServices/AppInventoryUpdaterHostedService.cs b/BTCPayServer/HostedServices/AppInventoryUpdaterHostedService.cs index 92744375e..0ac3b6ba3 100644 --- a/BTCPayServer/HostedServices/AppInventoryUpdaterHostedService.cs +++ b/BTCPayServer/HostedServices/AppInventoryUpdaterHostedService.cs @@ -37,11 +37,11 @@ namespace BTCPayServer.HostedServices { switch (data.AppType) { - case PointOfSaleApp.AppType: + case PointOfSaleAppType.AppType: var possettings = data.GetSettings(); return (Data: data, Settings: (object)possettings, Items: _appService.Parse(possettings.Template, possettings.Currency)); - case CrowdfundApp.AppType: + case CrowdfundAppType.AppType: var cfsettings = data.GetSettings(); return (Data: data, Settings: (object)cfsettings, Items: _appService.Parse(cfsettings.PerksTemplate, cfsettings.TargetCurrency)); @@ -68,11 +68,11 @@ namespace BTCPayServer.HostedServices switch (valueTuple.Data.AppType) { - case PointOfSaleApp.AppType: + case PointOfSaleAppType.AppType: ((PointOfSaleSettings)valueTuple.Settings).Template = _appService.SerializeTemplate(valueTuple.Items); break; - case CrowdfundApp.AppType: + case CrowdfundAppType.AppType: ((CrowdfundSettings)valueTuple.Settings).PerksTemplate = _appService.SerializeTemplate(valueTuple.Items); break; diff --git a/BTCPayServer/Hosting/MigrationStartupTask.cs b/BTCPayServer/Hosting/MigrationStartupTask.cs index 20866af21..71e38e88b 100644 --- a/BTCPayServer/Hosting/MigrationStartupTask.cs +++ b/BTCPayServer/Hosting/MigrationStartupTask.cs @@ -470,7 +470,7 @@ WHERE cte.""Id""=p.""Id"" string newTemplate; switch (app.AppType) { - case CrowdfundApp.AppType: + case CrowdfundAppType.AppType: var settings1 = app.GetSettings(); if (string.IsNullOrEmpty(settings1.TargetCurrency)) { @@ -486,7 +486,7 @@ WHERE cte.""Id""=p.""Id"" }; break; - case PointOfSaleApp.AppType: + case PointOfSaleAppType.AppType: var settings2 = app.GetSettings(); if (string.IsNullOrEmpty(settings2.Currency)) diff --git a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs index 717a6af76..55a5427b9 100644 --- a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs @@ -33,7 +33,7 @@ namespace BTCPayServer.Models.AppViewModels private void SetApps(AppService appService) { - var defaultAppType = PointOfSaleApp.AppType; + var defaultAppType = PointOfSaleAppType.AppType; var choices = appService.GetAvailableAppTypes().Select(pair => new SelectListItem(pair.Value, pair.Key, pair.Key == defaultAppType)); diff --git a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs index bbcd41cc3..3bfbb82a0 100644 --- a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs +++ b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs @@ -36,7 +36,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers StoreRepository storeRepository, UIInvoiceController invoiceController, UserManager userManager, - CrowdfundApp app) + CrowdfundAppType app) { _currencies = currencies; _appService = appService; @@ -53,21 +53,21 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers private readonly AppService _appService; private readonly UIInvoiceController _invoiceController; private readonly UserManager _userManager; - private readonly CrowdfundApp _app; + private readonly CrowdfundAppType _app; [HttpGet("/")] [HttpGet("/apps/{appId}/crowdfund")] [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] - [DomainMappingConstraint(CrowdfundApp.AppType)] + [DomainMappingConstraint(CrowdfundAppType.AppType)] public async Task ViewCrowdfund(string appId) { - var app = await _appService.GetApp(appId, CrowdfundApp.AppType, true); + var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, true); if (app == null) return NotFound(); var settings = app.GetSettings(); - var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, CrowdfundApp.AppType) != null; + var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, CrowdfundAppType.AppType) != null; var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency); if (!hasEnoughSettingsToLoad) @@ -92,17 +92,17 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] - [DomainMappingConstraint(CrowdfundApp.AppType)] + [DomainMappingConstraint(CrowdfundAppType.AppType)] [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) { - var app = await _appService.GetApp(appId, CrowdfundApp.AppType, true); + var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, true); if (app == null) return NotFound(); var settings = app.GetSettings(); - var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, CrowdfundApp.AppType) != null; + var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, CrowdfundAppType.AppType) != null; if (!settings.Enabled && !isAdmin) { @@ -398,7 +398,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers private async Task GetAppInfo(string appId) { - var app = await _appService.GetApp(appId, CrowdfundApp.AppType, true); + var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, true); if (app is null) { return null; diff --git a/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs b/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs index 3a3f11ed1..b8e24219a 100644 --- a/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs +++ b/BTCPayServer/Plugins/Crowdfund/CrowdfundPlugin.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +10,7 @@ using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.Plugins.Crowdfund.Controllers; using BTCPayServer.Plugins.Crowdfund.Models; +using BTCPayServer.Plugins.PointOfSale; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; @@ -29,14 +31,14 @@ namespace BTCPayServer.Plugins.Crowdfund public override void Execute(IServiceCollection services) { services.AddSingleton(new UIExtension("Crowdfund/NavExtension", "apps-nav")); - services.AddSingleton(); - services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(); base.Execute(services); } } - public class CrowdfundApp: IApp + public class CrowdfundAppType: AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType { private readonly LinkGenerator _linkGenerator; private readonly IOptions _options; @@ -45,12 +47,8 @@ namespace BTCPayServer.Plugins.Crowdfund private readonly HtmlSanitizer _htmlSanitizer; private readonly InvoiceRepository _invoiceRepository; public const string AppType = "Crowdfund"; - public string Description => AppType; - public string Type => AppType; - public bool SupportsSalesStats => true; - public bool SupportsItemStats => true; - public CrowdfundApp( + public CrowdfundAppType( LinkGenerator linkGenerator, IOptions options, InvoiceRepository invoiceRepository, @@ -58,6 +56,7 @@ namespace BTCPayServer.Plugins.Crowdfund CurrencyNameTable currencyNameTable, HtmlSanitizer htmlSanitizer) { + Description = Type = AppType; _linkGenerator = linkGenerator; _options = options; _displayFormatter = displayFormatter; @@ -66,10 +65,10 @@ namespace BTCPayServer.Plugins.Crowdfund _invoiceRepository = invoiceRepository; } - public Task ConfigureLink(AppData app) + public override Task ConfigureLink(AppData app) { return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UICrowdfundController.UpdateCrowdfund), - "UICrowdfund", new { appId = app.Id }, _options.Value.RootPath)); + "UICrowdfund", new { appId = app.Id }, _options.Value.RootPath)!); } public Task GetSalesStats(AppData app, InvoiceEntity[] paidInvoices, int numberOfDays) @@ -114,14 +113,14 @@ namespace BTCPayServer.Plugins.Crowdfund return Task.FromResult>(perkCount); } - - public async Task GetInfo(AppData appData) + + public override async Task GetInfo(AppData appData) { var settings = appData.GetSettings(); var resetEvery = settings.StartDate.HasValue ? settings.ResetEvery : CrowdfundResetEvery.Never; DateTime? lastResetDate = null; DateTime? nextResetDate = null; - if (resetEvery != CrowdfundResetEvery.Never) + if (resetEvery != CrowdfundResetEvery.Never && settings.StartDate is not null) { lastResetDate = settings.StartDate.Value; @@ -187,7 +186,7 @@ namespace BTCPayServer.Plugins.Crowdfund .ToList(); var remainingPerks = perks.Where(item => !newPerksOrder.Contains(item)); newPerksOrder.AddRange(remainingPerks); - perks = newPerksOrder.ToArray(); + perks = newPerksOrder.ToArray()!; } var store = appData.StoreData; @@ -247,17 +246,17 @@ namespace BTCPayServer.Plugins.Crowdfund }; } - public Task SetDefaultSettings(AppData appData, string defaultCurrency) + public override Task SetDefaultSettings(AppData appData, string defaultCurrency) { var emptyCrowdfund = new CrowdfundSettings { TargetCurrency = defaultCurrency }; appData.SetSettings(emptyCrowdfund); return Task.CompletedTask; } - public Task ViewLink(AppData app) + public override Task ViewLink(AppData app) { return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UICrowdfundController.ViewCrowdfund), - "UICrowdfund", new {appId = app.Id}, _options.Value.RootPath)); + "UICrowdfund", new {appId = app.Id}, _options.Value.RootPath)!); } private static bool IsPaid(InvoiceEntity entity) diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs index d7d265d30..9746816a7 100644 --- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs +++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs @@ -64,11 +64,11 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers [HttpGet("/")] [HttpGet("/apps/{appId}/pos")] [HttpGet("/apps/{appId}/pos/{viewType?}")] - [DomainMappingConstraint(PointOfSaleApp.AppType)] + [DomainMappingConstraint(PointOfSaleAppType.AppType)] [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] public async Task ViewPointOfSale(string appId, PosViewType? viewType = null) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (app == null) return NotFound(); var settings = app.GetSettings(); @@ -121,7 +121,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers [HttpPost("/apps/{appId}/pos/{viewType?}")] [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] - [DomainMappingConstraint(PointOfSaleApp.AppType)] + [DomainMappingConstraint(PointOfSaleAppType.AppType)] [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] public async Task ViewPointOfSale(string appId, @@ -137,7 +137,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore, CancellationToken cancellationToken = default) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (string.IsNullOrEmpty(choiceKey) && amount <= 0) { return RedirectToAction(nameof(ViewPointOfSale), new { appId }); @@ -334,7 +334,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] public async Task POSForm(string appId, PosViewType? viewType = null) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (app == null) return NotFound(); @@ -380,7 +380,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.Unset)] public async Task POSFormSubmit(string appId, FormViewModel viewModel, PosViewType? viewType = null) { - var app = await _appService.GetApp(appId, PointOfSaleApp.AppType); + var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType); if (app == null) return NotFound(); diff --git a/BTCPayServer/Plugins/PointOfSale/PointOfSalePlugin.cs b/BTCPayServer/Plugins/PointOfSale/PointOfSalePlugin.cs index 60ed171a3..1b6930bb2 100644 --- a/BTCPayServer/Plugins/PointOfSale/PointOfSalePlugin.cs +++ b/BTCPayServer/Plugins/PointOfSale/PointOfSalePlugin.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -28,7 +29,7 @@ namespace BTCPayServer.Plugins.PointOfSale public override void Execute(IServiceCollection services) { services.AddSingleton(new UIExtension("PointOfSale/NavExtension", "apps-nav")); - services.AddSingleton(); + services.AddSingleton(); base.Execute(services); } } @@ -45,34 +46,37 @@ namespace BTCPayServer.Plugins.PointOfSale Print } - public class PointOfSaleApp: IApp + public class PointOfSaleAppType: AppBaseType, IHasSaleStatsAppType, IHasItemStatsAppType { private readonly LinkGenerator _linkGenerator; private readonly IOptions _btcPayServerOptions; private readonly DisplayFormatter _displayFormatter; private readonly HtmlSanitizer _htmlSanitizer; public const string AppType = "PointOfSale"; - public string Description => "Point of Sale"; - public string Type => AppType; - public bool SupportsSalesStats => true; - public bool SupportsItemStats => true; - public PointOfSaleApp( + public PointOfSaleAppType( LinkGenerator linkGenerator, IOptions btcPayServerOptions, DisplayFormatter displayFormatter, HtmlSanitizer htmlSanitizer) { + Type = AppType; + Description = "Point of Sale"; _linkGenerator = linkGenerator; _btcPayServerOptions = btcPayServerOptions; _displayFormatter = displayFormatter; _htmlSanitizer = htmlSanitizer; } - public Task ConfigureLink(AppData app) + public override Task ConfigureLink(AppData app) { return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UIPointOfSaleController.UpdatePointOfSale), - "UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath)); + "UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath)!); + } + + public override Task GetInfo(AppData appData) + { + return Task.FromResult(null); } public Task GetSalesStats(AppData app, InvoiceEntity[] paidInvoices, int numberOfDays) @@ -114,22 +118,17 @@ namespace BTCPayServer.Plugins.PointOfSale return Task.FromResult>(itemCount); } - public Task GetInfo(AppData appData) - { - throw new NotImplementedException(); - } - - public Task SetDefaultSettings(AppData appData, string defaultCurrency) + public override Task SetDefaultSettings(AppData appData, string defaultCurrency) { var empty = new PointOfSaleSettings { Currency = defaultCurrency }; appData.SetSettings(empty); return Task.CompletedTask; } - public Task ViewLink(AppData app) + public override Task ViewLink(AppData app) { return Task.FromResult(_linkGenerator.GetPathByAction(nameof(UIPointOfSaleController.ViewPointOfSale), - "UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath)); + "UIPointOfSale", new { appId = app.Id }, _btcPayServerOptions.Value.RootPath)!); } } } diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index 6d9b8bc0a..b83bcac18 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -31,7 +31,7 @@ namespace BTCPayServer.Services.Apps { public class AppService { - private readonly IEnumerable _apps; + private readonly Dictionary _appTypes; readonly ApplicationDbContextFactory _ContextFactory; private readonly InvoiceRepository _InvoiceRepository; readonly CurrencyNameTable _Currencies; @@ -40,7 +40,7 @@ namespace BTCPayServer.Services.Apps private readonly HtmlSanitizer _HtmlSanitizer; public CurrencyNameTable Currencies => _Currencies; public AppService( - IEnumerable apps, + IEnumerable apps, ApplicationDbContextFactory contextFactory, InvoiceRepository invoiceRepository, CurrencyNameTable currencies, @@ -48,7 +48,7 @@ namespace BTCPayServer.Services.Apps StoreRepository storeRepository, HtmlSanitizer htmlSanitizer) { - _apps = apps; + _appTypes = apps.ToDictionary(a => a.Type, a=> a); _ContextFactory = contextFactory; _InvoiceRepository = invoiceRepository; _Currencies = currencies; @@ -56,40 +56,33 @@ namespace BTCPayServer.Services.Apps _HtmlSanitizer = htmlSanitizer; _displayFormatter = displayFormatter; } - +#nullable enable public Dictionary GetAvailableAppTypes() { - return _apps.ToDictionary(app => app.Type, app => app.Description); - } - - public Task ConfigureLink(AppData app, string vmSelectedAppType) - { - return GetAppForType(vmSelectedAppType).ConfigureLink(app); + return _appTypes.ToDictionary(app => app.Key, app => app.Value.Description); } - private IApp GetAppForType(string appType) + public AppBaseType? GetAppType(string appType) { - return _apps.First(app => app.Type == appType); + _appTypes.TryGetValue(appType, out var a); + return a; } - - public async Task GetInfo(string appId) + + public async Task GetInfo(string appId) { var appData = await GetApp(appId, null); if (appData is null) - { return null; - } - var app = GetAppForType(appData.AppType); - if (app is null) - { + var appType = GetAppType(appData.AppType); + if (appType is null) return null; - } - - return app.GetInfo(appData); + return appType.GetInfo(appData); } - + public async Task> GetItemStats(AppData appData) { + if (GetAppType(appData.AppType) is not IHasItemStatsAppType salesType) + throw new InvalidOperationException("This app isn't a SalesAppBaseType"); var paidInvoices = await GetInvoicesForApp(_InvoiceRepository,appData, null, new [] { @@ -97,7 +90,7 @@ namespace BTCPayServer.Services.Apps InvoiceState.ToString(InvoiceStatusLegacy.Confirmed), InvoiceState.ToString(InvoiceStatusLegacy.Complete) }); - return await GetAppForType(appData.AppType).GetItemStats(appData, paidInvoices); + return await salesType.GetItemStats(appData, paidInvoices); } public static Task GetSalesStatswithPOSItems(ViewPointOfSaleViewModel.Item[] items, @@ -136,6 +129,8 @@ namespace BTCPayServer.Services.Apps public async Task GetSalesStats(AppData app, int numberOfDays = 7) { + if (GetAppType(app.AppType) is not IHasSaleStatsAppType salesType) + throw new InvalidOperationException("This app isn't a SalesAppBaseType"); var paidInvoices = await GetInvoicesForApp(_InvoiceRepository, app, DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays), new [] { @@ -143,12 +138,13 @@ namespace BTCPayServer.Services.Apps InvoiceState.ToString(InvoiceStatusLegacy.Confirmed), InvoiceState.ToString(InvoiceStatusLegacy.Complete) }); - return await GetAppForType(app.AppType).GetSalesStats(app, paidInvoices, numberOfDays); + + return await salesType.GetSalesStats(app, paidInvoices, numberOfDays); } public class InvoiceStatsItem { - public string ItemCode { get; set; } + public string ItemCode { get; set; } = string.Empty; public decimal FiatPrice { get; set; } public DateTime Date { get; set; } } @@ -204,8 +200,8 @@ namespace BTCPayServer.Services.Apps public static string GetAppOrderId(string appType, string appId) => appType switch { - CrowdfundApp.AppType => $"crowdfund-app_{appId}", - PointOfSaleApp.AppType => $"pos-app_{appId}", + CrowdfundAppType.AppType => $"crowdfund-app_{appId}", + PointOfSaleAppType.AppType => $"pos-app_{appId}", _ => $"{appType}_{appId}" }; @@ -215,7 +211,7 @@ namespace BTCPayServer.Services.Apps return invoice.GetInternalTags("APP#"); } - public static async Task GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[] status = null) + public static async Task GetInvoicesForApp(InvoiceRepository invoiceRepository, AppData appData, DateTimeOffset? startDate = null, string[]? status = null) { var invoices = await invoiceRepository.GetInvoices(new InvoiceQuery { @@ -252,7 +248,7 @@ namespace BTCPayServer.Services.Apps return await ctx.SaveChangesAsync() == 1; } - public async Task GetAllApps(string userId, bool allowNoUser = false, string storeId = null) + public async Task GetAllApps(string? userId, bool allowNoUser = false, string? storeId = null) { await using var ctx = _ContextFactory.CreateContext(); var listApps = await ctx.UserStore @@ -294,7 +290,7 @@ namespace BTCPayServer.Services.Apps string style; switch (appType) { - case PointOfSaleApp.AppType: + case PointOfSaleAppType.AppType: var settings = app.GetSettings(); string posViewStyle = (settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView).ToString(); style = typeof(PosViewType).DisplayName(posViewStyle); @@ -328,7 +324,7 @@ namespace BTCPayServer.Services.Apps return await query.ToListAsync(); } - public async Task GetApp(string appId, string appType, bool includeStore = false) + public async Task GetApp(string appId, string? appType, bool includeStore = false) { await using var ctx = _ContextFactory.CreateContext(); var query = ctx.Apps @@ -342,7 +338,7 @@ namespace BTCPayServer.Services.Apps return await query.FirstOrDefaultAsync(); } - public Task GetStore(AppData app) + public Task GetStore(AppData app) { return _storeRepository.FindStore(app.StoreDataId); } @@ -354,7 +350,7 @@ namespace BTCPayServer.Services.Apps { var itemNode = new YamlMappingNode(); itemNode.Add("title", new YamlScalarNode(item.Title)); - if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) + if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup && item.Price.Value is not null) itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant())); if (!string.IsNullOrEmpty(item.Description)) { @@ -436,8 +432,11 @@ namespace BTCPayServer.Services.Apps case "false": case null: price.Type = ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed; - price.Value = decimal.Parse(pValue.Value.Value, CultureInfo.InvariantCulture); - price.Formatted = displayFormatter.Currency(price.Value.Value, currency, DisplayFormatter.CurrencyFormat.Symbol); + if (pValue?.Value.Value is not null) + { + price.Value = decimal.Parse(pValue.Value.Value, CultureInfo.InvariantCulture); + price.Formatted = displayFormatter.Currency(price.Value.Value, currency, DisplayFormatter.CurrencyFormat.Symbol); + } break; } @@ -464,13 +463,13 @@ namespace BTCPayServer.Services.Apps { return Parse(htmlSanitizer, displayFormatter, template, currency).Where(c => !c.Disabled).ToArray(); } - - +#nullable restore private class PosHolder { private readonly HtmlSanitizer _htmlSanitizer; - public PosHolder(HtmlSanitizer htmlSanitizer) + public PosHolder( + HtmlSanitizer htmlSanitizer) { _htmlSanitizer = htmlSanitizer; } @@ -506,8 +505,8 @@ namespace BTCPayServer.Services.Apps public string Key { get; set; } public YamlScalarNode Value { get; set; } } - - public async Task GetAppDataIfOwner(string userId, string appId, string type = null) +#nullable enable + public async Task GetAppDataIfOwner(string userId, string appId, string? type = null) { if (userId == null || appId == null) return null; @@ -542,7 +541,7 @@ namespace BTCPayServer.Services.Apps await ctx.SaveChangesAsync(); } - private static bool TryParseJson(string json, out JObject result) + private static bool TryParseJson(string json, [MaybeNullWhen(false)] out JObject result) { result = null; try @@ -584,7 +583,7 @@ namespace BTCPayServer.Services.Apps public async Task SetDefaultSettings(AppData appData, string defaultCurrency) { - var app = GetAppForType(appData.AppType); + var app = GetAppType(appData.AppType); if (app is null) { appData.SetSettings(null); @@ -597,19 +596,10 @@ namespace BTCPayServer.Services.Apps public async Task ViewLink(AppData app) { - var appType = GetAppForType(app.AppType); + var appType = GetAppType(app.AppType); return await appType?.ViewLink(app)!; } #nullable restore - public bool SupportsSalesStats(AppData app) - { - return GetAppForType(app.AppType).SupportsSalesStats; - } - - public bool SupportsItemStats(AppData app) - { - return GetAppForType(app.AppType).SupportsItemStats; - } } public class ItemStats diff --git a/BTCPayServer/Services/Apps/AppType.cs b/BTCPayServer/Services/Apps/AppType.cs index dfa9b2bc4..b843bf0e4 100644 --- a/BTCPayServer/Services/Apps/AppType.cs +++ b/BTCPayServer/Services/Apps/AppType.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; @@ -6,18 +7,22 @@ using BTCPayServer.Services.Invoices; namespace BTCPayServer.Services.Apps { - public interface IApp + public abstract class AppBaseType + { + public string Description { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public abstract Task GetInfo(AppData appData); + public abstract Task ConfigureLink(AppData app); + public abstract Task ViewLink(AppData app); + public abstract Task SetDefaultSettings(AppData appData, string defaultCurrency); + } + public interface IHasSaleStatsAppType { - public string Description { get; } - public string Type { get; } - public bool SupportsSalesStats { get; } - public bool SupportsItemStats { get; } - Task ConfigureLink(AppData app); - Task ViewLink(AppData app); - Task SetDefaultSettings(AppData appData, string defaultCurrency); Task GetSalesStats(AppData app, InvoiceEntity[] paidInvoices, int numberOfDays); + } + public interface IHasItemStatsAppType + { Task> GetItemStats(AppData appData, InvoiceEntity[] invoiceEntities); - Task GetInfo(AppData appData); } public enum RequiresRefundEmail diff --git a/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml b/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml index b841fb1b0..9cdf8f0a9 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml @@ -8,7 +8,7 @@ @{ var store = Context.GetStoreData(); } -@if (store != null && Model.AppType == CrowdfundApp.AppType) +@if (store != null && Model.AppType == CrowdfundAppType.AppType) {