GreenField: Handle status codes, error models consistently

This commit is contained in:
Kukks 2020-06-03 11:58:49 +02:00
parent b9ef5af5d7
commit f313a5f221
7 changed files with 316 additions and 194 deletions

View File

@ -0,0 +1,26 @@
using System;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers.GreenField
{
public static class GreenFieldUtils
{
public static IActionResult GetValidationResponse(this ControllerBase controller)
{
return controller.UnprocessableEntity( new ValidationProblemDetails(controller.ModelState));
}
public static IActionResult GetExceptionResponse(this ControllerBase controller, Exception e)
{
return GetGeneralErrorResponse(controller, e.Message);
}
public static IActionResult GetGeneralErrorResponse(this ControllerBase controller, string error)
{
return controller.BadRequest( new ProblemDetails()
{
Detail = error
});
}
}
}

View File

@ -44,8 +44,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
}
@ -73,8 +72,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
return Ok();
@ -103,8 +101,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
}
@ -157,13 +154,11 @@ namespace BTCPayServer.Controllers.GreenField
return Ok();
}
ModelState.AddModelError(string.Empty, response.Result.ToString());
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetGeneralErrorResponse(response.Result.ToString());
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
}
@ -193,7 +188,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception)
{
ModelState.AddModelError(nameof(lightningInvoice), "The BOLT11 invoice was invalid.");
ModelState.AddModelError(nameof(lightningInvoice.Invoice), "The BOLT11 invoice was invalid.");
}
if (CheckValidation(out var errorActionResult))
@ -204,17 +199,13 @@ namespace BTCPayServer.Controllers.GreenField
var result = await lightningClient.Pay(lightningInvoice.Invoice);
switch (result.Result)
{
case PayResult.Ok:
return Ok();
case PayResult.CouldNotFindRoute:
ModelState.AddModelError(nameof(lightningInvoice.Invoice), "Could not find route");
break;
return this.GetGeneralErrorResponse("Could not find route");
case PayResult.Error:
ModelState.AddModelError(nameof(lightningInvoice.Invoice), result.ErrorDetail);
break;
return this.GetGeneralErrorResponse(result.ErrorDetail);
}
return BadRequest(new ValidationProblemDetails(ModelState));
return Ok();
}
public virtual async Task<IActionResult> GetInvoice(string cryptoCode, string id)
@ -237,8 +228,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
}
@ -269,8 +259,7 @@ namespace BTCPayServer.Controllers.GreenField
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetExceptionResponse(e);
}
}
@ -292,7 +281,7 @@ namespace BTCPayServer.Controllers.GreenField
{
if (!ModelState.IsValid)
{
result = BadRequest(new ValidationProblemDetails(ModelState));
result = this.GetValidationResponse();
return true;
}

View File

@ -137,7 +137,7 @@ namespace BTCPayServer.Controllers.GreenField
if (!string.IsNullOrEmpty(data.CustomCSSLink) && data.CustomCSSLink.Length > 500)
ModelState.AddModelError(nameof(data.CustomCSSLink), "CustomCSSLink is 500 chars max");
return !ModelState.IsValid ? BadRequest(new ValidationProblemDetails(ModelState)) : null;
return !ModelState.IsValid ? this.GetValidationResponse() :null;
}
private static Client.Models.PaymentRequestData FromModel(PaymentRequestData data)

View File

