Skip to content

Commit

Permalink
[User Management] Add user Search (#552)
Browse files Browse the repository at this point in the history
* Add user search.

* Add translation.

* Apply php-cs-fixer changes

* Add SimpleUserHydrator to tests.

* Change Error.

* Fix typo.

---------

Co-authored-by: martineiber <[email protected]>
  • Loading branch information
martineiber and martineiber authored Nov 14, 2024
1 parent a93fac0 commit 5810041
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 20 deletions.
3 changes: 3 additions & 0 deletions config/users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ services:
Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserInformationHydratorInterface:
class: Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserInformationHydrator

Pimcore\Bundle\StudioBackendBundle\User\Hydrator\SimpleUserHydratorInterface:
class: Pimcore\Bundle\StudioBackendBundle\User\Hydrator\SimpleUserHydrator


#
# Repositories
Expand Down
84 changes: 84 additions & 0 deletions src/User/Controller/SearchUserController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Controller;

use OpenApi\Attributes\Get;
use Pimcore\Bundle\StudioBackendBundle\Controller\AbstractApiController;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\DatabaseException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Parameter\Query\TextFieldParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Property\GenericCollection;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\Content\CollectionJson;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
use Pimcore\Bundle\StudioBackendBundle\User\Schema\SimpleUser;
use Pimcore\Bundle\StudioBackendBundle\User\Service\UserServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\UserPermissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\PaginatedResponseTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\SerializerInterface;

/**
* @internal
*/
final class SearchUserController extends AbstractApiController
{
use PaginatedResponseTrait;

public function __construct(
SerializerInterface $serializer,
private readonly UserServiceInterface $userService
) {
parent::__construct($serializer);
}

/**
* @throws NotFoundException|DatabaseException
*/
#[Route('/user/search', name: 'pimcore_studio_api_user_search', methods: ['GET'])]
#[IsGranted(UserPermissions::USER_MANAGEMENT->value)]
#[Get(
path: self::PREFIX . '/user/search',
operationId: 'pimcore_studio_api_user_search',
summary: 'user_search_summary',
tags: [Tags::User->value]
)]
#[TextFieldParameter(
name: 'searchQuery',
description: 'Query to search for an user. This can be a part of username, firstname, lastname, email or id.',
required: false
)]
#[SuccessResponse(
description: 'user_search_summary_response',
content: new CollectionJson(new GenericCollection(SimpleUser::class))
)]
#[DefaultResponses([
HttpResponseCodes::NOT_FOUND,
HttpResponseCodes::INTERNAL_SERVER_ERROR,
])]
public function getUserSearch(#[MapQueryParameter] string $searchQuery): JsonResponse
{
return $this->jsonResponse(
$this->userService->userSearch($searchQuery)
);
}
}
34 changes: 34 additions & 0 deletions src/User/Hydrator/SimpleUserHydrator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Hydrator;

use Pimcore\Bundle\StudioBackendBundle\User\Schema\SimpleUser;
use Pimcore\Model\UserInterface;

/**
* @internal
*/
final class SimpleUserHydrator implements SimpleUserHydratorInterface
{
public function hydrate(UserInterface $user): SimpleUser
{
return new SimpleUser(
id: $user->getId(),
username: $user->getName(),
);
}
}
28 changes: 28 additions & 0 deletions src/User/Hydrator/SimpleUserHydratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\User\Hydrator;

use Pimcore\Bundle\StudioBackendBundle\User\Schema\SimpleUser;
use Pimcore\Model\UserInterface;

/**
* @internal
*/
interface SimpleUserHydratorInterface
{
public function hydrate(UserInterface $user): SimpleUser;
}
32 changes: 32 additions & 0 deletions src/User/Repository/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,36 @@ public function getUsers(): array
throw new DatabaseException(sprintf('Error while fetching users: %s', $e->getMessage()));
}
}

/**
* @return UserInterface[]
*
* @throws DatabaseException
*/
public function searchUser(string $searchQuery): array
{
$list = [];
$q = '%' . $searchQuery . '%';

try {
$userListing = new UserListing();
$userListing->setCondition(
'name LIKE ? OR firstname LIKE ? OR lastname LIKE ? OR email LIKE ? OR id = ?',
[$q, $q, $q, $q, (int)$searchQuery]
);
$userListing->setOrder('ASC');
$userListing->setOrderKey('name');
$userListing->load();

foreach ($userListing->getUsers() as $user) {
if ($user instanceof UserInterface && $user->getName() !== 'system') {
$list[] = $user;
}
}

return $list;
} catch (Exception $e) {
throw new DatabaseException(sprintf('Error while searching for users: %s', $e->getMessage()));
}
}
}
7 changes: 7 additions & 0 deletions src/User/Repository/UserRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ public function getUserListingByRoleId(int $roleId, ?int $excludeUserId = null):
* @throws DatabaseException
*/
public function getUsers(): array;

