diff --git a/BTCPayServer.Client/Models/CreateAppRequest.cs b/BTCPayServer.Client/Models/CreateAppRequest.cs new file mode 100644 index 000000000..08857902c --- /dev/null +++ b/BTCPayServer.Client/Models/CreateAppRequest.cs @@ -0,0 +1,9 @@ +namespace BTCPayServer.Client.Models +{ + public class CreateAppRequest + { + public string StoreId { get; set; } + public string AppName { get; set; } + public string AppType { get; set; } + } +} diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs new file mode 100644 index 000000000..ed047856a --- /dev/null +++ b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs @@ -0,0 +1,103 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Client; +using BTCPayServer.Client.Models; +using BTCPayServer.Data; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Stores; +using BTCPayServer.Abstractions.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace BTCPayServer.Controllers.Greenfield +{ + [ApiController] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [EnableCors(CorsPolicies.All)] + public class GreenfieldAppsController : ControllerBase + { + private readonly AppService _appService; + private readonly StoreRepository _storeRepository; + + public GreenfieldAppsController( + AppService appService, + StoreRepository storeRepository, + UserManager userManager, + BTCPayNetworkProvider btcPayNetworkProvider + ) + { + _appService = appService; + _storeRepository = storeRepository; + } + + [HttpPost("~/api/v1/apps")] + [Authorize(Policy = Policies.CanModifyStoreSettingsUnscoped, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + public async Task CreateApp(CreateAppRequest request) + { + var validationResult = await Validate(request); + if (validationResult != null) + { + return validationResult; + } + + var appData = new AppData + { + StoreDataId = request.StoreId, + Name = request.AppName, + AppType = request.AppType + }; + + var defaultCurrency = (await _storeRepository.FindStore(request.StoreId)).GetStoreBlob().DefaultCurrency; + Enum.TryParse(request.AppType, out AppType appType); + switch (appType) + { + case AppType.Crowdfund: + var emptyCrowdfund = new CrowdfundSettings { TargetCurrency = defaultCurrency }; + appData.SetSettings(emptyCrowdfund); + break; + case AppType.PointOfSale: + var empty = new PointOfSaleSettings { Currency = defaultCurrency }; + appData.SetSettings(empty); + break; + } + + await _appService.UpdateOrCreateApp(appData); + + return Ok(appData); + } + + async private Task Validate(CreateAppRequest request) + { + if (request is null) + { + return BadRequest(); + } + + if (!Enum.TryParse(request.AppType, out AppType appType)) + { + ModelState.AddModelError(nameof(request.AppType), "Invalid app type"); + } + + if (string.IsNullOrEmpty(request.AppName)) + { + ModelState.AddModelError(nameof(request.AppName), "App name is missing"); + } + else if (request.AppName.Length < 1 || request.AppName.Length > 50) + { + ModelState.AddModelError(nameof(request.AppName), "Name can only be between 1 and 50 characters"); + } + + var store = await _storeRepository.FindStore(request.StoreId); + if (store == null) + { + ModelState.AddModelError(nameof(request.StoreId), "Store with provided ID not found"); + } + + return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null; + } + } +}