Add functionality to find a caller by phone number

Added a new method in PersonRepository to allow querying people by phone number. Also, a new REST API endpoint "/public/api/1.0/ticket/find-caller" was introduced and it can find a caller by their phone number. Accompanied this feature addition with corresponding test cases.
This commit is contained in:
Julien Fastré 2024-05-17 13:14:26 +02:00
parent 66dc603c85
commit 78d1776733
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 225 additions and 0 deletions

View File

@ -12,10 +12,12 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
use libphonenumber\PhoneNumber;
class PersonRepository implements ObjectRepository
{
@ -29,6 +31,8 @@ class PersonRepository implements ObjectRepository
/**
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*
* @deprecated
*/
public function countByPhone(
string $phonenumber,
@ -71,6 +75,8 @@ class PersonRepository implements ObjectRepository
/**
* @throws \Exception
*
* @deprecated Use @see{self::findByPhoneNumber} or use a dedicated method in PersonACLAwareRepository
*/
public function findByPhone(
string $phonenumber,
@ -91,6 +97,25 @@ class PersonRepository implements ObjectRepository
return $qb->getQuery()->getResult();
}
/**
* Find a person which is associated to the given phonenumber, without restrictions
* on any.
*
* @return list<Person
*/
public function findByPhoneNumber(PhoneNumber $phoneNumber, int $firstResult = 0, int $maxResults = 50): array
{
$qb = $this->repository->createQueryBuilder('p');
$qb->select('p');
$this->searchByPhoneNumbers($qb, $phoneNumber);
$qb->setFirstResult($firstResult)
->setMaxResults($maxResults);
return $qb->getQuery()->getResult();
}
public function findOneBy(array $criteria)
{
return $this->repository->findOneBy($criteria);
@ -109,6 +134,20 @@ class PersonRepository implements ObjectRepository
}
}
private function searchByPhoneNumbers(QueryBuilder $qb, PhoneNumber $phoneNumber): void
{
$qb->setParameter('number', $phoneNumber, 'phone_number');
$orX = $qb->expr()->orX();
$orX->add($qb->expr()->eq('p.mobilenumber', ':number'));
$orX->add($qb->expr()->eq('p.phonenumber', ':number'));
$orX->add(
$qb->expr()->exists('SELECT 1 FROM '.PersonPhone::class.' k WHERE k.phonenumber = :number AND k.person = p')
);
$qb->andWhere($orX);
}
/**
* @throws \Exception
*/

View File

@ -0,0 +1,58 @@
<?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\TicketBundle\Controller;
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use libphonenumber\NumberParseException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller for a rest api to find a caller for a given phonenumber.
*
* TODO: currently, this rest api is not secured
*/
class FindCallerController
{
public function __construct(private PhonenumberHelper $phonenumberHelper, private PersonRepository $personRepository, private PersonRenderInterface $personRender) {}
#[Route('/public/api/1.0/ticket/find-caller', name: 'find-caller', methods: ['GET'])]
public function findCaller(Request $request): Response
{
$caller = $request->query->get('caller', '');
if ('' === $caller) {
throw new BadRequestHttpException('Missing "caller" query parameter');
}
try {
$phoneNumber = $this->phonenumberHelper->parse($caller);
} catch (NumberParseException $e) {
throw new BadRequestHttpException('Unable to parse number', $e);
}
$persons = $this->personRepository->findByPhoneNumber($phoneNumber, 0, 2);
$asArray = match (count($persons)) {
0 => ['found' => false, 'name' => null],
1 => ['found' => true, 'name' => $this->personRender->renderString($persons[0], ['addAge' => false])],
default => ['found' => true, 'name' => 'multiple'],
};
return new JsonResponse($asArray);
}
}

View File

@ -0,0 +1,128 @@
<?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\TicketBundle\Tests\Controller;
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Chill\TicketBundle\Controller\FindCallerController;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* @internal
*
* @coversNothing
*/
class FindCallerControllerTest extends TestCase
{
use ProphecyTrait;
/**
* @dataProvider provideFindCaller
*/
public function testFindCaller(string $caller, array $persons, array $expected): void
{
$controller = $this->buildController($persons);
$request = new Request(query: ['caller' => $caller]);
$response = $controller->findCaller($request);
$actual = json_decode($response->getContent(), true);
self::assertEqualsCanonicalizing($expected, $actual);
}
public function testFindCallerWithoutCallerArgument(): void
{
self::expectException(BadRequestHttpException::class);
$controller = $this->buildController([]);
$request = new Request(query: []);
$controller->findCaller($request);
}
public function testFindCallerWithEmptyCallerArgument(): void
{
self::expectException(BadRequestHttpException::class);
$controller = $this->buildController([]);
$request = new Request(query: ['caller' => '']);
$controller->findCaller($request);
}
public function testFindCallerWithInvalidCaller(): void
{
self::expectException(BadRequestHttpException::class);
$controller = $this->buildController([]);
$request = new Request(query: ['caller' => 'abcde']);
$controller->findCaller($request);
}
public static function provideFindCaller(): iterable
{
yield [
'32486540600',
[],
['found' => false, 'name' => null],
];
yield [
'32486540600',
[new Person()],
['found' => true, 'name' => 'pppp'],
]
;
yield [
'32486540600',
[new Person(), new Person()],
['found' => true, 'name' => 'multiple'],
];
}
private function buildController(array $personsFound): FindCallerController
{
$phonenumberHelper =
$subject = new PhonenumberHelper(
new ArrayAdapter(),
new ParameterBag([
'chill_main.phone_helper' => [
'default_carrier_code' => 'BE',
],
]),
new NullLogger()
);
$personRepository = $this->prophesize(PersonRepository::class);
$personRepository->findByPhoneNumber(Argument::any(), Argument::type('int'), Argument::type('int'))->willReturn($personsFound);
$personRender = $this->prophesize(PersonRenderInterface::class);
$personRender->renderString(Argument::type(Person::class), Argument::type('array'))->willReturn('pppp');
return new FindCallerController($phonenumberHelper, $personRepository->reveal(), $personRender->reveal());
}
}