mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Greenfield: Refactor app endpoints (#6051)
* Greenfield: Refactor app endpoints - Do not change unset data - Clean up difference between request (template) and data (items/perks) - Add missing properties (form id and custom tip percentage) - Update docs * Revert ToSettings changes in GreenfieldAppsController
This commit is contained in:
@@ -17,7 +17,8 @@ using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
using CrowdfundResetEvery = BTCPayServer.Client.Models.CrowdfundResetEvery;
|
||||
using PosViewType = BTCPayServer.Client.Models.PosViewType;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
@@ -47,19 +48,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
[HttpPost("~/api/v1/stores/{storeId}/apps/crowdfund")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> CreateCrowdfundApp(string storeId, CreateCrowdfundAppRequest request)
|
||||
public async Task<IActionResult> CreateCrowdfundApp(string storeId, CrowdfundAppRequest request)
|
||||
{
|
||||
var store = await _storeRepository.FindStore(storeId);
|
||||
if (store == null)
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
|
||||
// This is not obvious but we must have a non-null currency or else request validation may work incorrectly
|
||||
request.TargetCurrency = request.TargetCurrency ?? store.GetStoreBlob().DefaultCurrency;
|
||||
// This is not obvious, but we must have a non-null currency or else request validation may not work correctly
|
||||
request.TargetCurrency ??= store.GetStoreBlob().DefaultCurrency;
|
||||
|
||||
var validationResult = ValidateCrowdfundAppRequest(request);
|
||||
if (validationResult != null)
|
||||
ValidateAppRequest(request);
|
||||
ValidateCrowdfundAppRequest(request);
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return validationResult;
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var appData = new AppData
|
||||
@@ -70,7 +72,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Archived = request.Archived ?? false
|
||||
};
|
||||
|
||||
appData.SetSettings(ToCrowdfundSettings(request));
|
||||
var settings = ToCrowdfundSettings(request, new CrowdfundSettings { Title = request.Title ?? request.AppName });
|
||||
appData.SetSettings(settings);
|
||||
|
||||
await _appService.UpdateOrCreateApp(appData);
|
||||
|
||||
@@ -79,19 +82,20 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
[HttpPost("~/api/v1/stores/{storeId}/apps/pos")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> CreatePointOfSaleApp(string storeId, CreatePointOfSaleAppRequest request)
|
||||
public async Task<IActionResult> CreatePointOfSaleApp(string storeId, PointOfSaleAppRequest request)
|
||||
{
|
||||
var store = await _storeRepository.FindStore(storeId);
|
||||
if (store == null)
|
||||
return this.CreateAPIError(404, "store-not-found", "The store was not found");
|
||||
|
||||
// This is not obvious but we must have a non-null currency or else request validation may work incorrectly
|
||||
request.Currency = request.Currency ?? store.GetStoreBlob().DefaultCurrency;
|
||||
// This is not obvious, but we must have a non-null currency or else request validation may not work correctly
|
||||
request.Currency ??= store.GetStoreBlob().DefaultCurrency;
|
||||
|
||||
var validationResult = ValidatePOSAppRequest(request);
|
||||
if (validationResult != null)
|
||||
ValidateAppRequest(request);
|
||||
ValidatePOSAppRequest(request);
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return validationResult;
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var appData = new AppData
|
||||
@@ -102,7 +106,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Archived = request.Archived ?? false
|
||||
};
|
||||
|
||||
appData.SetSettings(ToPointOfSaleSettings(request));
|
||||
var settings = ToPointOfSaleSettings(request, new PointOfSaleSettings { Title = request.Title ?? request.AppName });
|
||||
appData.SetSettings(settings);
|
||||
|
||||
await _appService.UpdateOrCreateApp(appData);
|
||||
|
||||
@@ -111,7 +116,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
[HttpPut("~/api/v1/apps/pos/{appId}")]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> UpdatePointOfSaleApp(string appId, CreatePointOfSaleAppRequest request)
|
||||
public async Task<IActionResult> UpdatePointOfSaleApp(string appId, PointOfSaleAppRequest request)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
@@ -121,21 +126,28 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
|
||||
// This is not obvious but we must have a non-null currency or else request validation may work incorrectly
|
||||
request.Currency = request.Currency ?? settings.Currency;
|
||||
// This is not obvious, but we must have a non-null currency or else request validation may not work correctly
|
||||
request.Currency ??= settings.Currency;
|
||||
|
||||
var validationResult = ValidatePOSAppRequest(request);
|
||||
if (validationResult != null)
|
||||
ValidatePOSAppRequest(request);
|
||||
if (!string.IsNullOrEmpty(request.AppName))
|
||||
{
|
||||
return validationResult;
|
||||
ValidateAppRequest(request);
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
app.Name = request.AppName;
|
||||
if (!string.IsNullOrEmpty(request.AppName))
|
||||
{
|
||||
app.Name = request.AppName;
|
||||
}
|
||||
if (request.Archived != null)
|
||||
{
|
||||
app.Archived = request.Archived.Value;
|
||||
}
|
||||
app.SetSettings(ToPointOfSaleSettings(request));
|
||||
app.SetSettings(ToPointOfSaleSettings(request, settings));
|
||||
|
||||
await _appService.UpdateOrCreateApp(app);
|
||||
|
||||
@@ -218,11 +230,12 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found");
|
||||
}
|
||||
|
||||
private CrowdfundSettings ToCrowdfundSettings(CreateCrowdfundAppRequest request)
|
||||
private CrowdfundSettings ToCrowdfundSettings(CrowdfundAppRequest request, CrowdfundSettings settings)
|
||||
{
|
||||
var parsedSounds = ValidateStringArray(request.Sounds);
|
||||
var parsedColors = ValidateStringArray(request.AnimationColors);
|
||||
|
||||
Enum.TryParse<BTCPayServer.Services.Apps.CrowdfundResetEvery>(request.ResetEvery.ToString(), true, out var resetEvery);
|
||||
|
||||
return new CrowdfundSettings
|
||||
{
|
||||
Title = request.Title?.Trim() ?? request.AppName,
|
||||
@@ -244,32 +257,36 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
SoundsEnabled = request.SoundsEnabled ?? parsedSounds != null,
|
||||
AnimationsEnabled = request.AnimationsEnabled ?? parsedColors != null,
|
||||
ResetEveryAmount = request.ResetEveryAmount ?? 1,
|
||||
ResetEvery = (Services.Apps.CrowdfundResetEvery)request.ResetEvery,
|
||||
ResetEvery = resetEvery,
|
||||
DisplayPerksValue = request.DisplayPerksValue ?? false,
|
||||
DisplayPerksRanking = request.DisplayPerksRanking ?? false,
|
||||
SortPerksByPopularity = request.SortPerksByPopularity ?? false,
|
||||
Sounds = parsedSounds ?? new CrowdfundSettings().Sounds,
|
||||
AnimationColors = parsedColors ?? new CrowdfundSettings().AnimationColors
|
||||
AnimationColors = parsedColors ?? new CrowdfundSettings().AnimationColors,
|
||||
FormId = request.FormId
|
||||
};
|
||||
}
|
||||
|
||||
private PointOfSaleSettings ToPointOfSaleSettings(CreatePointOfSaleAppRequest request)
|
||||
private PointOfSaleSettings ToPointOfSaleSettings(PointOfSaleAppRequest request, PointOfSaleSettings settings)
|
||||
{
|
||||
Enum.TryParse<BTCPayServer.Plugins.PointOfSale.PosViewType>(request.DefaultView.ToString(), true, out var defaultView);
|
||||
|
||||
return new PointOfSaleSettings
|
||||
{
|
||||
Title = request.Title ?? request.AppName,
|
||||
DefaultView = (PosViewType)request.DefaultView,
|
||||
ShowItems = request.ShowItems,
|
||||
ShowCustomAmount = request.ShowCustomAmount,
|
||||
ShowDiscount = request.ShowDiscount,
|
||||
ShowSearch = request.ShowSearch,
|
||||
ShowCategories = request.ShowCategories,
|
||||
EnableTips = request.EnableTips,
|
||||
DefaultView = defaultView,
|
||||
ShowItems = request.ShowItems ?? false,
|
||||
ShowCustomAmount = request.ShowCustomAmount ?? false,
|
||||
ShowDiscount = request.ShowDiscount ?? false,
|
||||
ShowSearch = request.ShowSearch ?? false,
|
||||
ShowCategories = request.ShowCategories ?? false,
|
||||
EnableTips = request.EnableTips ?? false,
|
||||
Currency = request.Currency,
|
||||
Template = request.Template != null ? AppService.SerializeTemplate(AppService.Parse(request.Template)) : null,
|
||||
ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
|
||||
CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
|
||||
CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
|
||||
CustomTipPercentages = request.CustomTipPercentages,
|
||||
NotificationUrl = request.NotificationUrl,
|
||||
RedirectUrl = request.RedirectUrl,
|
||||
Description = request.Description,
|
||||
@@ -278,27 +295,27 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
};
|
||||
}
|
||||
|
||||
private AppDataBase ToModel(AppData appData)
|
||||
private AppBaseData ToModel(AppData appData)
|
||||
{
|
||||
return new AppDataBase
|
||||
return new AppBaseData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
AppName = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
Created = appData.Created,
|
||||
};
|
||||
}
|
||||
|
||||
private AppDataBase ToModel(Models.AppViewModels.ListAppsViewModel.ListAppViewModel appData)
|
||||
private AppBaseData ToModel(Models.AppViewModels.ListAppsViewModel.ListAppViewModel appData)
|
||||
{
|
||||
return new AppDataBase
|
||||
return new AppBaseData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.AppName,
|
||||
AppName = appData.AppName,
|
||||
StoreId = appData.StoreId,
|
||||
Created = appData.Created,
|
||||
};
|
||||
@@ -307,17 +324,18 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private PointOfSaleAppData ToPointOfSaleModel(AppData appData)
|
||||
{
|
||||
var settings = appData.GetSettings<PointOfSaleSettings>();
|
||||
|
||||
Enum.TryParse<PosViewType>(settings.DefaultView.ToString(), true, out var defaultView);
|
||||
|
||||
return new PointOfSaleAppData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
AppName = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
Created = appData.Created,
|
||||
Title = settings.Title,
|
||||
DefaultView = settings.DefaultView.ToString(),
|
||||
DefaultView = defaultView,
|
||||
ShowItems = settings.ShowItems,
|
||||
ShowCustomAmount = settings.ShowCustomAmount,
|
||||
ShowDiscount = settings.ShowDiscount,
|
||||
@@ -325,28 +343,30 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ShowCategories = settings.ShowCategories,
|
||||
EnableTips = settings.EnableTips,
|
||||
Currency = settings.Currency,
|
||||
Items = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.Template),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
)
|
||||
),
|
||||
FixedAmountPayButtonText = settings.ButtonText,
|
||||
CustomAmountPayButtonText = settings.CustomButtonText,
|
||||
TipText = settings.CustomTipText,
|
||||
CustomTipPercentages = settings.CustomTipPercentages,
|
||||
FormId = settings.FormId,
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
RedirectUrl = settings.RedirectUrl,
|
||||
Description = settings.Description,
|
||||
RedirectAutomatically = settings.RedirectAutomatically ?? false,
|
||||
RedirectAutomatically = settings.RedirectAutomatically,
|
||||
Items = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.Template),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver =
|
||||
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
private IActionResult? ValidatePOSAppRequest(CreatePointOfSaleAppRequest request)
|
||||
private void ValidatePOSAppRequest(PointOfSaleAppRequest request)
|
||||
{
|
||||
var validationResult = ValidateCreateAppRequest(request);
|
||||
if (request.Currency != null && _currencies.GetCurrencyData(request.Currency, false) == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Currency), "Invalid currency");
|
||||
@@ -364,25 +384,19 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.Template), "Invalid template");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
validationResult = this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
private CrowdfundAppData ToCrowdfundModel(AppData appData)
|
||||
{
|
||||
var settings = appData.GetSettings<CrowdfundSettings>();
|
||||
Enum.TryParse<CrowdfundResetEvery>(settings.ResetEvery.ToString(), true, out var resetEvery);
|
||||
|
||||
return new CrowdfundAppData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
AppName = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
Created = appData.Created,
|
||||
Title = settings.Title,
|
||||
@@ -396,6 +410,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
MainImageUrl = settings.MainImageUrl,
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
Tagline = settings.Tagline,
|
||||
DisqusEnabled = settings.DisqusEnabled,
|
||||
DisqusShortname = settings.DisqusShortname,
|
||||
SoundsEnabled = settings.SoundsEnabled,
|
||||
AnimationsEnabled = settings.AnimationsEnabled,
|
||||
ResetEveryAmount = settings.ResetEveryAmount,
|
||||
ResetEvery = resetEvery,
|
||||
DisplayPerksValue = settings.DisplayPerksValue,
|
||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = settings.Sounds,
|
||||
AnimationColors = settings.AnimationColors,
|
||||
Perks = JsonConvert.DeserializeObject(
|
||||
JsonConvert.SerializeObject(
|
||||
AppService.Parse(settings.PerksTemplate),
|
||||
@@ -404,18 +429,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
)
|
||||
),
|
||||
DisqusEnabled = settings.DisqusEnabled,
|
||||
DisqusShortname = settings.DisqusShortname,
|
||||
SoundsEnabled = settings.SoundsEnabled,
|
||||
AnimationsEnabled = settings.AnimationsEnabled,
|
||||
ResetEveryAmount = settings.ResetEveryAmount,
|
||||
ResetEvery = settings.ResetEvery.ToString(),
|
||||
DisplayPerksValue = settings.DisplayPerksValue,
|
||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = settings.Sounds,
|
||||
AnimationColors = settings.AnimationColors
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,34 +449,38 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return arr.Select(s => s.Trim()).ToArray();
|
||||
}
|
||||
|
||||
private IActionResult? ValidateCrowdfundAppRequest(CreateCrowdfundAppRequest request)
|
||||
private void ValidateCrowdfundAppRequest(CrowdfundAppRequest request)
|
||||
{
|
||||
var validationResult = ValidateCreateAppRequest(request);
|
||||
if (request.TargetCurrency != null && _currencies.GetCurrencyData(request.TargetCurrency, false) == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.TargetCurrency), "Invalid currency");
|
||||
}
|
||||
|
||||
try
|
||||
if (request.PerksTemplate != null)
|
||||
{
|
||||
// Just checking if we can serialize
|
||||
AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PerksTemplate), "Invalid template");
|
||||
try
|
||||
{
|
||||
// Just checking if we can serialize
|
||||
AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate));
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.PerksTemplate), "Invalid template");
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ResetEvery != Client.Models.CrowdfundResetEvery.Never && request.StartDate == null)
|
||||
if (request.ResetEvery.HasValue && request.ResetEvery != CrowdfundResetEvery.Never)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.StartDate), "A start date is needed when the goal resets every X amount of time");
|
||||
if (request.StartDate == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.StartDate), "A start date is needed when the goal resets every X amount of time");
|
||||
}
|
||||
if (request.ResetEveryAmount <= 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ResetEveryAmount), "You must reset the goal at a minimum of 1");
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ResetEvery != Client.Models.CrowdfundResetEvery.Never && request.ResetEveryAmount <= 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ResetEveryAmount), "You must reset the goal at a minimum of 1");
|
||||
}
|
||||
|
||||
|
||||
if (request.Sounds != null && ValidateStringArray(request.Sounds) == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Sounds), "Sounds must be a non-empty array of non-empty strings");
|
||||
@@ -473,36 +491,22 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
ModelState.AddModelError(nameof(request.AnimationColors), "Animation colors must be a non-empty array of non-empty strings");
|
||||
}
|
||||
|
||||
if (request.StartDate != null && request.EndDate != null && DateTimeOffset.Compare((DateTimeOffset)request.StartDate, (DateTimeOffset)request.EndDate!) > 0)
|
||||
if (request is { StartDate: not null, EndDate: not null } && DateTimeOffset.Compare((DateTimeOffset)request.StartDate, (DateTimeOffset)request.EndDate!) > 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.EndDate), "End date cannot be before start date");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
validationResult = this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
private IActionResult? ValidateCreateAppRequest(CreateAppRequest request)
|
||||
private void ValidateAppRequest(IAppRequest? request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.AppName))
|
||||
if (string.IsNullOrEmpty(request?.AppName))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.AppName), "App name is missing");
|
||||
}
|
||||
else if (request.AppName.Length < 1 || request.AppName.Length > 50)
|
||||
else if (request.AppName.Length is < 1 or > 50)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.AppName), "Name can only be between 1 and 50 characters");
|
||||
ModelState.AddModelError(nameof(request.AppName), "App name can only be between 1 and 50 characters");
|
||||
}
|
||||
|
||||
return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user