/**
* @return UserInterface[]
*
* @throws DatabaseException
*/
public function searchUser(string $searchQuery): array;
}
56 changes: 38 additions & 18 deletions src/User/Service/UserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
use Pimcore\Bundle\StudioBackendBundle\User\Event\SimpleUserEvent;
use Pimcore\Bundle\StudioBackendBundle\User\Event\UserEvent;
use Pimcore\Bundle\StudioBackendBundle\User\Event\UserTreeNodeEvent;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\SimpleUserHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserTreeNodeHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\MappedParameter\CreateParameter;
use Pimcore\Bundle\StudioBackendBundle\User\RateLimiter\RateLimiterInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Repository\UserFolderRepositoryInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Repository\UserRepositoryInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Schema\ResetPassword;
use Pimcore\Bundle\StudioBackendBundle\User\Schema\SimpleUser;
use Pimcore\Bundle\StudioBackendBundle\User\Schema\User as UserSchema;
use Pimcore\Model\UserInterface;
use Psr\Log\LoggerInterface;
Expand All @@ -63,7 +63,8 @@ public function __construct(
private EventDispatcherInterface $eventDispatcher,
private SecurityServiceInterface $securityService,
private UserFolderRepositoryInterface $userFolderRepository,
private UserHydratorInterface $userHydrator
private UserHydratorInterface $userHydrator,
private SimpleUserHydratorInterface $simpleUserHydrator
) {
}

Expand Down Expand Up @@ -148,26 +149,14 @@ public function deleteUser(int $userId): void

}

/**
* @throws DatabaseException
*/
public function getUsers(): Collection
{
$users = $this->userRepository->getUsers();
$items = [];

foreach ($users as $user) {
$item = new SimpleUser(
id: $user->getId(),
username: $user->getName(),
);

$this->eventDispatcher->dispatch(
new SimpleUserEvent($item),
SimpleUserEvent::EVENT_NAME
);

$items[] = $item;
}

return new Collection(count($items), $items);
return $this->getSimpleUserCollection($users);
}

/**
Expand Down Expand Up @@ -237,4 +226,35 @@ public function getUserById(int $userId): UserSchema

return $user;
}

/**
* @throws DatabaseException
*/
public function userSearch(string $searchQuery): Collection
{
$users = $this->userRepository->searchUser($searchQuery);

return $this->getSimpleUserCollection($users);
}

/**
* @param UserInterface[] $users
*/
private function getSimpleUserCollection(array $users): Collection
{
$items = [];

foreach ($users as $user) {
$item = $this->simpleUserHydrator->hydrate($user);

$this->eventDispatcher->dispatch(
new SimpleUserEvent($item),
SimpleUserEvent::EVENT_NAME
);

$items[] = $item;
}

return new Collection(count($items), $items);
}
}
5 changes: 5 additions & 0 deletions src/User/Service/UserServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ public function getUserById(int $userId): UserSchema;
* @throws DatabaseException
*/
public function getUsers(): Collection;

/**
* @throws DatabaseException
*/
public function userSearch(string $searchQuery): Collection;
}
5 changes: 4 additions & 1 deletion tests/Unit/User/Service/UserServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\DatabaseException;
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\SimpleUserHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\Hydrator\UserTreeNodeHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\User\RateLimiter\RateLimiterInterface;
Expand Down Expand Up @@ -115,6 +116,7 @@ private function getUserService(
$eventDispatcherMock = $this->makeEmpty(EventDispatcherInterface::class);
$userFolderRepositoryMock = $this->makeEmpty(UserFolderRepositoryInterface::class);
$userHydratorMock = $this->makeEmpty(UserHydratorInterface::class);
$simpleUserHydratorMock = $this->makeEmpty(SimpleUserHydratorInterface::class);

return new UserService(
$authenticationResolverMock,
Expand All @@ -127,7 +129,8 @@ private function getUserService(
$eventDispatcherMock,
$securityServiceMock,
$userFolderRepositoryMock,
$userHydratorMock
$userHydratorMock,
$simpleUserHydratorMock
);
}
}
4 changes: 3 additions & 1 deletion translations/studio_api_docs.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,6 @@ data_object_get_grid_success_response: Data object grid data
data_object_get_grid_summary: Get data object data for grid
asset_delete_grid_configuration_by_configurationId_description: |
Delete grid configuration for a specific folder and given configuration ID <strong>{configurationId}</strong>
asset_delete_grid_configuration_by_configurationId_summary: Delete grid configuration for a specific folder and given configuration ID
asset_delete_grid_configuration_by_configurationId_summary: Delete grid configuration for a specific folder and given configuration ID
user_search_summary: Search for users by query. The query can be a part of the username, first name, last name, email or user ID.
user_search_response: List of users

0 comments on commit 5810041

Please sign in to comment.