1
0
mirror of https://github.com/flarum/core.git synced 2025-07-31 13:40:20 +02:00

#2492 - Groups filtering & retrieve single endpoint (#3084)

Fixes #2492

* Added api/groups/{id} endpoint for retrieving a single group by its id
* Fixed GroupRepository incorrectly opening query to User instead of Group model
* Added filtering & paging abilities to GET api/groups endpoint
* Added test for sorting for GET api/groups endpoint

Co-authored-by: Alexander Skvortsov <38059171+askvortsov1@users.noreply.github.com>
This commit is contained in:
MatusMak
2021-10-25 17:48:25 +02:00
committed by GitHub
parent a9b1a518a2
commit 5a1bf08d3f
9 changed files with 451 additions and 5 deletions

View File

@@ -10,8 +10,10 @@
namespace Flarum\Api\Controller; namespace Flarum\Api\Controller;
use Flarum\Api\Serializer\GroupSerializer; use Flarum\Api\Serializer\GroupSerializer;
use Flarum\Group\Group; use Flarum\Group\Filter\GroupFilterer;
use Flarum\Http\RequestUtil; use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\Query\QueryCriteria;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document; use Tobscure\JsonApi\Document;
@@ -22,6 +24,38 @@ class ListGroupsController extends AbstractListController
*/ */
public $serializer = GroupSerializer::class; public $serializer = GroupSerializer::class;
/**
* {@inheritdoc}
*/
public $sortFields = ['nameSingular', 'namePlural', 'isHidden'];
/**
* {@inheritdoc}
*
* @var int
*/
public $limit = -1;
/**
* @var GroupFilterer
*/
protected $filterer;
/**
* @var UrlGenerator
*/
protected $url;
/**
* @param GroupFilterer $filterer
* @param UrlGenerator $url
*/
public function __construct(GroupFilterer $filterer, UrlGenerator $url)
{
$this->filterer = $filterer;
$this->url = $url;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -29,10 +63,25 @@ class ListGroupsController extends AbstractListController
{ {
$actor = RequestUtil::getActor($request); $actor = RequestUtil::getActor($request);
$results = Group::whereVisibleTo($actor)->get(); $filters = $this->extractFilter($request);
$sort = $this->extractSort($request);
$sortIsDefault = $this->sortIsDefault($request);
$this->loadRelations($results, []); $limit = $this->extractLimit($request);
$offset = $this->extractOffset($request);
return $results; $criteria = new QueryCriteria($actor, $filters, $sort, $sortIsDefault);
$queryResults = $this->filterer->filter($criteria, $limit, $offset);
$document->addPaginationLinks(
$this->url->to('api')->route('groups.index'),
$request->getQueryParams(),
$offset,
$limit,
$queryResults->areMoreResults() ? null : 0
);
return $queryResults->getResults();
} }
} }

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Api\Controller;
use Flarum\Api\Serializer\GroupSerializer;
use Flarum\Group\GroupRepository;
use Flarum\Http\RequestUtil;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class ShowGroupController extends AbstractShowController
{
/**
* @var GroupRepository
*/
protected $groups;
/**
* {@inheritdoc}
*/
public $serializer = GroupSerializer::class;
/**
* @param \Flarum\Group\GroupRepository $groups
*/
public function __construct(GroupRepository $groups)
{
$this->groups = $groups;
}
/**
* {@inheritdoc}
*/
protected function data(ServerRequestInterface $request, Document $document)
{
$id = Arr::get($request->getQueryParams(), 'id');
$actor = RequestUtil::getActor($request);
$group = $this->groups->findOrFail($id, $actor);
return $group;
}
}

View File

