mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-22 15:43:51 +00:00
[WIP] Add aggregated address search API endpoint
Introduced a new API endpoint `/api/1.0/main/address-reference/aggregated/search` for aggregated address reference search with support for query filtering. Extended repository with `findAggregatedBySearchString` method and updated materialized view `view_chill_main_address_reference`. Added test coverage and API specification details.
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Repository\AddressReferenceRepository;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
|
final readonly class AddressReferenceAggregatedApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Security $security,
|
||||||
|
private AddressReferenceRepository $addressReferenceRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route(path: '/api/1.0/main/address-reference/aggregated/search')]
|
||||||
|
public function search(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('IS_AUTHENTICATED')) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request->query->has('q')) {
|
||||||
|
throw new BadRequestHttpException('Parameter "q" is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$q = trim($request->query->get('q'));
|
||||||
|
|
||||||
|
if ('' === $q) {
|
||||||
|
throw new BadRequestHttpException('Parameter "q" is required and cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->addressReferenceRepository->findAggregatedBySearchString($q);
|
||||||
|
|
||||||
|
return new JsonResponse(iterator_to_array($result));
|
||||||
|
}
|
||||||
|
}
|
@@ -67,6 +67,40 @@ final readonly class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $this->repository->findAll();
|
return $this->repository->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findAggregatedBySearchString(string $search, PostalCode|int|null $postalCode = null, int $firstResult = 0, int $maxResults = 50): iterable
|
||||||
|
{
|
||||||
|
$terms = $this->buildTermsFromSearchString($search);
|
||||||
|
if ([] === $terms) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection = $this->entityManager->getConnection();
|
||||||
|
$qb = $connection->createQueryBuilder();
|
||||||
|
|
||||||
|
$qb->select('var.street AS street', 'cmpc.id AS postcode_id', 'cmpc.code AS code', 'cmpc.label AS label', 'jsonb_object_agg(var.address_id, var.streetnumber ORDER BY var.row_number) AS positions')
|
||||||
|
->from('view_chill_main_address_reference', 'var')
|
||||||
|
->innerJoin('var', 'chill_main_postal_code', 'cmpc', 'cmpc.id = var.postcode_id')
|
||||||
|
->groupBy('cmpc.id', 'var.street')
|
||||||
|
->setFirstResult($firstResult)
|
||||||
|
->setMaxResults($maxResults);
|
||||||
|
|
||||||
|
$paramId = 0;
|
||||||
|
|
||||||
|
foreach ($terms as $k => $term) {
|
||||||
|
$qb->andWhere('var.address like ?');
|
||||||
|
$qb->setParameter(++$paramId, "%{$term}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $postalCode) {
|
||||||
|
$qb->andWhere('var.postcode_id = ?');
|
||||||
|
$qb->setParameter(++$paramId, $postalCode instanceof PostalCode ? $postalCode->getId() : $postalCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $qb->executeQuery();
|
||||||
|
|
||||||
|
return $result->iterateAssociative();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return iterable<AddressReference>
|
* @return iterable<AddressReference>
|
||||||
*/
|
*/
|
||||||
|
@@ -56,6 +56,20 @@ class AddressReferenceRepositoryTest extends KernelTestCase
|
|||||||
self::assertIsInt($actual, $text);
|
self::assertIsInt($actual, $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider generateSearchString
|
||||||
|
*/
|
||||||
|
public function testFindAggreggateBySearchString(string $search, int|PostalCode|null $postalCode, string $text, ?array $expected = null): void
|
||||||
|
{
|
||||||
|
$actual = static::$repository->findAggregatedBySearchString($search, $postalCode);
|
||||||
|
|
||||||
|
self::assertIsIterable($actual, $text);
|
||||||
|
|
||||||
|
if (null !== $expected) {
|
||||||
|
self::assertEquals($expected, iterator_to_array($actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function generateSearchString(): iterable
|
public static function generateSearchString(): iterable
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
|
@@ -595,6 +595,25 @@ paths:
|
|||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/address-reference/aggregated/search:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- address
|
||||||
|
summary: Search for address reference aggregated
|
||||||
|
parameters:
|
||||||
|
- name: q
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The search pattern
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
400:
|
||||||
|
description: "Bad Request"
|
||||||
/1.0/main/postal-code/search.json:
|
/1.0/main/postal-code/search.json:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@@ -24,10 +24,13 @@ final class Version20250214154310 extends AbstractMigration
|
|||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
$this->addSql(<<<'SQL'
|
$this->addSql(<<<'SQL'
|
||||||
create materialized view IF NOT EXISTS view_chill_main_address_reference as
|
create materialized view public.view_chill_main_address_reference as
|
||||||
SELECT row_number() OVER () AS row_number,
|
SELECT row_number() OVER () AS row_number,
|
||||||
|
cmar.street AS street,
|
||||||
|
cmar.streetnumber AS streetnumber,
|
||||||
cmar.id AS address_id,
|
cmar.id AS address_id,
|
||||||
lower(unaccent(cmar.street || ' '::text || cmar.streetnumber || ' '::text || cmpc.code::text || ' '::text ||
|
lower(unaccent(
|
||||||
|
(((((cmar.street || ' '::text) || cmar.streetnumber) || ' '::text) || cmpc.code::text) || ' '::text) ||
|
||||||
cmpc.label::text)) AS address,
|
cmpc.label::text)) AS address,
|
||||||
cmpc.id AS postcode_id
|
cmpc.id AS postcode_id
|
||||||
FROM chill_main_address_reference cmar
|
FROM chill_main_address_reference cmar
|
||||||
|
Reference in New Issue
Block a user