@ -47,7 +47,7 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}")]
public async Task<ActionResult> RemoveStore(string storeId)
public async Task<IActionResult> RemoveStore(string storeId)
{
var store = HttpContext.GetStoreData();
if (store == null)
@ -57,8 +57,8 @@ namespace BTCPayServer.Controllers.GreenField
if (!_storeRepository.CanDeleteStores())
{
ModelState.AddModelError(string.Empty, "BTCPay Server is using a database server that does not allow you to remove stores.");
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetGeneralErrorResponse(
"BTCPay Server is using a database server that does not allow you to remove stores.");
}
await _storeRepository.RemoveStore(storeId, _userManager.GetUserId(User));
return Ok();
@ -194,8 +194,8 @@ namespace BTCPayServer.Controllers.GreenField
ModelState.AddModelError(nameof(request.MonitoringExpiration), "InvoiceExpiration can only be between 10 and 34560 mins");
if(request.PaymentTolerance < 0 && request.PaymentTolerance > 100)
ModelState.AddModelError(nameof(request.PaymentTolerance), "PaymentTolerance can only be between 0 and 100 percent");
return !ModelState.IsValid ? BadRequest(new ValidationProblemDetails(ModelState)) : null;
return !ModelState.IsValid ? this.GetValidationResponse() : null;
}
}
}

View File

