mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
extend search api to users
This commit is contained in:
parent
6b4e27a531
commit
54c4524b27
@ -22,7 +22,9 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Search\SearchApiNoQueryException;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Chill\MainBundle\Search\UnknowSearchDomainException;
|
use Chill\MainBundle\Search\UnknowSearchDomainException;
|
||||||
@ -33,6 +35,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|||||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Chill\MainBundle\Search\SearchProvider;
|
use Chill\MainBundle\Search\SearchProvider;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Search\SearchApi;
|
use Chill\MainBundle\Search\SearchApi;
|
||||||
@ -46,15 +49,15 @@ use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
|||||||
class SearchController extends AbstractController
|
class SearchController extends AbstractController
|
||||||
{
|
{
|
||||||
protected SearchProvider $searchProvider;
|
protected SearchProvider $searchProvider;
|
||||||
|
|
||||||
protected TranslatorInterface $translator;
|
protected TranslatorInterface $translator;
|
||||||
|
|
||||||
protected PaginatorFactory $paginatorFactory;
|
protected PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
protected SearchApi $searchApi;
|
protected SearchApi $searchApi;
|
||||||
|
|
||||||
function __construct(
|
function __construct(
|
||||||
SearchProvider $searchProvider,
|
SearchProvider $searchProvider,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
PaginatorFactory $paginatorFactory,
|
PaginatorFactory $paginatorFactory,
|
||||||
SearchApi $searchApi
|
SearchApi $searchApi
|
||||||
@ -65,14 +68,14 @@ class SearchController extends AbstractController
|
|||||||
$this->searchApi = $searchApi;
|
$this->searchApi = $searchApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function searchAction(Request $request, $_format)
|
public function searchAction(Request $request, $_format)
|
||||||
{
|
{
|
||||||
$pattern = $request->query->get('q', '');
|
$pattern = $request->query->get('q', '');
|
||||||
|
|
||||||
if ($pattern === ''){
|
if ($pattern === ''){
|
||||||
switch($_format) {
|
switch($_format) {
|
||||||
case 'html':
|
case 'html':
|
||||||
return $this->render('@ChillMain/Search/error.html.twig',
|
return $this->render('@ChillMain/Search/error.html.twig',
|
||||||
array(
|
array(
|
||||||
'message' => $this->translator->trans("Your search is empty. "
|
'message' => $this->translator->trans("Your search is empty. "
|
||||||
@ -86,16 +89,16 @@ class SearchController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $request->query->get('name', NULL);
|
$name = $request->query->get('name', NULL);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($name === NULL) {
|
if ($name === NULL) {
|
||||||
if ($_format === 'json') {
|
if ($_format === 'json') {
|
||||||
return new JsonResponse('Currently, we still do not aggregate results '
|
return new JsonResponse('Currently, we still do not aggregate results '
|
||||||
. 'from different providers', JsonResponse::HTTP_BAD_REQUEST);
|
. 'from different providers', JsonResponse::HTTP_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no specific search selected. Rendering result in "preview" mode
|
// no specific search selected. Rendering result in "preview" mode
|
||||||
$results = $this->searchProvider
|
$results = $this->searchProvider
|
||||||
->getSearchResults(
|
->getSearchResults(
|
||||||
@ -119,7 +122,7 @@ class SearchController extends AbstractController
|
|||||||
),
|
),
|
||||||
$_format
|
$_format
|
||||||
)];
|
)];
|
||||||
|
|
||||||
if ($_format === 'json') {
|
if ($_format === 'json') {
|
||||||
return new JsonResponse(\reset($results));
|
return new JsonResponse(\reset($results));
|
||||||
}
|
}
|
||||||
@ -141,8 +144,8 @@ class SearchController extends AbstractController
|
|||||||
'pattern' => $pattern
|
'pattern' => $pattern
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $this->render('@ChillMain/Search/list.html.twig',
|
return $this->render('@ChillMain/Search/list.html.twig',
|
||||||
array('results' => $results, 'pattern' => $pattern)
|
array('results' => $results, 'pattern' => $pattern)
|
||||||
);
|
);
|
||||||
@ -159,29 +162,33 @@ class SearchController extends AbstractController
|
|||||||
." one type");
|
." one type");
|
||||||
}
|
}
|
||||||
|
|
||||||
$collection = $this->searchApi->getResults($query, $types, []);
|
try {
|
||||||
|
$collection = $this->searchApi->getResults($query, $types, []);
|
||||||
|
} catch (SearchApiNoQueryException $e) {
|
||||||
|
throw new BadRequestHttpException($e->getMessage(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->json($collection);
|
return $this->json($collection, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], [ "groups" => ["read"]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function advancedSearchListAction(Request $request)
|
public function advancedSearchListAction(Request $request)
|
||||||
{
|
{
|
||||||
/* @var $variable Chill\MainBundle\Search\SearchProvider */
|
/* @var $variable Chill\MainBundle\Search\SearchProvider */
|
||||||
$searchProvider = $this->searchProvider;
|
$searchProvider = $this->searchProvider;
|
||||||
$advancedSearchProviders = $searchProvider
|
$advancedSearchProviders = $searchProvider
|
||||||
->getHasAdvancedFormSearchServices();
|
->getHasAdvancedFormSearchServices();
|
||||||
|
|
||||||
if(\count($advancedSearchProviders) === 1) {
|
if(\count($advancedSearchProviders) === 1) {
|
||||||
\reset($advancedSearchProviders);
|
\reset($advancedSearchProviders);
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_main_advanced_search', [
|
return $this->redirectToRoute('chill_main_advanced_search', [
|
||||||
'name' => \key($advancedSearchProviders)
|
'name' => \key($advancedSearchProviders)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('@ChillMain/Search/choose_list.html.twig');
|
return $this->render('@ChillMain/Search/choose_list.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function advancedSearchAction($name, Request $request)
|
public function advancedSearchAction($name, Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -190,22 +197,22 @@ class SearchController extends AbstractController
|
|||||||
/* @var $variable Chill\MainBundle\Search\HasAdvancedSearchFormInterface */
|
/* @var $variable Chill\MainBundle\Search\HasAdvancedSearchFormInterface */
|
||||||
$search = $this->searchProvider
|
$search = $this->searchProvider
|
||||||
->getHasAdvancedFormByName($name);
|
->getHasAdvancedFormByName($name);
|
||||||
|
|
||||||
} catch (\Chill\MainBundle\Search\UnknowSearchNameException $e) {
|
} catch (\Chill\MainBundle\Search\UnknowSearchNameException $e) {
|
||||||
throw $this->createNotFoundException("no advanced search for "
|
throw $this->createNotFoundException("no advanced search for "
|
||||||
. "$name");
|
. "$name");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->query->has('q')) {
|
if ($request->query->has('q')) {
|
||||||
$data = $search->convertTermsToFormData($searchProvider->parse(
|
$data = $search->convertTermsToFormData($searchProvider->parse(
|
||||||
$request->query->get('q')));
|
$request->query->get('q')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->createAdvancedSearchForm($name, $data ?? []);
|
$form = $this->createAdvancedSearchForm($name, $data ?? []);
|
||||||
|
|
||||||
if ($request->isMethod(Request::METHOD_POST)) {
|
if ($request->isMethod(Request::METHOD_POST)) {
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$pattern = $this->searchProvider
|
$pattern = $this->searchProvider
|
||||||
->getHasAdvancedFormByName($name)
|
->getHasAdvancedFormByName($name)
|
||||||
@ -215,8 +222,8 @@ class SearchController extends AbstractController
|
|||||||
'q' => $pattern, 'name' => $name
|
'q' => $pattern, 'name' => $name
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('@ChillMain/Search/advanced_search.html.twig',
|
return $this->render('@ChillMain/Search/advanced_search.html.twig',
|
||||||
[
|
[
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
@ -224,15 +231,15 @@ class SearchController extends AbstractController
|
|||||||
'title' => $search->getAdvancedSearchTitle()
|
'title' => $search->getAdvancedSearchTitle()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createAdvancedSearchForm($name, array $data = [])
|
protected function createAdvancedSearchForm($name, array $data = [])
|
||||||
{
|
{
|
||||||
$builder = $this
|
$builder = $this
|
||||||
->get('form.factory')
|
->get('form.factory')
|
||||||
->createNamedBuilder(
|
->createNamedBuilder(
|
||||||
null,
|
null,
|
||||||
FormType::class,
|
FormType::class,
|
||||||
$data,
|
$data,
|
||||||
[ 'method' => Request::METHOD_POST ]
|
[ 'method' => Request::METHOD_POST ]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -240,12 +247,12 @@ class SearchController extends AbstractController
|
|||||||
->getHasAdvancedFormByName($name)
|
->getHasAdvancedFormByName($name)
|
||||||
->buildForm($builder)
|
->buildForm($builder)
|
||||||
;
|
;
|
||||||
|
|
||||||
$builder->add('submit', SubmitType::class, [
|
$builder->add('submit', SubmitType::class, [
|
||||||
'label' => 'Search'
|
'label' => 'Search'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $builder->getForm();
|
return $builder->getForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Search\Entity;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Repository\UserRepository;
|
||||||
|
use Chill\MainBundle\Search\SearchApiInterface;
|
||||||
|
use Chill\MainBundle\Search\SearchApiQuery;
|
||||||
|
|
||||||
|
class SearchUserApiProvider implements SearchApiInterface
|
||||||
|
{
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param UserRepository $userRepository
|
||||||
|
*/
|
||||||
|
public function __construct(UserRepository $userRepository)
|
||||||
|
{
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideQuery(string $pattern, array $parameters): SearchApiQuery
|
||||||
|
{
|
||||||
|
$query = new SearchApiQuery();
|
||||||
|
$query
|
||||||
|
->setSelectKey("user")
|
||||||
|
->setSelectJsonbMetadata("jsonb_build_object('id', u.id)")
|
||||||
|
->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical),
|
||||||
|
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ])
|
||||||
|
->setFromClause("users AS u")
|
||||||
|
->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15
|
||||||
|
OR
|
||||||
|
SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15
|
||||||
|
", [ $pattern, $pattern ]);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsTypes(string $pattern, array $types, array $parameters): bool
|
||||||
|
{
|
||||||
|
return \in_array('user', $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare(array $metadatas): void
|
||||||
|
{
|
||||||
|
$ids = \array_map(fn($m) => $m['id'], $metadatas);
|
||||||
|
|
||||||
|
$this->userRepository->findBy([ 'id' => $ids ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsResult(string $key, array $metadatas): bool
|
||||||
|
{
|
||||||
|
return $key === 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult(string $key, array $metadata, float $pertinence)
|
||||||
|
{
|
||||||
|
return $this->userRepository->find($metadata['id']);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Search;
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Search\Entity\SearchUserApiProvider;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\PersonBundle\Search\SearchPersonApiProvider;
|
use Chill\PersonBundle\Search\SearchPersonApiProvider;
|
||||||
@ -25,12 +26,14 @@ class SearchApi
|
|||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
SearchPersonApiProvider $searchPerson,
|
SearchPersonApiProvider $searchPerson,
|
||||||
ThirdPartyApiSearch $thirdPartyApiSearch,
|
ThirdPartyApiSearch $thirdPartyApiSearch,
|
||||||
|
SearchUserApiProvider $searchUser,
|
||||||
PaginatorFactory $paginator
|
PaginatorFactory $paginator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
$this->providers[] = $searchPerson;
|
$this->providers[] = $searchPerson;
|
||||||
$this->providers[] = $thirdPartyApiSearch;
|
$this->providers[] = $thirdPartyApiSearch;
|
||||||
|
$this->providers[] = $searchUser;
|
||||||
$this->paginator = $paginator;
|
$this->paginator = $paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +44,10 @@ class SearchApi
|
|||||||
{
|
{
|
||||||
$queries = $this->findQueries($pattern, $types, $parameters);
|
$queries = $this->findQueries($pattern, $types, $parameters);
|
||||||
|
|
||||||
|
if (0 === count($queries)) {
|
||||||
|
throw new SearchApiNoQueryException($pattern, $types, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
$total = $this->countItems($queries, $types, $parameters);
|
$total = $this->countItems($queries, $types, $parameters);
|
||||||
$paginator = $this->paginator->create($total);
|
$paginator = $this->paginator->create($total);
|
||||||
|
|
||||||
@ -49,9 +56,7 @@ class SearchApi
|
|||||||
$this->prepareProviders($rawResults);
|
$this->prepareProviders($rawResults);
|
||||||
$results = $this->buildResults($rawResults);
|
$results = $this->buildResults($rawResults);
|
||||||
|
|
||||||
$collection = new Collection($results, $paginator);
|
return new Collection($results, $paginator);
|
||||||
|
|
||||||
return $collection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findQueries($pattern, array $types, array $parameters): array
|
private function findQueries($pattern, array $types, array $parameters): array
|
||||||
@ -77,7 +82,7 @@ class SearchApi
|
|||||||
$rsmCount->addScalarResult('count', 'count');
|
$rsmCount->addScalarResult('count', 'count');
|
||||||
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
|
$countNq = $this->em->createNativeQuery($countQuery, $rsmCount);
|
||||||
$countNq->setParameters($parameters);
|
$countNq->setParameters($parameters);
|
||||||
|
|
||||||
return $countNq->getSingleScalarResult();
|
return $countNq->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +135,7 @@ class SearchApi
|
|||||||
|
|
||||||
$nq = $this->em->createNativeQuery($union, $rsm);
|
$nq = $this->em->createNativeQuery($union, $rsm);
|
||||||
$nq->setParameters($parameters);
|
$nq->setParameters($parameters);
|
||||||
|
|
||||||
return $nq->getResult();
|
return $nq->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +147,7 @@ class SearchApi
|
|||||||
if ($p->supportsResult($r['key'], $r['metadata'])) {
|
if ($p->supportsResult($r['key'], $r['metadata'])) {
|
||||||
$metadatas[$k][] = $r['metadata'];
|
$metadatas[$k][] = $r['metadata'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +166,7 @@ class SearchApi
|
|||||||
$p->getResult($r['key'], $r['metadata'], $r['pertinence'])
|
$p->getResult($r['key'], $r['metadata'], $r['pertinence'])
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Search;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class SearchApiNoQueryException extends \RuntimeException
|
||||||
|
{
|
||||||
|
private string $pattern;
|
||||||
|
private array $types;
|
||||||
|
private array $parameters;
|
||||||
|
|
||||||
|
public function __construct(string $pattern = "", array $types = [], array $parameters = [], $code = 0, Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$typesStr = \implode(", ", $types);
|
||||||
|
$message = "No query for this search: pattern : {$pattern}, types: {$typesStr}";
|
||||||
|
$this->pattern = $pattern;
|
||||||
|
$this->types = $types;
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
@ -127,8 +127,6 @@ paths:
|
|||||||
- person
|
- person
|
||||||
- thirdparty
|
- thirdparty
|
||||||
description: >
|
description: >
|
||||||
**Warning**: This is currently a stub (not really implemented
|
|
||||||
|
|
||||||
The search is performed across multiple entities. The entities must be listed into
|
The search is performed across multiple entities. The entities must be listed into
|
||||||
`type` parameters.
|
`type` parameters.
|
||||||
|
|
||||||
@ -152,6 +150,7 @@ paths:
|
|||||||
enum:
|
enum:
|
||||||
- person
|
- person
|
||||||
- thirdparty
|
- thirdparty
|
||||||
|
- user
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
|
@ -7,3 +7,8 @@ services:
|
|||||||
Chill\MainBundle\Search\SearchApi:
|
Chill\MainBundle\Search\SearchApi:
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\MainBundle\Search\Entity\:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
resource: '../../Search/Entity'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user