From 5cd5699f40316e251ae573b5714c3ee20a5398f0 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Thu, 5 Sep 2019 23:39:41 +0000 Subject: [PATCH 1/8] Added Query Builder --- src/Http/Controllers/BaseRestfulController.php | 5 +++++ src/Http/Controllers/RestfulController.php | 8 +++++++- src/Models/RestfulModel.php | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Http/Controllers/BaseRestfulController.php b/src/Http/Controllers/BaseRestfulController.php index a02f492..0bda9ef 100644 --- a/src/Http/Controllers/BaseRestfulController.php +++ b/src/Http/Controllers/BaseRestfulController.php @@ -39,6 +39,11 @@ class BaseRestfulController extends Controller */ public static $transformer = null; + /** + * @var array|string|\Spatie\QueryBuilder\Sorts\Sort + */ + public static $allowedSorts = null; + /** * RestfulController constructor. * diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index 32b0110..8182b92 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -4,6 +4,8 @@ use Illuminate\Database\QueryException; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; +use Spatie\QueryBuilder\QueryBuilder; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -20,9 +22,13 @@ public function getAll() $model = new static::$model; - $query = $model::with($model::getCollectionWith()); + $query = QueryBuilder::for($model::with($model::getCollectionWith())); $this->qualifyCollectionQuery($query); + $query->allowedSorts($model::getAllowedSorts()); + + $query->allowedFilters($model::getAllowedFilters()); + // Handle pagination, if applicable $perPage = $model->getPerPage(); if ($perPage) { diff --git a/src/Models/RestfulModel.php b/src/Models/RestfulModel.php index 69ceb83..e9a02e5 100644 --- a/src/Models/RestfulModel.php +++ b/src/Models/RestfulModel.php @@ -68,6 +68,10 @@ class RestfulModel extends Model */ public static $transformer = null; + public static $allowedSorts = null; + + public static $allowedFilters = null; + /** * Return the validation rules for this model * @@ -201,6 +205,20 @@ public static function getCollectionWith() } } + public static function getAllowedSorts() + { + if (!is_null(static::$allowedSorts)){ + return static::$allowedSorts; + } + } + + public static function getAllowedFilters() + { + if (!is_null(static::$allowedFilters)) { + return static::$allowedFilters; + } + } + /************************************************************ * Extending Laravel Functions Below ***********************************************************/ From dbee8d511135111f197966356d80c873cfa2c51d Mon Sep 17 00:00:00 2001 From: Mischa King Date: Mon, 9 Sep 2019 13:02:55 +1000 Subject: [PATCH 2/8] Update RestfulController.php --- src/Http/Controllers/RestfulController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index 32b0110..da5fcb1 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -26,7 +26,7 @@ public function getAll() // Handle pagination, if applicable $perPage = $model->getPerPage(); if ($perPage) { - $paginator = $query->paginate($perPage); + $paginator = $query->paginate($perPage)->appends(Input::only(['filter', 'sort'])); return $this->response->paginator($paginator, $this->getTransformer()); } else { From e060f8d4d135a791afa4ab415232f355e36ec371 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Mon, 9 Sep 2019 03:06:17 +0000 Subject: [PATCH 3/8] Added filter and sort to query string for links --- src/Http/Controllers/RestfulController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index 8182b92..d3c8846 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -32,7 +32,7 @@ public function getAll() // Handle pagination, if applicable $perPage = $model->getPerPage(); if ($perPage) { - $paginator = $query->paginate($perPage); + $paginator = $query->paginate($perPage)->appends(Input::only(['filter', 'sort'])); return $this->response->paginator($paginator, $this->getTransformer()); } else { From 4b31f38a8cce3db1f7ce9c3a1c356212c0496825 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Mon, 9 Sep 2019 03:35:22 +0000 Subject: [PATCH 4/8] Patch missing use --- src/Http/Controllers/RestfulController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index d3c8846..4b40fea 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -4,6 +4,7 @@ use Illuminate\Database\QueryException; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Log; use Spatie\QueryBuilder\QueryBuilder; use Symfony\Component\HttpKernel\Exception\HttpException; From 6c2f73b4e446be921135bfbb05170c2b330a217c Mon Sep 17 00:00:00 2001 From: Mischa King Date: Tue, 18 Feb 2020 05:29:02 +0000 Subject: [PATCH 5/8] Fixed StyleCI issues --- src/Http/Controllers/RestfulController.php | 2 +- src/Models/RestfulModel.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index b250a0d..3c46fdc 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -225,7 +225,7 @@ public function delete($uuid) * @param string $endpoint * @return string $cacheKey */ - public static function getCacheKey(string $endpoint = 'getAll') : ?string + public static function getCacheKey(string $endpoint = 'getAll'): ?string { if ($endpoint == 'getAll') { return sprintf(static::CACHE_KEY_GET_ALL, static::$model); diff --git a/src/Models/RestfulModel.php b/src/Models/RestfulModel.php index e9a02e5..09f3846 100644 --- a/src/Models/RestfulModel.php +++ b/src/Models/RestfulModel.php @@ -207,14 +207,14 @@ public static function getCollectionWith() public static function getAllowedSorts() { - if (!is_null(static::$allowedSorts)){ + if (! is_null(static::$allowedSorts)) { return static::$allowedSorts; } } public static function getAllowedFilters() { - if (!is_null(static::$allowedFilters)) { + if (! is_null(static::$allowedFilters)) { return static::$allowedFilters; } } From 0282dda786839cf6ddbf23ab2e35e98611c524a6 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Tue, 18 Feb 2020 05:30:10 +0000 Subject: [PATCH 6/8] Fixed StyleCI issues --- src/Http/Controllers/RestfulController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index 3c46fdc..89e2f3e 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -5,7 +5,6 @@ use Illuminate\Database\QueryException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Log; use Spatie\QueryBuilder\QueryBuilder; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; From baef9191376afa9c1014c52bdab1e10a13b68139 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Tue, 18 Feb 2020 05:40:38 +0000 Subject: [PATCH 7/8] add composer package spatie/laravel-query-builder --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 37cba67..b2ad376 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "illuminate/support": "^6 || ^5.8", "webpatser/laravel-uuid": "^3.0", "ramsey/uuid": "^3.0", - "atehnix/laravel-stubs": "~6.0" + "atehnix/laravel-stubs": "~6.0", + "spatie/laravel-query-builder": "^2.7" }, "require-dev": { "ext-json": "*", From 0cc570a6df2cfd95d35aad5e0cdf055522bdee96 Mon Sep 17 00:00:00 2001 From: Mischa King Date: Tue, 18 Feb 2020 07:24:49 +0000 Subject: [PATCH 8/8] Added unit test --- src/Http/Controllers/RestfulController.php | 13 ++++++---- test/app/Models/User.php | 10 ++++++++ test/config/query-builder.php | 29 ++++++++++++++++++++++ test/copy-over-deps.php | 2 +- test/tests/Api/Get/GetTest.php | 4 +++ test/tests/SetupTestApp.php | 2 ++ 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/config/query-builder.php diff --git a/src/Http/Controllers/RestfulController.php b/src/Http/Controllers/RestfulController.php index 89e2f3e..9121c0f 100644 --- a/src/Http/Controllers/RestfulController.php +++ b/src/Http/Controllers/RestfulController.php @@ -4,7 +4,6 @@ use Illuminate\Database\QueryException; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Input; use Spatie\QueryBuilder\QueryBuilder; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -49,12 +48,16 @@ public function getAll() }), $this->getTransformer()); } - $query = QueryBuilder::for($model::with($model::getCollectionWith())); + $query = QueryBuilder::for($model::with($model::getCollectionWith()), Request()); $this->qualifyCollectionQuery($query); - $query->allowedSorts($model::getAllowedSorts()); + if ($sorts = $model::getAllowedSorts()) { + $query = $query->allowedSorts($sorts); + } - $query->allowedFilters($model::getAllowedFilters()); + if ($filters = $model::getAllowedFilters()) { + $query = $query->allowedFilters($filters); + } // Handle pagination, if applicable $perPage = $model->getPerPage(); @@ -64,7 +67,7 @@ public function getAll() $perPage = intval(request()->input('per_page')); } - $paginator = $query->paginate($perPage)->appends(Input::only(['filter', 'sort'])); + $paginator = $query->paginate($perPage)->appends(request()->only(['filter', 'sort'])); return $this->response->paginator($paginator, $this->getTransformer()); } else { diff --git a/test/app/Models/User.php b/test/app/Models/User.php index b52ba00..cc01baa 100644 --- a/test/app/Models/User.php +++ b/test/app/Models/User.php @@ -31,6 +31,16 @@ class User extends BaseModel implements */ public static $localWith = ['primaryRole', 'roles']; + /** + * @var array Sortable attributes + */ + public static $allowedSorts = ['name']; + + /** + * @var array attribute filters + */ + public static $allowedFilters = ['email']; + /** * The attributes that are mass assignable. * diff --git a/test/config/query-builder.php b/test/config/query-builder.php new file mode 100644 index 0000000..b444354 --- /dev/null +++ b/test/config/query-builder.php @@ -0,0 +1,29 @@ + [ + 'include' => 'include', + + 'filter' => 'filter', + + 'sort' => 'sort', + + 'fields' => 'fields', + + 'append' => 'append', + ], + + /* + * Related model counts are included using the relationship name suffixed with this string. + * For example: GET /users?include=postsCount + */ + 'count_suffix' => 'Count', + +]; diff --git a/test/copy-over-deps.php b/test/copy-over-deps.php index 25fd717..5e256c0 100755 --- a/test/copy-over-deps.php +++ b/test/copy-over-deps.php @@ -3,7 +3,7 @@ $sourceDir = '/www/laravel/bp-demo'; $migrationsDir = './database/migrations'; -$configsToCopy = ['api.php', 'auth.php', 'jwt.php']; +$configsToCopy = ['api.php', 'auth.php', 'jwt.php', 'query-builder.php']; // Copy over database dir echo `rm -rf ./database/*`; diff --git a/test/tests/Api/Get/GetTest.php b/test/tests/Api/Get/GetTest.php index 6a29e3c..98a805c 100644 --- a/test/tests/Api/Get/GetTest.php +++ b/test/tests/Api/Get/GetTest.php @@ -22,6 +22,10 @@ public function testGet() $jsonResponse->assertStatus(200); + $jsonResponse = $this->actingAsAdmin() + ->json('GET', '/users?sort=-name&filter[email]=admin.com'); + + $jsonResponse->assertStatus(200); } } diff --git a/test/tests/SetupTestApp.php b/test/tests/SetupTestApp.php index 218ebc5..391c8d6 100644 --- a/test/tests/SetupTestApp.php +++ b/test/tests/SetupTestApp.php @@ -58,6 +58,7 @@ protected function getEnvironmentSetUp($app) $app['config']->set('api', include __DIR__ . '/../config/api.php'); $app['config']->set('auth', include __DIR__ . '/../config/auth.php'); $app['config']->set('jwt', include __DIR__ . '/../config/jwt.php'); + $app['config']->set('query-builder', include __DIR__ . '/../config/query-builder.php'); } /** @@ -74,6 +75,7 @@ protected function getPackageProviders($app) \Specialtactics\L5Api\L5ApiServiceProvider::class, \Specialtactics\L5Api\Test\Mocks\AppServiceProvider::class, \Specialtactics\L5Api\Test\Mocks\RouteServiceProvider::class, + \Spatie\QueryBuilder\QueryBuilderServiceProvider::class, ]; }