@ -62,16 +62,21 @@ namespace BTCPayServer.Controllers.GreenField
[AllowAnonymous]
[HttpPost("~/api/v1/users")]
public async Task<ActionResult<ApplicationUserData>> CreateUser(CreateApplicationUserRequest request, CancellationToken cancellationToken = default)
public async Task<IActionResult> CreateUser(CreateApplicationUserRequest request, CancellationToken cancellationToken = default)
{
if (request?.Email is null)
return BadRequest(CreateValidationProblem(nameof(request.Email), "Email is missing"));
if (!Validation.EmailValidator.IsEmail(request.Email))
ModelState.AddModelError(nameof(request.Email), "Email is missing");
if (!string.IsNullOrEmpty(request?.Email) && !Validation.EmailValidator.IsEmail(request.Email))
{
return BadRequest(CreateValidationProblem(nameof(request.Email), "Invalid email"));
ModelState.AddModelError(nameof(request.Email), "Invalid email");
}
if (request?.Password is null)
return BadRequest(CreateValidationProblem(nameof(request.Password), "Password is missing"));
ModelState.AddModelError(nameof(request.Password), "Password is missing");
if (!ModelState.IsValid)
{
return this.GetValidationResponse();
}
var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any();
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
var isAuth = User.Identity.AuthenticationType == GreenFieldConstants.AuthenticationType;
@ -113,7 +118,7 @@ namespace BTCPayServer.Controllers.GreenField
{
ModelState.AddModelError(nameof(request.Password), error.Description);
}
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetValidationResponse();
}
if (!isAdmin)
{
@ -127,7 +132,7 @@ namespace BTCPayServer.Controllers.GreenField
{
ModelState.AddModelError(string.Empty, error.Description);
}
return BadRequest(new ValidationProblemDetails(ModelState));
return this.GetValidationResponse();
}
if (request.IsAdministrator is true)
@ -152,13 +157,6 @@ namespace BTCPayServer.Controllers.GreenField
return CreatedAtAction(string.Empty, user);
}
private ValidationProblemDetails CreateValidationProblem(string propertyName, string errorMessage)
{
var modelState = new ModelStateDictionary();
modelState.AddModelError(propertyName, errorMessage);
return new ValidationProblemDetails(modelState);
}
private static ApplicationUserData FromModel(ApplicationUser data)
{
return new ApplicationUserData()

View File

@ -30,7 +30,7 @@
}
}
},
"400": {
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
@ -40,8 +40,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
@ -77,8 +90,8 @@
"200": {
"description": "Successfully connected"
},
"400": {
"description": "A list of errors that occurred when attempting to connect to the lightning node",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -87,8 +100,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -145,17 +171,20 @@
}
},
"400": {
"description": "A list of errors that occurred",
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
@ -189,8 +218,8 @@
"200": {
"description": "Successfully opened"
},
"400": {
"description": "A list of errors that occurred when attempting to connect to the lightning node",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -199,8 +228,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -253,18 +295,11 @@
}
}
},
"400": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
@ -316,18 +351,11 @@
}
}
},
"400": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration or the specified invoice was not found "
}
},
"security": [
@ -363,8 +391,8 @@
"200": {
"description": "Successfully paid"
},
"400": {
"description": "A list of errors that occurred when attempting to pay the lightning invoice",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -373,8 +401,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -427,8 +468,8 @@
}
}
},
"400": {
"description": "A list of errors that occurred when attempting to create the lightning invoice",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -437,8 +478,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {

View File

@ -3,19 +3,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/info": {
"get": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Get node information",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -24,6 +15,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "View information about the lightning node",
@ -39,7 +39,7 @@
}
}
},
"400": {
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
@ -49,14 +49,27 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -66,19 +79,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/connect": {
"post": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Connect to lightning node",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -87,6 +91,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "Connect to another lightning node.",
@ -95,8 +108,8 @@
"200": {
"description": "Successfully connected"
},
"400": {
"description": "A list of errors that occurred when attempting to connect to the lightning node",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -105,8 +118,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -122,7 +148,7 @@
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -132,19 +158,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/channels": {
"get": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Get channels",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -153,6 +170,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "View information about the current channels of the lightning node",
@ -172,23 +198,26 @@
}
},
"400": {
"description": "A list of errors that occurred",
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -196,19 +225,10 @@
},
"post": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Open channel",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -217,6 +237,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "Open a channel with another lightning node. You should connect to that node first.",
@ -225,8 +254,8 @@
"200": {
"description": "Successfully opened"
},
"400": {
"description": "A list of errors that occurred when attempting to connect to the lightning node",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -235,8 +264,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -252,7 +294,7 @@
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -262,19 +304,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/address": {
"get": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Get deposit address",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -283,6 +316,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "Get an on-chain deposit address for the lightning node ",
@ -298,46 +340,31 @@
}
}
},
"400": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
]
}
},
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/{id}": {
"get": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Get invoice",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -347,6 +374,15 @@
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
},
{
"name": "id",
"in": "path",
@ -370,24 +406,17 @@
}
}
},
"400": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration or the specified invoice was not found "
}
},
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -397,19 +426,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay": {
"post": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Pay Lightning Invoice",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -418,6 +438,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "Pay a lightning invoice.",
@ -426,8 +455,8 @@
"200": {
"description": "Successfully paid"
},
"400": {
"description": "A list of errors that occurred when attempting to pay the lightning invoice",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -436,8 +465,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -453,7 +495,7 @@
"security": [
{
"API Key": [
"btcpay.store.canuselightningnode"
"btcpay.server.canuseinternallightningnode"
],
"Basic": []
}
@ -463,19 +505,10 @@
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices": {
"post": {
"tags": [
"Lightning (Store)"
"Lightning (Internal Node)"
],
"summary": "Create lightning invoice",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning node configuration you wish to use",
"schema": {
"type": "string"
}
},
{
"name": "cryptoCode",
"in": "path",
@ -484,6 +517,15 @@
"schema": {
"type": "string"
}
},
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store id with the lightning-node configuration to query",
"schema": {
"type": "string"
}
}
],
"description": "Create a lightning invoice.",
@ -499,8 +541,8 @@
}
}
},
"400": {
"description": "A list of errors that occurred when attempting to create the lightning invoice",
"422": {
"description": "A list of errors that occurred",
"content": {
"application/json": {
"schema": {
@ -509,8 +551,21 @@
}
}
},
"400": {
"description": "An error occurred",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden"
},
"404": {
"description": "The lightning node configuration was not found"
}
},
"requestBody": {
@ -526,7 +581,7 @@
"security": [
{
"API Key": [
"btcpay.store.cancreatelightninginvoice"
"btcpay.server.cancreatelightninginvoiceinternalnode"
],
"Basic": []
}
@ -536,7 +591,7 @@
},
"tags": [
{
"name": "Lightning (Store)"
"name": "Lightning (Internal Node)"
}
]
}