mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Make sure model binder error are returning error 422, use DateTimeOffsetModelBinder
This commit is contained in:
@@ -12,19 +12,19 @@ namespace BTCPayServer.Client
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null,
|
||||
long? startDate = null,
|
||||
long? endDate = null,
|
||||
DateTimeOffset? startDate = null,
|
||||
DateTimeOffset? endDate = null,
|
||||
bool includeArchived = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
Dictionary<string, object> queryPayload = new Dictionary<string, object>();
|
||||
queryPayload.Add(nameof(includeArchived), includeArchived);
|
||||
|
||||
if (startDate != null)
|
||||
queryPayload.Add(nameof(startDate), startDate);
|
||||
if (startDate is DateTimeOffset s)
|
||||
queryPayload.Add(nameof(startDate), Utils.DateTimeToUnixTime(s));
|
||||
|
||||
if (endDate != null)
|
||||
queryPayload.Add(nameof(endDate), endDate);
|
||||
if (endDate is DateTimeOffset e)
|
||||
queryPayload.Add(nameof(endDate), Utils.DateTimeToUnixTime(e));
|
||||
|
||||
if (orderId != null)
|
||||
queryPayload.Add(nameof(orderId), orderId);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -69,6 +70,13 @@ namespace BTCPayServer.Client
|
||||
return JsonConvert.DeserializeObject<T>(str);
|
||||
}
|
||||
|
||||
public async Task<T> SendHttpRequest<T>(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
HttpMethod method = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var resp = await _httpClient.SendAsync(CreateHttpRequest(path, queryPayload, method), cancellationToken);
|
||||
return await HandleResponse<T>(resp);
|
||||
}
|
||||
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||
Dictionary<string, object> queryPayload = null,
|
||||
HttpMethod method = null)
|
||||
|
||||
@@ -966,8 +966,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
//list Filtered
|
||||
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddHours(-1)),
|
||||
NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddHours(1)));
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddHours(-1),
|
||||
DateTimeOffset.Now.AddHours(1));
|
||||
|
||||
Assert.NotNull(invoicesFiltered);
|
||||
Assert.Single(invoicesFiltered);
|
||||
@@ -975,11 +975,24 @@ namespace BTCPayServer.Tests
|
||||
|
||||
//list Yesterday
|
||||
var invoicesYesterday = await viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddDays(-2)),
|
||||
NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddDays(-1)));
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddDays(-2),
|
||||
DateTimeOffset.Now.AddDays(-1));
|
||||
Assert.NotNull(invoicesYesterday);
|
||||
Assert.Empty(invoicesYesterday);
|
||||
|
||||
// Error, startDate and endDate inverted
|
||||
await AssertValidationError(new[] { "startDate", "endDate" },
|
||||
() => viewOnly.GetInvoices(user.StoreId,
|
||||
orderId: null, status: null, DateTimeOffset.Now.AddDays(-1),
|
||||
DateTimeOffset.Now.AddDays(-2)));
|
||||
|
||||
await AssertValidationError(new[] { "startDate" },
|
||||
() => viewOnly.SendHttpRequest<Client.Models.InvoiceData[]>($"api/v1/stores/{user.StoreId}/invoices", new Dictionary<string, object>()
|
||||
{
|
||||
{ "startDate", "blah" }
|
||||
}));
|
||||
|
||||
|
||||
//list Existing OrderId
|
||||
var invoicesExistingOrderId =
|
||||
await viewOnly.GetInvoices(user.StoreId, orderId: newInvoice.Metadata["orderId"].ToString());
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public static class GreenFieldUtils
|
||||
{
|
||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
||||
{
|
||||
return controller.UnprocessableEntity(modelState.ToGreenfieldValidationError());
|
||||
}
|
||||
|
||||
public static List<GreenfieldValidationError> ToGreenfieldValidationError(this ModelStateDictionary modelState)
|
||||
{
|
||||
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||
foreach (var error in modelState)
|
||||
@@ -17,8 +22,10 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
return controller.UnprocessableEntity(errors.ToArray());
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||
|
||||
@@ -49,25 +49,35 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/invoices")]
|
||||
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[] orderId = null, [FromQuery] string[] status = null,
|
||||
[FromQuery] long? startDate = null,
|
||||
[FromQuery] long? endDate = null, [FromQuery] bool includeArchived = false)
|
||||
[FromQuery]
|
||||
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||
DateTimeOffset? startDate = null,
|
||||
[FromQuery]
|
||||
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||
DateTimeOffset? endDate = null, [FromQuery] bool includeArchived = false)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return StoreNotFound();
|
||||
}
|
||||
if (startDate is DateTimeOffset s &&
|
||||
endDate is DateTimeOffset e &&
|
||||
s > e)
|
||||
{
|
||||
this.ModelState.AddModelError(nameof(startDate), "startDate should not be above endDate");
|
||||
this.ModelState.AddModelError(nameof(endDate), "endDate should not be below startDate");
|
||||
}
|
||||
|
||||
DateTimeOffset startDateTimeOffset = Utils.UnixTimeToDateTime(startDate.GetValueOrDefault(DateTimeOffset.MinValue.ToUnixTimeSeconds()));
|
||||
DateTimeOffset endDateTimeOffset = Utils.UnixTimeToDateTime(endDate.GetValueOrDefault(DateTimeOffset.MaxValue.ToUnixTimeSeconds()));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
var invoices =
|
||||
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StoreId = new[] {store.Id},
|
||||
IncludeArchived = includeArchived,
|
||||
StartDate = startDateTimeOffset,
|
||||
EndDate = endDateTimeOffset,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate,
|
||||
OrderId = orderId,
|
||||
Status = status
|
||||
});
|
||||
|
||||
@@ -30,6 +30,8 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using NBitcoin;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BTCPayServer.Controllers.GreenField;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@@ -122,11 +124,9 @@ namespace BTCPayServer.Hosting
|
||||
})
|
||||
.ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
var builtInFactory = options.InvalidModelStateResponseFactory;
|
||||
options.InvalidModelStateResponseFactory = context =>
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
|
||||
return builtInFactory(context);
|
||||
return new UnprocessableEntityObjectResult(context.ModelState.ToGreenfieldValidationError());
|
||||
};
|
||||
})
|
||||
.AddRazorOptions(o =>
|
||||
|
||||
46
BTCPayServer/ModelBinders/DateTimeOffsetModelBinder.cs
Normal file
46
BTCPayServer/ModelBinders/DateTimeOffsetModelBinder.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
|
||||
namespace BTCPayServer.ModelBinders
|
||||
{
|
||||
public class DateTimeOffsetModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
if (!typeof(DateTimeOffset).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType) &&
|
||||
!typeof(DateTimeOffset?).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (val == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
string v = val.FirstValue as string;
|
||||
if (v == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sec = long.Parse(v, CultureInfo.InvariantCulture);
|
||||
bindingContext.Result = ModelBindingResult.Success(NBitcoin.Utils.UnixTimeToDateTime(sec));
|
||||
}
|
||||
catch
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid unix timestamp");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user