New code for API 2

This commit is contained in:
Sander Dorigo 2022-06-23 09:33:43 +02:00
parent e0c9d3627e
commit b12d72bef6
13 changed files with 108 additions and 11 deletions

View File

@ -83,9 +83,13 @@ abstract class Controller extends BaseController
{ {
$bag = new ParameterBag; $bag = new ParameterBag;
$page = (int)request()->get('page'); $page = (int)request()->get('page');
if (0 === $page) {
if ($page < 1) {
$page = 1; $page = 1;
} }
if ($page > (2^16)) {
$page = (2^16);
}
$bag->set('page', $page); $bag->set('page', $page);
// some date fields: // some date fields:

View File

@ -62,7 +62,7 @@ class AboutController extends Controller
'driver' => $currentDriver, 'driver' => $currentDriver,
]; ];
return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE); return response()->api(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
} }
/** /**
@ -83,6 +83,6 @@ class AboutController extends Controller
$resource = new Item(auth()->user(), $transformer, 'users'); $resource = new Item(auth()->user(), $transformer, 'users');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); return response()->api($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
} }
} }

View File

@ -104,7 +104,7 @@ class StoreRequest extends FormRequest
$type = $this->convertString('type'); $type = $this->convertString('type');
$rules = [ $rules = [
'name' => 'required|min:1|uniqueAccountForUser', 'name' => 'required|min:1|uniqueAccountForUser',
'type' => 'required|' . sprintf('in:%s', $types), 'type' => 'required|min:1|' . sprintf('in:%s', $types),
'iban' => ['iban', 'nullable', new UniqueIban(null, $type)], 'iban' => ['iban', 'nullable', new UniqueIban(null, $type)],
'bic' => 'bic|nullable', 'bic' => 'bic|nullable',
'account_number' => ['between:1,255', 'nullable', new UniqueAccountNumber(null, $type)], 'account_number' => ['between:1,255', 'nullable', new UniqueAccountNumber(null, $type)],

View File

@ -59,7 +59,7 @@ class SumController extends Controller
$converted = $this->cerSum($result); $converted = $this->cerSum($result);
// convert to JSON response: // convert to JSON response:
return response()->json($converted); return response()->api($converted);
} }
/** /**
@ -73,6 +73,6 @@ class SumController extends Controller
$converted = $this->cerSum($result); $converted = $this->cerSum($result);
// convert to JSON response: // convert to JSON response:
return response()->json($converted); return response()->api($converted);
} }
} }

View File

@ -58,8 +58,8 @@ class DateRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
return [ return [
'start' => 'required|date', 'start' => 'required|date|after:1900-01-01|before:2099-12-31',
'end' => 'required|date|after:start', 'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
]; ];
} }
} }

View File

@ -0,0 +1,13 @@
<?php
namespace FireflyIII\Exceptions;
use Exception;
/**
*
*/
class BadHttpHeaderException extends Exception
{
public int $statusCode = 406;
}

View File

@ -40,7 +40,9 @@ use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException as LaravelValidationException; use Illuminate\Validation\ValidationException as LaravelValidationException;
use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException; use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable; use Throwable;
@ -94,8 +96,19 @@ class Handler extends ExceptionHandler
// somehow Laravel handler does not catch this: // somehow Laravel handler does not catch this:
return response()->json(['message' => $e->getMessage(), 'exception' => 'OAuthServerException'], 401); return response()->json(['message' => $e->getMessage(), 'exception' => 'OAuthServerException'], 401);
} }
if($e instanceof BadRequestHttpException) {
return response()->json(['message' => $e->getMessage(), 'exception' => 'BadRequestHttpException'], 400);
}
if ($e instanceof BadHttpHeaderException) {
// is always API exception.
return response()->json(['message' => $e->getMessage(), 'exception' => 'BadHttpHeaderException'], $e->statusCode);
}
if ($request->expectsJson()) { if ($request->expectsJson()) {
$errorCode = 500;
$errorCode = $e instanceof MethodNotAllowedHttpException ? 405: $errorCode;
$isDebug = config('app.debug', false); $isDebug = config('app.debug', false);
if ($isDebug) { if ($isDebug) {
return response()->json( return response()->json(
@ -106,12 +119,13 @@ class Handler extends ExceptionHandler
'file' => $e->getFile(), 'file' => $e->getFile(),
'trace' => $e->getTrace(), 'trace' => $e->getTrace(),
], ],
500 $errorCode
); );
} }
return response()->json( return response()->json(
['message' => sprintf('Internal Firefly III Exception: %s', $e->getMessage()), 'exception' => get_class($e)], 500 ['message' => sprintf('Internal Firefly III Exception: %s', $e->getMessage()), 'exception' => get_class($e)],
$errorCode
); );
} }