@@ -224,6 +224,13 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
$route->toController(Controller\CreateGroupController::class) $route->toController(Controller\CreateGroupController::class)
); );
// Show a single group
$map->get(
'/groups/{id}',
'groups.show',
$route->toController(Controller\ShowGroupController::class)
);
// Edit a group // Edit a group
$map->patch( $map->patch(
'/groups/{id}', '/groups/{id}',

View File

@@ -13,6 +13,8 @@ use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Query as DiscussionQuery; use Flarum\Discussion\Query as DiscussionQuery;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\ContainerUtil; use Flarum\Foundation\ContainerUtil;
use Flarum\Group\Filter as GroupFilter;
use Flarum\Group\Filter\GroupFilterer;
use Flarum\Post\Filter as PostFilter; use Flarum\Post\Filter as PostFilter;
use Flarum\Post\Filter\PostFilterer; use Flarum\Post\Filter\PostFilterer;
use Flarum\User\Filter\UserFilterer; use Flarum\User\Filter\UserFilterer;
@@ -41,6 +43,9 @@ class FilterServiceProvider extends AbstractServiceProvider
UserQuery\EmailFilterGambit::class, UserQuery\EmailFilterGambit::class,
UserQuery\GroupFilterGambit::class, UserQuery\GroupFilterGambit::class,
], ],
GroupFilterer::class => [
GroupFilter\HiddenFilter::class,
],
PostFilterer::class => [ PostFilterer::class => [
PostFilter\AuthorFilter::class, PostFilter\AuthorFilter::class,
PostFilter\DiscussionFilter::class, PostFilter\DiscussionFilter::class,

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Group\Filter;
use Flarum\Filter\AbstractFilterer;
use Flarum\Group\GroupRepository;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder;
class GroupFilterer extends AbstractFilterer
{
/**
* @var GroupRepository
*/
protected $groups;
/**
* @param GroupRepository $groups
* @param array $filters
* @param array $filterMutators
*/
public function __construct(GroupRepository $groups, array $filters, array $filterMutators)
{
parent::__construct($filters, $filterMutators);
$this->groups = $groups;
}
protected function getQuery(User $actor): Builder
{
return $this->groups->query()->whereVisibleTo($actor);
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Group\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
class HiddenFilter implements FilterInterface
{
public function getFilterKey(): string
{
return 'hidden';
}
public function filter(FilterState $filterState, string $filterValue, bool $negate)
{
$filterState->getQuery()->where('is_hidden', $negate ? '!=' : '=', $filterValue);
}
}

View File

@@ -21,7 +21,7 @@ class GroupRepository
*/ */
public function query() public function query()
{ {
return User::query(); return Group::query();
} }
/** /**

View File

@@ -65,6 +65,148 @@ class ListTest extends TestCase
$this->assertEquals(['1', '2', '3', '4', '10'], Arr::pluck($data['data'], 'id')); $this->assertEquals(['1', '2', '3', '4', '10'], Arr::pluck($data['data'], 'id'));
} }
/**
* @test
*/
public function filters_only_public_groups_for_admin()
{
$response = $this->send(
$this->request('GET', '/api/groups', [
'authenticatedAs' => 1,
])
->withQueryParams([
'filter' => ['hidden' => 0],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// The four default groups created by the installer without our hidden group
$this->assertEquals(['1', '2', '3', '4'], Arr::pluck($data['data'], 'id'));
}
/**
* @test
*/
public function filters_only_hidden_groups_for_admin()
{
$response = $this->send(
$this->request('GET', '/api/groups', [
'authenticatedAs' => 1,
])
->withQueryParams([
'filter' => ['hidden' => 1],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Only our hidden group
$this->assertEquals(['10'], Arr::pluck($data['data'], 'id'));
}
/**
* @test
*/
public function filters_only_public_groups_for_guest()
{
$response = $this->send(
$this->request('GET', '/api/groups')
->withQueryParams([
'filter' => ['hidden' => 0],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// The four default groups created by the installer without our hidden group
$this->assertEquals(['1', '2', '3', '4'], Arr::pluck($data['data'], 'id'));
}
/**
* @test
*/
public function hides_hidden_groups_when_filtering_for_guest()
{
$response = $this->send(
$this->request('GET', '/api/groups')
->withQueryParams([
'filter' => ['hidden' => 1],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// When guest attempts to filter for hidden groups, system should
// still apply scoping and exclude those groups from results
$this->assertEquals([], Arr::pluck($data['data'], 'id'));
}
/**
* @test
*/
public function paginates_groups_without_filter()
{
$response = $this->send(
$this->request('GET', '/api/groups')
->withQueryParams([
'page' => ['limit' => '2', 'offset' => '2'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Show second page of groups
$this->assertEquals(['3', '4'], Arr::pluck($data['data'], 'id'));
}
/**
* @test
*/
public function paginates_groups_with_filter()
{
$response = $this->send(
$this->request('GET', '/api/groups')
->withQueryParams([
'filter' => ['hidden' => 1],
'page' => ['limit' => '1', 'offset' => '1'],
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Show second page of groups. Because there is only one hidden group,
// second page should be empty.
$this->assertEmpty($data['data']);
}
/**
* @test
*/
public function sorts_groups_by_name()
{
$response = $this->send(
$this->request('GET', '/api/groups', [
'authenticatedAs' => 1,
])
->withQueryParams([
'sort' => 'nameSingular',
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Ascending alphabetical order is: Admin - Guest - Hidden - Member - Mod
$this->assertEquals(['1', '2', '10', '3', '4'], Arr::pluck($data['data'], 'id'));
}
protected function hiddenGroup(): array protected function hiddenGroup(): array
{ {
return [ return [

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\api\groups;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ShowTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->prepareDatabase([
'groups' => [
$this->hiddenGroup(),
],
]);
}
/**
* @test
*/
public function shows_public_group_for_guest()
{
$response = $this->send(
$this->request('GET', '/api/groups/1')
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Default group created by the installer should be returned
$this->assertEquals('1', Arr::get($data, 'data.id'));
}
/**
* @test
*/
public function shows_public_group_for_admin()
{
$response = $this->send(
$this->request('GET', '/api/groups/1', [
'authenticatedAs' => 1,
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Default group created by the installer should be returned
$this->assertEquals('1', Arr::get($data, 'data.id'));
}
/**
* @test
*/
public function hides_hidden_group_for_guest()
{
$response = $this->send(
$this->request('GET', '/api/groups/10')
);
// Hidden group should not be returned for guest
$this->assertEquals(404, $response->getStatusCode());
}
/**
* @test
*/
public function shows_hidden_group_for_admin()
{
$response = $this->send(
$this->request('GET', '/api/groups/10', [
'authenticatedAs' => 1,
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
// Hidden group should be returned for admin
$this->assertEquals('10', Arr::get($data, 'data.id'));
}
/**
* @test
*/
public function rejects_request_for_non_existing_group()
{
$response = $this->send(
$this->request('GET', '/api/groups/999', [
'authenticatedAs' => 1,
])
);
// If group does not exist in database, controller
// should reject the request with 404 Not found
$this->assertEquals(404, $response->getStatusCode());
}
protected function hiddenGroup(): array
{
return [
'id' => 10,
'name_singular' => 'Hidden',
'name_plural' => 'Ninjas',
'color' => null,
'icon' => 'fas fa-wrench',
'is_hidden' => 1
];
}
}