Filtering of API-Results (#985)

* Add FilteredApiResponse

* Use FilteredApiResponse for Generic-Entity-Search

* Use FilteredApiResponse for Recipe-Fullfillment

* Use FilteredApiResponse for GetUsers

* Use FilteredApiResponse for current Tasks

* Use FilteredApiResponse for ProductStockEntries & ProductStockLocations

* Use FilteredApiResponse for current chores

* Use FilteredApiResponse for batteries-current

* Fix missing highlighting of "< X days"

* Keep to use existing views

Co-authored-by: Bernd Bestel <bernd@berrnd.de>
This commit is contained in:
fipwmaqzufheoxq92ebc
2020-09-01 19:59:40 +02:00
committed by GitHub
parent 60f3d900e8
commit 32a4f81f62
21 changed files with 197 additions and 57 deletions

View File

@@ -2,10 +2,16 @@
namespace Grocy\Controllers;
use LessQL\Result;
class BaseApiController extends BaseController
{
protected $OpenApiSpec = null;
const PATTERN_FIELD = '[A-Za-z_][A-Za-z0-9_]+';
const PATTERN_OPERATOR = '!?(=|~|<|>|(>=)|(<=))';
const PATTERN_VALUE = '[A-Za-z_0-9.]+';
public function __construct(\DI\Container $container)
{
parent::__construct($container);
@@ -29,6 +35,68 @@ class BaseApiController extends BaseController
]);
}
public function FilteredApiResponse(\Psr\Http\Message\ResponseInterface $response, Result $data, array $query)
{
$data = $this->queryData($data, $query);
return $this->ApiResponse($response, $data);
}
protected function queryData(Result $data, array $query)
{
if (isset($query['query']))
$data = $this->filter($data, $query['query']);
if (isset($query['limit']))
$data = $data->limit(intval($query['limit']), intval($query['offset'] ?? 0));
if (isset($query['order']))
$data = $data->orderBy($query['order']);
return $data;
}
protected function filter(Result $data, array $query): Result
{
foreach ($query as $q) {
$matches = array();
preg_match('/(?P<field>' . self::PATTERN_FIELD . ')'
. '(?P<op>' . self::PATTERN_OPERATOR . ')'
. '(?P<value>' . self::PATTERN_VALUE . ')/',
$q, $matches
);
error_log(var_export($matches, true));
switch ($matches['op']) {
case '=':
$data = $data->where($matches['field'], $matches['value']);
break;
case '!=':
$data = $data->whereNot($matches['field'], $matches['value']);
break;
case '~':
$data = $data->where($matches['field'] . ' LIKE ?', '%' . $matches['value'] . '%');
break;
case '!~':
$data = $data->where($matches['field'] . ' NOT LIKE ?', '%' . $matches['value'] . '%');
break;
case '!>=':
case '<':
$data = $data->where($matches['field'] . ' < ?', $matches['value']);
break;
case '!<=':
case '>':
$data = $data->where($matches['field'] . ' > ?', $matches['value']);
break;
case '!<':
case '>=':
$data = $data->where($matches['field'] . ' >= ?', $matches['value']);
break;
case '!>':
case '<=':
$data = $data->where($matches['field'] . ' <= ?', $matches['value']);
break;
}
}
return $data;
}
protected function getOpenApispec()
{
if ($this->OpenApiSpec == null)

View File

@@ -21,7 +21,7 @@ class BatteriesApiController extends BaseApiController
public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->ApiResponse($response, $this->getBatteriesService()->GetCurrent());
return $this->FilteredApiResponse($response, $this->getBatteriesService()->GetCurrent(), $request->getQueryParams());
}
public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)

View File

@@ -58,7 +58,7 @@ class ChoresApiController extends BaseApiController
public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->ApiResponse($response, $this->getChoresService()->GetCurrent());
return $this->FilteredApiResponse($response, $this->getChoresService()->GetCurrent(), $request->getQueryParams());
}
public function TrackChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)

View File

@@ -3,6 +3,7 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Slim\Exception\HttpBadRequestException;
class GenericEntityApiController extends BaseApiController
{
@@ -178,12 +179,13 @@ class GenericEntityApiController extends BaseApiController
{
try
{
return $this->ApiResponse($response, $this->getDatabase()->{$args['entity']}
()->where('name LIKE ?', '%' . $args['searchString'] . '%'));
return $this->FilteredApiResponse($response, $this->getDatabase()->{$args['entity']}
()->where('name LIKE ?', '%' . $args['searchString'] . '%'), $request->getQueryParams());
}
catch (\PDOException $ex)
{
return $this->GenericErrorResponse($response, 'The given entity has no field "name"');
throw new HttpBadRequestException($request, $ex->getMessage(), $ex);
//return $this->GenericErrorResponse($response, 'The given entity has no field "name"', $ex);
}
}

View File

@@ -44,7 +44,7 @@ class RecipesApiController extends BaseApiController
{
if (!isset($args['recipeId']))
{
return $this->ApiResponse($response, $this->getRecipesService()->GetRecipesResolved());
return $this->FilteredApiResponse($response, $this->getRecipesService()->GetRecipesResolved(), $request->getQueryParams());
}
$recipeResolved = FindObjectInArrayByPropertyValue($this->getRecipesService()->GetRecipesResolved(), 'recipe_id', $args['recipeId']);

View File

@@ -552,12 +552,12 @@ class StockApiController extends BaseApiController
$allowSubproductSubstitution = true;
}
return $this->ApiResponse($response, $this->getStockService()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution));
return $this->FilteredApiResponse($response, $this->getStockService()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution, false), $request->getQueryParams());
}
public function ProductStockLocations(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->ApiResponse($response, $this->getStockService()->GetProductStockLocations($args['productId']));
return $this->FilteredApiResponse($response, $this->getStockService()->GetProductStockLocations($args['productId']), $request->getQueryParams());
}
public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)

View File

@@ -8,7 +8,7 @@ class TasksApiController extends BaseApiController
{
public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->ApiResponse($response, $this->getTasksService()->GetCurrent());
return $this->FilteredApiResponse($response, $this->getTasksService()->GetCurrent(), $request->getQueryParams());
}
public function MarkTaskAsCompleted(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)

View File

@@ -123,7 +123,7 @@ class UsersApiController extends BaseApiController
User::checkPermission($request, User::PERMISSION_USERS_READ);
try
{
return $this->ApiResponse($response, $this->getUsersService()->GetUsersAsDto());
return $this->FilteredApiResponse($response, $this->getUsersService()->GetUsersAsDto(), $request->getQueryParams());
}
catch (\Exception $ex)
{