Skip to content

Commit

Permalink
feat(api): add filtering to Stats endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
hippothomas committed Jan 25, 2024
1 parent a0b269c commit 294dc62
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 15 deletions.
19 changes: 10 additions & 9 deletions docs/api-documentation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ curl '<APP_BASE_URL>/api/v1/stats' \

**HTTP GET Request Parameters:**<br/>

| Object | Description |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `date_from` | Filter results based on a specific timeframe by passing a from-date in `YYYY-MM-DD` format. You can also specify an exact time in ISO-8601 date format, e.g. `2024-01-01T00:00:00.000Z`. |
| `date_to` | Filter results based on a specific timeframe by passing an end-date in `YYYY-MM-DD` format. You can also specify an exact time in ISO-8601 date format, e.g. `2024-01-01T00:00:00.000Z`. |
| `interval` | Filter results based on an interval in the timeframe selected. Available values: `day` (Default), `week`, `month`, `year`. |
| `fields` | Select information needed and optimize bandwidth with a partial response. Example: If you want to only need the values for `calories` and `proteins`, you should set the value `calories,proteins`. By default, everything is returned. |
| `sort` | By default, results are sorted by date/time descending. Use this parameter to specify a sorting order. Available values: `DESC` (Default), `ASC`. |
| `limit` | Specify a pagination limit (number of results per page) for your API request. Default limit value is `100`, maximum allowed limit value is `500`. |
| `offset` | Specify a pagination offset value for your API request. Example: An offset value of `100` combined with a limit value of 10 would show results 100-110. Default value is `0`, starting with the first available result. |
| Object | Description |
|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `date_from` | Filter results based on a specific timeframe by passing a from-date in `YYYY-MM-DD` format. You can also specify an exact time in ISO-8601 date format, e.g. `2024-01-01T00:00:00.000Z`. |
| `date_to` | Filter results based on a specific timeframe by passing an end-date in `YYYY-MM-DD` format. You can also specify an exact time in ISO-8601 date format, e.g. `2024-01-01T00:00:00.000Z`. |
| `interval` | Filter results based on an interval in the timeframe selected. Available values: `day` (Default), `week`, `month`, `year`. |
| `fields` | Select information needed and optimize bandwidth with a partial response. Example: If you want to only need the values for `calories` and `proteins`, you should set the value `calories,proteins`. By default, everything is returned. |
| `sort` | By default, results are sorted by date/time descending. Use this parameter to specify a sorting order. Available values: `DESC` (Default), `ASC`. |
| `limit` | Specify a pagination limit (number of results per page) for your API request. Default limit value is `100`, maximum allowed limit value is `500`. |
| `offset` | Specify a pagination offset value for your API request. Example: An offset value of `100` combined with a limit value of 10 would show results 100-110. Default value is `0`, starting with the first available result. |
| `filter` | Filter results based on filtering values. You need to use the template `field[operator]:number` where `field` is a nutrition title, `operator` is either `gt` (Greater than) or `lt` (Less than) and `number` is a numeric value. You can have multiple filter by using a comma between them. Example: `calories[lt]:1350,proteins[gt]:50` will return results where `calories` are less than `1350` and `proteins` are greater than `50`. |

**Example API Response:**<br/>
```json
Expand Down
42 changes: 41 additions & 1 deletion src/Controller/StatsApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class StatsApiController extends ApiController
"month" => "P1M",
"year" => "P1Y"
];
private const array FILTER_POSSIBILITIES = [
"lt" => '$lt', // Less than
"gt" => '$gt', // Greater than
];

public function __construct(
private readonly MongoDB $mongoDB,
Expand Down Expand Up @@ -94,13 +98,17 @@ public function index(Request $request): JsonResponse
throw new HttpException(400, 'Invalid limit or offset value. Limit must be greater than 0, and offset must be non-negative.');
}

// Handle filtering
$filters_parameter = (string) $request->get('filter');

$cursor = $this->mongoDB->retrieveUserNutritionData(
$user->getId(),
$date_list,
$this->getFields($fields_parameter),
$sort,
$limit,
$offset
$offset,
$this->getFilters($filters_parameter),
);
if ($cursor === false) {
throw new HttpException(500, 'An error occurred while processing your request. Please try again later.');
Expand Down Expand Up @@ -165,4 +173,36 @@ private function getDateInterval(string $interval_parameter): DateInterval
// In case of exception or if not found in possibility list return day interval
return new DateInterval('P1D');
}

/**
* Get the filters list base on request parameters
* @param string $filters_parameter Filters passed in request parameters
* @return array
*/
private function getFilters(string $filters_parameter): array
{
$filters = [];

// Get filter list
foreach (explode(',', $filters_parameter) as $param) {
// Check if the filter pattern is respected : field[operator]:number
if (!preg_match('/([A-Za-z]+)\\[([A-Za-z0-9]+)]:([0-9]+)/i', $param, $matches)) {
continue;
}
// Check if the field is valid
if (!in_array($matches[1], self::FIELD_POSSIBILITIES)) {
continue;
}
// Check if the operator is valid
if (!array_key_exists($matches[2], self::FILTER_POSSIBILITIES)) {
continue;
}
// Check if the last parameter is a valid number
if (!is_numeric($matches[3])) {
continue;
}
$filters["nutrition.".$matches[1]][self::FILTER_POSSIBILITIES[$matches[2]]] = (int) $matches[3];
}
return $filters;
}
}
13 changes: 8 additions & 5 deletions src/Service/MongoDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,22 @@ public function findOne(int $user_id, DateTime $date): array|false
* @param int $sort Specify a sorting order
* @param int $limit Specify a pagination limit
* @param int $offset Specify a pagination offset value
* @param array $filters_group List filter for the mongo request
* @return CursorInterface|false Returns the retrieved nutrition data, false otherwise
*/
public function retrieveUserNutritionData(int $user_id, array $date_list, array $fields_group, int $sort = -1, int $limit = 100, int $offset = 0): CursorInterface|false
public function retrieveUserNutritionData(int $user_id, array $date_list, array $fields_group, int $sort = -1, int $limit = 100, int $offset = 0, array $filters_group = []): CursorInterface|false
{
$collection = $this->getCollection();
if (!$collection) return false;
$match = ['user_id' => $user_id];

// Prepare the date list for the request
if (empty($date_list)) return false;
$dates = [];
foreach ($date_list as $date) {
$dates[] = [ 'entry.dateTime' => new Regex("{$date}.*") ];
}
$match['$or'] = $dates;

// Prepare the fields for the request
$fields = [];
Expand All @@ -137,14 +140,14 @@ public function retrieveUserNutritionData(int $user_id, array $date_list, array
}
}

// Add filters for the request
$match = array_merge($match, $filters_group);

// Search into the collection and return results as an array, if successfully found
try {
return $collection->aggregate([
[
'$match' => [
'user_id' => $user_id,
'$or' => $dates
]
'$match' => $match
],
[
'$sort' => [ 'timestamp' => -1 ]
Expand Down

0 comments on commit 294dc62

Please sign in to comment.