View File

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Http; namespace FireflyIII\Http;
use FireflyIII\Http\Middleware\AcceptHeaders;
use FireflyIII\Http\Middleware\Authenticate; use FireflyIII\Http\Middleware\Authenticate;
use FireflyIII\Http\Middleware\Binder; use FireflyIII\Http\Middleware\Binder;
use FireflyIII\Http\Middleware\EncryptCookies; use FireflyIII\Http\Middleware\EncryptCookies;
@ -177,6 +178,7 @@ class Kernel extends HttpKernel
], ],
'api' => [ 'api' => [
AcceptHeaders::class,
EnsureFrontendRequestsAreStateful::class, EnsureFrontendRequestsAreStateful::class,
'auth:api,sanctum', 'auth:api,sanctum',
'bindings', 'bindings',

View File

@ -0,0 +1,45 @@
<?php
namespace FireflyIII\Http\Middleware;
use FireflyIII\Exceptions\BadHttpHeaderException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Log;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
*
*/
class AcceptHeaders
{
/**
* Handle the incoming requests.
*
* @param Request $request
* @param callable $next
* @return Response
* @throws BadHttpHeaderException
*/
public function handle($request, $next): mixed
{
$method = $request->getMethod();
if ('GET' === $method && !$request->accepts(['application/json', 'application/vdn.api+json'])) {
throw new BadHttpHeaderException('Your request must accept either application/json or application/vdn.api+json.');
}
if (('POST' === $method || 'PUT' === $method) && 'application/json' !== (string)$request->header('Content-Type')) {
$error = new BadHttpHeaderException('B');
$error->statusCode = 415;
throw $error;
}
// throw bad request if trace id is not a UUID
$uuid = $request->header('X-Trace-Id', null);
if (is_string($uuid) && '' !== trim($uuid) && (preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', trim($uuid)) !== 1)) {
throw new BadRequestHttpException('Bad X-Trace-Id header.');
}
return $next($request);
}
}

View File

@ -27,6 +27,7 @@ use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport; use Laravel\Passport\Passport;
use Laravel\Sanctum\Sanctum; use Laravel\Sanctum\Sanctum;
use URL; use URL;
use Illuminate\Support\Facades\Response;
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
@ -45,6 +46,20 @@ class AppServiceProvider extends ServiceProvider
if ('heroku' === config('app.env')) { if ('heroku' === config('app.env')) {
URL::forceScheme('https'); URL::forceScheme('https');
} }
Response::macro('api', function ($value) {
$headers = [
'Cache-Control' => 'no-store'
];
$uuid = (string) request()->header('X-Trace-Id');
if ('' !== trim($uuid) && (preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', trim($uuid)) === 1)) {
$headers['X-Trace-Id'] = $uuid;
}
return response()
->json($value)
->withHeaders($headers);
});
} }
/** /**

View File

@ -119,6 +119,9 @@ trait ConvertsDataTypes
$secondSearch = $keepNewlines ? ["\r"] : ["\r", "\n", "\t", "\036", "\025"]; $secondSearch = $keepNewlines ? ["\r"] : ["\r", "\n", "\t", "\036", "\025"];
$string = str_replace($secondSearch, '', $string); $string = str_replace($secondSearch, '', $string);
// clear zalgo text (TODO also in API v2)
$string = preg_replace('/\pM/u', '', $string);
return trim($string); return trim($string);
} }

View File

@ -427,7 +427,7 @@ class FireflyValidator extends Validator
return $this->validateByAccountTypeString($value, $parameters, $this->data['objectType']); return $this->validateByAccountTypeString($value, $parameters, $this->data['objectType']);
} }
if (array_key_exists('type', $this->data)) { if (array_key_exists('type', $this->data)) {
return $this->validateByAccountTypeString($value, $parameters, $this->data['type']); return $this->validateByAccountTypeString($value, $parameters, (string) $this->data['type']);
} }
if (array_key_exists('account_type_id', $this->data)) { if (array_key_exists('account_type_id', $this->data)) {
return $this->validateByAccountTypeId($value, $parameters); return $this->validateByAccountTypeId($value, $parameters);

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
/** /**
* V2 API route for TransactionSum API endpoints * V2 API route for TransactionSum API endpoints
* TODO what to do with these routes
*/ */
Route::group( Route::group(
['namespace' => 'FireflyIII\Api\V2\Controllers\Transaction\Sum', 'prefix' => 'v2/transaction/sum', ['namespace' => 'FireflyIII\Api\V2\Controllers\Transaction\Sum', 'prefix' => 'v2/transaction/sum',