2020-05-23 21:13:18 +02:00
using System ;
2020-03-24 16:18:43 +01:00
using System.Collections.Generic ;
using System.Linq ;
2020-03-31 14:43:35 +02:00
using System.Threading.Tasks ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Constants ;
2020-03-31 10:47:13 +02:00
using BTCPayServer.Client ;
2020-04-01 08:21:54 +02:00
using BTCPayServer.Client.Models ;
2020-03-31 14:43:35 +02:00
using BTCPayServer.Data ;
2020-03-24 16:18:43 +01:00
using BTCPayServer.Security ;
2020-03-31 14:43:35 +02:00
using BTCPayServer.Services.Stores ;
2020-03-24 16:18:43 +01:00
using Microsoft.AspNetCore.Authorization ;
2020-06-30 08:26:19 +02:00
using Microsoft.AspNetCore.Cors ;
2020-03-31 14:43:35 +02:00
using Microsoft.AspNetCore.Identity ;
2020-03-24 16:18:43 +01:00
using Microsoft.AspNetCore.Mvc ;
2020-03-31 10:47:13 +02:00
namespace BTCPayServer.Controllers.GreenField
2020-03-24 16:18:43 +01:00
{
[ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
2020-06-30 08:26:19 +02:00
[EnableCors(CorsPolicies.All)]
2020-04-27 13:13:20 +02:00
public class GreenFieldController : ControllerBase
2020-03-24 16:18:43 +01:00
{
2020-03-31 14:43:35 +02:00
private readonly StoreRepository _storeRepository ;
private readonly UserManager < ApplicationUser > _userManager ;
2020-04-27 13:13:20 +02:00
public GreenFieldController ( StoreRepository storeRepository , UserManager < ApplicationUser > userManager )
2020-03-31 14:43:35 +02:00
{
_storeRepository = storeRepository ;
_userManager = userManager ;
}
2020-03-24 16:18:43 +01:00
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores")]
public ActionResult < IEnumerable < Client . Models . StoreData > > GetStores ( )
{
2020-06-28 10:55:27 +02:00
var stores = HttpContext . GetStoresData ( ) ;
return Ok ( stores . Select ( FromModel ) ) ;
2020-03-24 16:18:43 +01:00
}
2020-06-28 10:55:27 +02:00
2020-03-31 10:47:13 +02:00
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}")]
public ActionResult < Client . Models . StoreData > GetStore ( string storeId )
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
return NotFound ( ) ;
}
return Ok ( FromModel ( store ) ) ;
}
2020-06-28 10:55:27 +02:00
2020-03-31 14:43:35 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}")]
2020-06-03 11:58:49 +02:00
public async Task < IActionResult > RemoveStore ( string storeId )
2020-03-31 14:43:35 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
return NotFound ( ) ;
}
if ( ! _storeRepository . CanDeleteStores ( ) )
{
2020-06-08 16:40:58 +02:00
return this . CreateAPIError ( "unsupported" ,
2020-06-03 11:58:49 +02:00
"BTCPay Server is using a database server that does not allow you to remove stores." ) ;
2020-03-31 14:43:35 +02:00
}
await _storeRepository . RemoveStore ( storeId , _userManager . GetUserId ( User ) ) ;
return Ok ( ) ;
}
2020-06-28 10:55:27 +02:00
2020-04-01 08:21:54 +02:00
[HttpPost("~/api/v1/stores")]
2020-06-27 08:34:03 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettingsUnscoped, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
2020-04-30 16:43:16 +02:00
public async Task < IActionResult > CreateStore ( CreateStoreRequest request )
2020-04-01 08:21:54 +02:00
{
2020-04-30 16:43:16 +02:00
var validationResult = Validate ( request ) ;
if ( validationResult ! = null )
{
return validationResult ;
}
var store = new Data . StoreData ( ) ;
ToModel ( request , store ) ;
await _storeRepository . CreateStore ( _userManager . GetUserId ( User ) , store ) ;
2020-04-01 08:21:54 +02:00
return Ok ( FromModel ( store ) ) ;
}
2020-06-28 10:55:27 +02:00
2020-04-27 13:13:20 +02:00
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/stores/{storeId}")]
2020-04-30 16:43:16 +02:00
public async Task < IActionResult > UpdateStore ( string storeId , UpdateStoreRequest request )
2020-04-27 13:13:20 +02:00
{
var store = HttpContext . GetStoreData ( ) ;
if ( store = = null )
{
return NotFound ( ) ;
}
2020-04-30 16:43:16 +02:00
var validationResult = Validate ( request ) ;
if ( validationResult ! = null )
{
return validationResult ;
}
2020-04-27 13:13:20 +02:00
2020-04-30 16:43:16 +02:00
ToModel ( request , store ) ;
2020-04-27 13:13:20 +02:00
await _storeRepository . UpdateStore ( store ) ;
return Ok ( FromModel ( store ) ) ;
}
2020-04-01 08:21:54 +02:00
2020-03-24 16:18:43 +01:00
private static Client . Models . StoreData FromModel ( Data . StoreData data )
{
2020-05-23 21:13:18 +02:00
var storeBlob = data . GetStoreBlob ( ) ;
2020-03-24 16:18:43 +01:00
return new Client . Models . StoreData ( )
{
2020-06-28 10:55:27 +02:00
Id = data . Id ,
2020-05-23 21:13:18 +02:00
Name = data . StoreName ,
Website = data . StoreWebsite ,
SpeedPolicy = data . SpeedPolicy ,
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
//blob
//we do not include DefaultCurrencyPairs,Spread, PreferredExchange, RateScripting, RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
NetworkFeeMode = storeBlob . NetworkFeeMode ,
RequiresRefundEmail = storeBlob . RequiresRefundEmail ,
2020-11-09 08:11:03 +01:00
LightningAmountInSatoshi = storeBlob . LightningAmountInSatoshi ,
LightningPrivateRouteHints = storeBlob . LightningPrivateRouteHints ,
OnChainWithLnInvoiceFallback = storeBlob . OnChainWithLnInvoiceFallback ,
RedirectAutomatically = storeBlob . RedirectAutomatically ,
2020-05-23 21:13:18 +02:00
ShowRecommendedFee = storeBlob . ShowRecommendedFee ,
RecommendedFeeBlockTarget = storeBlob . RecommendedFeeBlockTarget ,
DefaultLang = storeBlob . DefaultLang ,
2020-08-26 07:01:39 +02:00
MonitoringExpiration = storeBlob . MonitoringExpiration ,
InvoiceExpiration = storeBlob . InvoiceExpiration ,
2020-05-23 21:13:18 +02:00
CustomLogo = storeBlob . CustomLogo ,
CustomCSS = storeBlob . CustomCSS ,
HtmlTitle = storeBlob . HtmlTitle ,
2020-05-31 04:42:49 +02:00
AnyoneCanCreateInvoice = storeBlob . AnyoneCanInvoice ,
2020-05-23 21:13:18 +02:00
LightningDescriptionTemplate = storeBlob . LightningDescriptionTemplate ,
PaymentTolerance = storeBlob . PaymentTolerance ,
2020-11-09 08:11:03 +01:00
PayJoinEnabled = storeBlob . PayJoinEnabled
2020-03-24 16:18:43 +01:00
} ;
}
2020-05-23 21:13:18 +02:00
private static void ToModel ( StoreBaseData restModel , Data . StoreData model )
2020-04-22 14:42:30 +02:00
{
2020-05-23 21:13:18 +02:00
var blob = model . GetStoreBlob ( ) ;
model . StoreName = restModel . Name ;
2020-04-30 16:43:16 +02:00
model . StoreName = restModel . Name ;
2020-05-23 21:13:18 +02:00
model . StoreWebsite = restModel . Website ;
model . SpeedPolicy = restModel . SpeedPolicy ;
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
//blob
//we do not include DefaultCurrencyPairs;Spread; PreferredExchange; RateScripting; RateScript in this model and instead opt to set it in stores/storeid/rates endpoints
//we do not include CoinSwitchSettings in this model and instead opt to set it in stores/storeid/coinswitch endpoints
//we do not include ExcludedPaymentMethods in this model and instead opt to set it in stores/storeid/payment-methods endpoints
//we do not include EmailSettings in this model and instead opt to set it in stores/storeid/email endpoints
//we do not include OnChainMinValue and LightningMaxValue because moving the CurrencyValueJsonConverter to the Client csproj is hard and requires a refactor (#1571 & #1572)
blob . NetworkFeeMode = restModel . NetworkFeeMode ;
blob . RequiresRefundEmail = restModel . RequiresRefundEmail ;
2020-11-09 08:11:03 +01:00
blob . LightningAmountInSatoshi = restModel . LightningAmountInSatoshi ;
blob . LightningPrivateRouteHints = restModel . LightningPrivateRouteHints ;
blob . OnChainWithLnInvoiceFallback = restModel . OnChainWithLnInvoiceFallback ;
blob . RedirectAutomatically = restModel . RedirectAutomatically ;
2020-05-23 21:13:18 +02:00
blob . ShowRecommendedFee = restModel . ShowRecommendedFee ;
blob . RecommendedFeeBlockTarget = restModel . RecommendedFeeBlockTarget ;
blob . DefaultLang = restModel . DefaultLang ;
2020-08-26 07:01:39 +02:00
blob . MonitoringExpiration = restModel . MonitoringExpiration ;
blob . InvoiceExpiration = restModel . InvoiceExpiration ;
2020-05-23 21:13:18 +02:00
blob . CustomLogo = restModel . CustomLogo ;
blob . CustomCSS = restModel . CustomCSS ;
blob . HtmlTitle = restModel . HtmlTitle ;
2020-05-31 04:42:49 +02:00
blob . AnyoneCanInvoice = restModel . AnyoneCanCreateInvoice ;
2020-05-23 21:13:18 +02:00
blob . LightningDescriptionTemplate = restModel . LightningDescriptionTemplate ;
blob . PaymentTolerance = restModel . PaymentTolerance ;
blob . PayJoinEnabled = restModel . PayJoinEnabled ;
model . SetStoreBlob ( blob ) ;
2020-04-30 16:43:16 +02:00
}
private IActionResult Validate ( StoreBaseData request )
{
2020-05-23 21:13:18 +02:00
if ( request is null )
{
return BadRequest ( ) ;
}
2020-06-28 10:55:27 +02:00
2020-05-23 21:13:18 +02:00
if ( string . IsNullOrEmpty ( request . Name ) )
2020-04-30 16:43:16 +02:00
ModelState . AddModelError ( nameof ( request . Name ) , "Name is missing" ) ;
2020-06-28 10:55:27 +02:00
else if ( request . Name . Length < 1 | | request . Name . Length > 50 )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . Name ) , "Name can only be between 1 and 50 characters" ) ;
if ( ! string . IsNullOrEmpty ( request . Website ) & & ! Uri . TryCreate ( request . Website , UriKind . Absolute , out _ ) )
{
ModelState . AddModelError ( nameof ( request . Website ) , "Website is not a valid url" ) ;
}
2020-06-28 10:55:27 +02:00
if ( request . InvoiceExpiration < TimeSpan . FromMinutes ( 1 ) & & request . InvoiceExpiration > TimeSpan . FromMinutes ( 60 * 24 * 24 ) )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . InvoiceExpiration ) , "InvoiceExpiration can only be between 1 and 34560 mins" ) ;
2020-06-28 10:55:27 +02:00
if ( request . MonitoringExpiration < TimeSpan . FromMinutes ( 10 ) & & request . MonitoringExpiration > TimeSpan . FromMinutes ( 60 * 24 * 24 ) )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . MonitoringExpiration ) , "InvoiceExpiration can only be between 10 and 34560 mins" ) ;
2020-06-28 10:55:27 +02:00
if ( request . PaymentTolerance < 0 & & request . PaymentTolerance > 100 )
2020-05-23 21:13:18 +02:00
ModelState . AddModelError ( nameof ( request . PaymentTolerance ) , "PaymentTolerance can only be between 0 and 100 percent" ) ;
2020-06-03 11:58:49 +02:00
2020-06-08 16:40:58 +02:00
return ! ModelState . IsValid ? this . CreateValidationError ( ModelState ) : null ;
2020-04-22 14:42:30 +02:00
}
2020-03-24 16:18:43 +01:00
}
}