diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php index 018d23426..507810dcb 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php @@ -11,38 +11,63 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Controller; +use Chill\CalendarBundle\Repository\CalendarRangeRepository; use Chill\MainBundle\CRUD\Controller\ApiController; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Serializer\Model\Collection; 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; use function count; class CalendarRangeAPIController extends ApiController { - /** - * @Route("/api/1.0/calendar/calendar-range-available.{_format}", name="chill_api_single_calendar_range_available") - */ - public function availableRanges(Request $request, string $_format): JsonResponse + private CalendarRangeRepository $calendarRangeRepository; + + public function __construct(CalendarRangeRepository $calendarRangeRepository) { - $em = $this->getDoctrine()->getManager(); + $this->calendarRangeRepository = $calendarRangeRepository; + } - $sql = 'SELECT c FROM ChillCalendarBundle:CalendarRange c - WHERE NOT EXISTS (SELECT cal.id FROM ChillCalendarBundle:Calendar cal WHERE cal.calendarRange = c.id)'; + /** + * @Route("/api/1.0/calendar/calendar-range-available/{id}.{_format}", + * name="chill_api_single_calendar_range_available", + * requirements={"_format": "json"} + * ) + */ + public function availableRanges(User $user, Request $request, string $_format): JsonResponse + { + //return new JsonResponse(['ok' => true], 200, [], false); + $this->denyAccessUnlessGranted('ROLE_USER'); - if ($request->query->has('user')) { - $user = $request->query->get('user'); - $sql = $sql . ' AND c.user = :user'; - $query = $em->createQuery($sql) - ->setParameter('user', $user); - } else { - $query = $em->createQuery($sql); + if (!$request->query->has('dateFrom')) { + throw new BadRequestHttpException('You must provide a dateFrom parameter'); } - $results = $query->getResult(); + if (false === $dateFrom = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, + $request->query->get('dateFrom'))) { + throw new BadRequestHttpException('dateFrom not parsable'); + } - return $this->json(['count' => count($results), 'results' => $results], Response::HTTP_OK, [], ['groups' => ['read']]); - //TODO use also the paginator, eg return $this->serializeCollection('get', $request, $_format, $paginator, $results); + if (!$request->query->has('dateTo')) { + throw new BadRequestHttpException('You must provide a dateTo parameter'); + } + + if (false === $dateTo = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::ATOM, + $request->query->get('dateTo'))) { + throw new BadRequestHttpException('dateTo not parsable'); + } + + $total = $this->calendarRangeRepository->countByAvailableRangesForUser($user, $dateFrom, $dateTo); + $paginator = $this->getPaginatorFactory()->create($total); + $ranges = $this->calendarRangeRepository->findByAvailableRangesForUser($user, $dateFrom, $dateTo, + $paginator->getItemsPerPage(), $paginator->getCurrentPageFirstItemNumber()); + + $collection = new Collection($ranges, $paginator); + + return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['read']]); } } diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php index a16c7b72b..6b0f79c9b 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php @@ -25,7 +25,7 @@ use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Table(name="chill_calendar.calendar_range", indexes={@ORM\Index(name="idx_calendar_range_remote", columns={"remoteId"})}) - * @ORM\Entity(repositoryClass=CalendarRangeRepository::class) + * @ORM\Entity */ class CalendarRange implements TrackCreationInterface, TrackUpdateInterface { diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php index 5286c5515..76d62915a 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRangeRepository.php @@ -12,48 +12,107 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Repository; use Chill\CalendarBundle\Entity\CalendarRange; +use Chill\MainBundle\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectRepository; +use Monolog\DateTimeImmutable; +use UnexpectedValueException; -/** - * @method CalendarRange|null find($id, $lockMode = null, $lockVersion = null) - * @method CalendarRange|null findOneBy(array $criteria, array $orderBy = null) - * @method CalendarRange[] findAll() - * @method CalendarRange[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) - */ -class CalendarRangeRepository extends ServiceEntityRepository +class CalendarRangeRepository implements ObjectRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, CalendarRange::class); + $this->repository = $entityManager->getRepository(CalendarRange::class); } - // /** - // * @return CalendarRange[] Returns an array of CalendarRange objects - // */ - /* - public function findByExampleField($value) + public function find($id): ?CalendarRange { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('c.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; + return $this->repository->find($id); } - */ - /* - public function findOneBySomeField($value): ?CalendarRange + /** + * @return array|CalendarRange[] + */ + public function findAll(): array { - return $this->createQueryBuilder('c') - ->andWhere('c.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() + return $this->repository->findAll(); + } + + + /** + * @return array|CalendarRange[] + */ + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?CalendarRange + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return CalendarRange::class; + } + + public function countByAvailableRangesForUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): int + { + return $this->buildQueryAvailableRangesForUser($user, $from, $to) + ->select('COUNT(cr)') + ->getQuery()->getSingleScalarResult(); + } + + /** + * @return array|CalendarRange[] + */ + public function findByAvailableRangesForUser( + User $user, + \DateTimeImmutable $from, + \DateTimeImmutable $to, + ?int $limit = null, + ?int $offset = null + ): array { + $qb = $this->buildQueryAvailableRangesForUser($user, $from, $to); + + if ($limit !== null) { + $qb->setMaxResults($limit); + } + + if ($offset !== null) { + $qb->setFirstResult($offset); + } + + return $qb->getQuery()->getResult(); + } + + private function buildQueryAvailableRangesForUser(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('cr'); + + return $qb + ->where( + $qb->expr()->andX( + $qb->expr()->eq('cr.user', ':user'), + $qb->expr()->gte('cr.startDate', ':startDate'), + $qb->expr()->lte('cr.endDate', ':endDate'), + $qb->expr()->eq(0, 'SIZE(cr.calendars)') + ) + ) + ->setParameters([ + 'user' => $user, + 'startDate' => $from, + 'endDate' => $to, + ]) ; } - */ + + } diff --git a/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml b/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml index ebe0de88d..98e90d20d 100644 --- a/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillCalendarBundle/chill.api.specs.yaml @@ -170,12 +170,35 @@ paths: description: "OK" 422: description: "object with validation errors" - - /1.0/calendar/calendar-range-available.json: + + /1.0/calendar/calendar-range-available/{userId}.json: get: tags: - calendar summary: Return a list of available calendar range items. Available means calendar-range not being taken by a calendar entity + parameters: + - name: userId + in: path + required: true + description: The user id + schema: + type: integer + format: integer + minimum: 1 + - name: dateFrom + in: query + required: true + description: The date from, formatted as ISO8601 string + schema: + type: string + format: date-time + - name: dateTo + in: query + required: true + description: The date to, formatted as ISO8601 string + schema: + type: string + format: date-time responses: 200: - description: "ok" \ No newline at end of file + description: "ok"