mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +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 partial class BTCPayServerClient
|
||||||
{
|
{
|
||||||
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null,
|
public virtual async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string orderId = null, InvoiceStatus[] status = null,
|
||||||
long? startDate = null,
|
DateTimeOffset? startDate = null,
|
||||||
long? endDate = null,
|
DateTimeOffset? endDate = null,
|
||||||
bool includeArchived = false,
|
bool includeArchived = false,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
Dictionary<string, object> queryPayload = new Dictionary<string, object>();
|
Dictionary<string, object> queryPayload = new Dictionary<string, object>();
|
||||||
queryPayload.Add(nameof(includeArchived), includeArchived);
|
queryPayload.Add(nameof(includeArchived), includeArchived);
|
||||||
|
|
||||||
if (startDate != null)
|
if (startDate is DateTimeOffset s)
|
||||||
queryPayload.Add(nameof(startDate), startDate);
|
queryPayload.Add(nameof(startDate), Utils.DateTimeToUnixTime(s));
|
||||||
|
|
||||||
if (endDate != null)
|
if (endDate is DateTimeOffset e)
|
||||||
queryPayload.Add(nameof(endDate), endDate);
|
queryPayload.Add(nameof(endDate), Utils.DateTimeToUnixTime(e));
|
||||||
|
|
||||||
if (orderId != null)
|
if (orderId != null)
|
||||||
queryPayload.Add(nameof(orderId), orderId);
|
queryPayload.Add(nameof(orderId), orderId);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@@ -69,6 +70,13 @@ namespace BTCPayServer.Client
|
|||||||
return JsonConvert.DeserializeObject<T>(str);
|
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,
|
protected virtual HttpRequestMessage CreateHttpRequest(string path,
|
||||||
Dictionary<string, object> queryPayload = null,
|
Dictionary<string, object> queryPayload = null,
|
||||||
HttpMethod method = null)
|
HttpMethod method = null)
|
||||||
|
|||||||
@@ -966,8 +966,8 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
//list Filtered
|
//list Filtered
|
||||||
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
|
var invoicesFiltered = await viewOnly.GetInvoices(user.StoreId,
|
||||||
orderId: null, status: null, NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddHours(-1)),
|
orderId: null, status: null, DateTimeOffset.Now.AddHours(-1),
|
||||||
NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddHours(1)));
|
DateTimeOffset.Now.AddHours(1));
|
||||||
|
|
||||||
Assert.NotNull(invoicesFiltered);
|
Assert.NotNull(invoicesFiltered);
|
||||||
Assert.Single(invoicesFiltered);
|
Assert.Single(invoicesFiltered);
|
||||||
@@ -975,11 +975,24 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
//list Yesterday
|
//list Yesterday
|
||||||
var invoicesYesterday = await viewOnly.GetInvoices(user.StoreId,
|
var invoicesYesterday = await viewOnly.GetInvoices(user.StoreId,
|
||||||
orderId: null, status: null, NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddDays(-2)),
|
orderId: null, status: null, DateTimeOffset.Now.AddDays(-2),
|
||||||
NBitcoin.Utils.DateTimeToUnixTime(DateTimeOffset.Now.AddDays(-1)));
|
DateTimeOffset.Now.AddDays(-1));
|
||||||
Assert.NotNull(invoicesYesterday);
|
Assert.NotNull(invoicesYesterday);
|
||||||
Assert.Empty(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
|
//list Existing OrderId
|
||||||
var invoicesExistingOrderId =
|
var invoicesExistingOrderId =
|
||||||
await viewOnly.GetInvoices(user.StoreId, orderId: newInvoice.Metadata["orderId"].ToString());
|
await viewOnly.GetInvoices(user.StoreId, orderId: newInvoice.Metadata["orderId"].ToString());
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
public static class GreenFieldUtils
|
public static class GreenFieldUtils
|
||||||
{
|
{
|
||||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
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>();
|
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||||
foreach (var error in modelState)
|
foreach (var error in modelState)
|
||||||
@@ -17,8 +22,10 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
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)
|
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||||
{
|
{
|
||||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||||
|
|||||||
@@ -49,25 +49,35 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores/{storeId}/invoices")]
|
[HttpGet("~/api/v1/stores/{storeId}/invoices")]
|
||||||
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[] orderId = null, [FromQuery] string[] status = null,
|
public async Task<IActionResult> GetInvoices(string storeId, [FromQuery] string[] orderId = null, [FromQuery] string[] status = null,
|
||||||
[FromQuery] long? startDate = null,
|
[FromQuery]
|
||||||
[FromQuery] long? endDate = null, [FromQuery] bool includeArchived = false)
|
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||||
|
DateTimeOffset? startDate = null,
|
||||||
|
[FromQuery]
|
||||||
|
[ModelBinder(typeof(ModelBinders.DateTimeOffsetModelBinder))]
|
||||||
|
DateTimeOffset? endDate = null, [FromQuery] bool includeArchived = false)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null)
|
if (store == null)
|
||||||
{
|
{
|
||||||
return StoreNotFound();
|
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()));
|
if (!ModelState.IsValid)
|
||||||
DateTimeOffset endDateTimeOffset = Utils.UnixTimeToDateTime(endDate.GetValueOrDefault(DateTimeOffset.MaxValue.ToUnixTimeSeconds()));
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
var invoices =
|
var invoices =
|
||||||
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
{
|
{
|
||||||
StoreId = new[] {store.Id},
|
StoreId = new[] {store.Id},
|
||||||
IncludeArchived = includeArchived,
|
IncludeArchived = includeArchived,
|
||||||
StartDate = startDateTimeOffset,
|
StartDate = startDate,
|
||||||
EndDate = endDateTimeOffset,
|
EndDate = endDate,
|
||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
Status = status
|
Status = status
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using BTCPayServer.Controllers.GreenField;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -122,11 +124,9 @@ namespace BTCPayServer.Hosting
|
|||||||
})
|
})
|
||||||
.ConfigureApiBehaviorOptions(options =>
|
.ConfigureApiBehaviorOptions(options =>
|
||||||
{
|
{
|
||||||
var builtInFactory = options.InvalidModelStateResponseFactory;
|
|
||||||
options.InvalidModelStateResponseFactory = context =>
|
options.InvalidModelStateResponseFactory = context =>
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
|
return new UnprocessableEntityObjectResult(context.ModelState.ToGreenfieldValidationError());
|
||||||
return builtInFactory(context);
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.AddRazorOptions(o =>
|
.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