From c7a06c25838729bee830ac9a7fa9ae81224211c9 Mon Sep 17 00:00:00 2001 From: "Buster \"Silver Eagle\" Neece" Date: Thu, 23 Sep 2021 20:13:11 -0500 Subject: [PATCH] #4594 -- Fix how the Schedule embed displays by allowing calendar return data on public API endpoint. --- config/routes/api.php | 2 +- .../AbstractScheduledEntityController.php | 53 +------- .../Api/Stations/ScheduleAction.php | 121 ++++++++++++++++++ .../Api/Stations/ScheduleController.php | 85 ------------ .../Api/Traits/HasScheduleDisplay.php | 79 ++++++++++++ src/Entity/Api/StationSchedule.php | 14 +- .../ApiGenerator/ScheduleApiGenerator.php | 64 +++++++++ .../Repository/StationScheduleRepository.php | 67 +++++----- 8 files changed, 307 insertions(+), 178 deletions(-) create mode 100644 src/Controller/Api/Stations/ScheduleAction.php delete mode 100644 src/Controller/Api/Stations/ScheduleController.php create mode 100644 src/Controller/Api/Traits/HasScheduleDisplay.php create mode 100644 src/Entity/ApiGenerator/ScheduleApiGenerator.php diff --git a/config/routes/api.php b/config/routes/api.php index ef5fa93d9..061ed7079 100644 --- a/config/routes/api.php +++ b/config/routes/api.php @@ -227,7 +227,7 @@ return static function (RouteCollectorProxy $app) { ->setName('api:stations:profile') ->add(new Middleware\Permissions(Acl::STATION_VIEW, true)); - $group->get('/schedule', Controller\Api\Stations\ScheduleController::class) + $group->get('/schedule', Controller\Api\Stations\ScheduleAction::class) ->setName('api:stations:schedule'); $group->get('/history', Controller\Api\Stations\HistoryController::class) diff --git a/src/Controller/Api/Stations/AbstractScheduledEntityController.php b/src/Controller/Api/Stations/AbstractScheduledEntityController.php index 5d4ac25cb..e8f0bb909 100644 --- a/src/Controller/Api/Stations/AbstractScheduledEntityController.php +++ b/src/Controller/Api/Stations/AbstractScheduledEntityController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller\Api\Stations; +use App\Controller\Api\Traits\HasScheduleDisplay; use App\Entity; use App\Exception\ValidationException; use App\Http\Response; @@ -22,6 +23,8 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; */ abstract class AbstractScheduledEntityController extends AbstractStationApiCrudController { + use HasScheduleDisplay; + public function __construct( protected Entity\Repository\StationScheduleRepository $scheduleRepo, protected Scheduler $scheduler, @@ -38,54 +41,12 @@ abstract class AbstractScheduledEntityController extends AbstractStationApiCrudC array $scheduleItems, callable $rowRender ): ResponseInterface { - $tz = $request->getStation()->getTimezoneObject(); + [$startDate, $endDate] = $this->getDateRange($request); - $params = $request->getQueryParams(); - - $startDateStr = substr($params['start'], 0, 10); - $startDate = CarbonImmutable::createFromFormat('Y-m-d', $startDateStr, $tz); - - if (false === $startDate) { - throw new \InvalidArgumentException(sprintf('Could not parse start date: "%s"', $startDateStr)); - } - - $startDate = $startDate->subDay(); - - $endDateStr = substr($params['end'], 0, 10); - $endDate = CarbonImmutable::createFromFormat('Y-m-d', $endDateStr, $tz); - - if (false === $endDate) { - throw new \InvalidArgumentException(sprintf('Could not parse end date: "%s"', $endDateStr)); - } - - $events = []; - - foreach ($scheduleItems as $scheduleItem) { - /** @var Entity\StationSchedule $scheduleItem */ - $i = $startDate; - - while ($i <= $endDate) { - $dayOfWeek = $i->dayOfWeekIso; - - if ( - $this->scheduler->shouldSchedulePlayOnCurrentDate($scheduleItem, $i) - && $this->scheduler->isScheduleScheduledToPlayToday($scheduleItem, $dayOfWeek) - ) { - $rowStart = Entity\StationSchedule::getDateTime($scheduleItem->getStartTime(), $i); - $rowEnd = Entity\StationSchedule::getDateTime($scheduleItem->getEndTime(), $i); - - // Handle overnight schedule items - if ($rowEnd < $rowStart) { - $rowEnd = $rowEnd->addDay(); - } - - $events[] = $rowRender($scheduleItem, $rowStart, $rowEnd); - } - - $i = $i->addDay(); - } - } + $station = $request->getStation(); + $now = CarbonImmutable::now($station->getTimezoneObject()); + $events = $this->getEvents($startDate, $endDate, $now, $this->scheduler, $scheduleItems, $rowRender); return $response->withJson($events); } diff --git a/src/Controller/Api/Stations/ScheduleAction.php b/src/Controller/Api/Stations/ScheduleAction.php new file mode 100644 index 000000000..66e8a8488 --- /dev/null +++ b/src/Controller/Api/Stations/ScheduleAction.php @@ -0,0 +1,121 @@ +getStation(); + $tz = $station->getTimezoneObject(); + + $queryParams = $request->getQueryParams(); + + if (isset($queryParams['start'])) { + [$startDate, $endDate] = $this->getDateRange($request); + + $cacheKey = 'api_station_' . $station->getId() . '_schedule_' + . $startDate->format('Ymd') . '-' + . $endDate->format('Ymd'); + + $events = $cache->get( + $cacheKey, + function (CacheItem $item) use ( + $station, + $scheduleRepo, + $scheduleApiGenerator, + $scheduler, + $startDate, + $endDate + ) { + $item->expiresAfter(1); + + $nowTz = CarbonImmutable::now($station->getTimezoneObject()); + $events = $scheduleRepo->getAllScheduledItemsForStation($station); + + return $this->getEvents( + $startDate, + $endDate, + $nowTz, + $scheduler, + $events, + [$scheduleApiGenerator, '__invoke'] + ); + } + ); + } else { + if (!empty($queryParams['now'])) { + $now = CarbonImmutable::parse($queryParams['now'], $tz); + $cacheKey = 'api_station_' . $station->getId() . '_schedule_' . $now->format('Ymd_gia'); + } else { + $now = CarbonImmutable::now($tz); + $cacheKey = 'api_station_' . $station->getId() . '_schedule_upcoming'; + } + + $events = $cache->get( + $cacheKey, + function (CacheItem $item) use ($scheduleRepo, $station, $now) { + $item->expiresAfter(60); + return $scheduleRepo->getUpcomingSchedule($station, $now); + } + ); + + $rows = (int)$request->getQueryParam('rows', 5); + $events = array_slice($events, 0, $rows); + } + + return $response->withJson($events); + } +} diff --git a/src/Controller/Api/Stations/ScheduleController.php b/src/Controller/Api/Stations/ScheduleController.php deleted file mode 100644 index a59357106..000000000 --- a/src/Controller/Api/Stations/ScheduleController.php +++ /dev/null @@ -1,85 +0,0 @@ -getStation(); - $tz = $station->getTimezoneObject(); - - $now = $request->getQueryParam('now'); - if (!empty($now)) { - $now = CarbonImmutable::parse($now, $tz); - $cacheKey = 'api_station_' . $station->getId() . '_schedule_' . $now->format('Ymd_gia'); - } else { - $now = CarbonImmutable::now($tz); - $cacheKey = 'api_station_' . $station->getId() . '_schedule_upcoming'; - } - - $events = $cache->get( - $cacheKey, - function (CacheItem $item) use ($scheduleRepo, $station, $now) { - $item->expiresAfter(60); - return $scheduleRepo->getUpcomingSchedule($station, $now); - } - ); - - $rows = (int)$request->getQueryParam('rows', 5); - $events = array_slice($events, 0, $rows); - - return $response->withJson($events); - } -} diff --git a/src/Controller/Api/Traits/HasScheduleDisplay.php b/src/Controller/Api/Traits/HasScheduleDisplay.php new file mode 100644 index 000000000..1a2bc07a4 --- /dev/null +++ b/src/Controller/Api/Traits/HasScheduleDisplay.php @@ -0,0 +1,79 @@ +getStation()->getTimezoneObject(); + $params = $request->getQueryParams(); + + $startDateStr = substr($params['start'], 0, 10); + $startDate = CarbonImmutable::createFromFormat('Y-m-d', $startDateStr, $tz); + + if (false === $startDate) { + throw new \InvalidArgumentException(sprintf('Could not parse start date: "%s"', $startDateStr)); + } + + $startDate = $startDate->startOf('day'); + + $endDateStr = substr($params['end'], 0, 10); + $endDate = CarbonImmutable::createFromFormat('Y-m-d', $endDateStr, $tz); + + if (false === $endDate) { + throw new \InvalidArgumentException(sprintf('Could not parse end date: "%s"', $endDateStr)); + } + + $endDate = $endDate->endOf('day'); + + return [$startDate, $endDate]; + } + + protected function getEvents( + CarbonInterface $startDate, + CarbonInterface $endDate, + CarbonInterface $now, + Scheduler $scheduler, + array $scheduleItems, + callable $rowRender + ): array { + $events = []; + + foreach ($scheduleItems as $scheduleItem) { + /** @var Entity\StationSchedule $scheduleItem */ + $i = $startDate; + + while ($i <= $endDate) { + $dayOfWeek = $i->dayOfWeekIso; + + if ( + $scheduler->shouldSchedulePlayOnCurrentDate($scheduleItem, $i) + && $scheduler->isScheduleScheduledToPlayToday($scheduleItem, $dayOfWeek) + ) { + $rowStart = Entity\StationSchedule::getDateTime($scheduleItem->getStartTime(), $i); + $rowEnd = Entity\StationSchedule::getDateTime($scheduleItem->getEndTime(), $i); + + // Handle overnight schedule items + if ($rowEnd < $rowStart) { + $rowEnd = $rowEnd->addDay(); + } + + $events[] = $rowRender($scheduleItem, $rowStart, $rowEnd, $now); + } + + $i = $i->addDay(); + } + } + + return $events; + } +} diff --git a/src/Entity/Api/StationSchedule.php b/src/Entity/Api/StationSchedule.php index a00c74cfc..36a3b1192 100644 --- a/src/Entity/Api/StationSchedule.php +++ b/src/Entity/Api/StationSchedule.php @@ -17,56 +17,54 @@ class StationSchedule /** * Unique identifier for this schedule entry. * @OA\Property(example=1) - * @var int */ public int $id; /** * The type of this schedule entry. * @OA\Property(enum={App\Entity\Api\StationSchedule::TYPE_PLAYLIST, App\Entity\Api\StationSchedule::TYPE_STREAMER}, example=App\Entity\Api\StationSchedule::TYPE_PLAYLIST) - * @var string */ public string $type; /** * Either the playlist or streamer's display name. * @OA\Property(example="Example Schedule Entry") - * @var string */ public string $name; + /** + * The full name of the type and name combined. + * @OA\Property(example="Playlist: Example Schedule Entry") + */ + public string $title; + /** * The start time of the schedule entry, in UNIX format. * @OA\Property(example=1609480800) - * @var int */ public int $start_timestamp; /** * The start time of the schedule entry, in ISO 8601 format. * @OA\Property(example="020-02-19T03:00:00-06:00") - * @var string */ public string $start; /** * The end time of the schedule entry, in UNIX format. * @OA\Property(example=1609480800) - * @var int */ public int $end_timestamp; /** * The start time of the schedule entry, in ISO 8601 format. * @OA\Property(example="020-02-19T05:00:00-06:00") - * @var string */ public string $end; /** * Whether the event is currently ongoing. * @OA\Property(example=true) - * @var bool */ public bool $is_now; } diff --git a/src/Entity/ApiGenerator/ScheduleApiGenerator.php b/src/Entity/ApiGenerator/ScheduleApiGenerator.php new file mode 100644 index 000000000..40fbcc3c2 --- /dev/null +++ b/src/Entity/ApiGenerator/ScheduleApiGenerator.php @@ -0,0 +1,64 @@ +getPlaylist(); + $streamer = $scheduleItem->getStreamer(); + + if (null === $now) { + if (null !== $playlist) { + $station = $playlist->getStation(); + } elseif (null !== $streamer) { + $station = $streamer->getStation(); + } else { + $station = null; + } + + $now = CarbonImmutable::now($station?->getTimezoneObject()); + } + + if (null === $start || null === $end) { + $start = Entity\StationSchedule::getDateTime($scheduleItem->getStartTime(), $now); + $end = Entity\StationSchedule::getDateTime($scheduleItem->getEndTime(), $now); + + // Handle overnight schedule items + if ($end < $start) { + $end = $end->addDay(); + } + } + + $row = new Entity\Api\StationSchedule(); + $row->id = $scheduleItem->getIdRequired(); + $row->start_timestamp = $start->getTimestamp(); + $row->start = $start->toIso8601String(); + $row->end_timestamp = $end->getTimestamp(); + $row->end = $end->toIso8601String(); + $row->is_now = ($start <= $now && $end >= $now); + + if ($playlist instanceof Entity\StationPlaylist) { + $row->type = Entity\Api\StationSchedule::TYPE_PLAYLIST; + $row->name = $playlist->getName(); + $row->title = __('Playlist: %s', $row->name); + } elseif ($streamer instanceof Entity\StationStreamer) { + $row->type = Entity\Api\StationSchedule::TYPE_STREAMER; + $row->name = $streamer->getDisplayName(); + $row->title = __('Streamer: %s', $row->name); + } + + return $row; + } +} diff --git a/src/Entity/Repository/StationScheduleRepository.php b/src/Entity/Repository/StationScheduleRepository.php index f1ebd32ed..8253eda9f 100644 --- a/src/Entity/Repository/StationScheduleRepository.php +++ b/src/Entity/Repository/StationScheduleRepository.php @@ -19,18 +19,15 @@ use Symfony\Component\Serializer\Serializer; */ class StationScheduleRepository extends Repository { - protected Scheduler $scheduler; - public function __construct( ReloadableEntityManagerInterface $em, Serializer $serializer, Environment $environment, LoggerInterface $logger, - Scheduler $scheduler + protected Scheduler $scheduler, + protected Entity\ApiGenerator\ScheduleApiGenerator $scheduleApiGenerator ) { parent::__construct($em, $serializer, $environment, $logger); - - $this->scheduler = $scheduler; } /** @@ -85,6 +82,26 @@ class StationScheduleRepository extends Repository return $this->repository->findBy(['streamer' => $relation]); } + /** + * @param Entity\Station $station + * + * @return Entity\StationSchedule[] + */ + public function getAllScheduledItemsForStation(Entity\Station $station): array + { + return $this->em->createQuery( + <<<'DQL' + SELECT ssc, sp, sst + FROM App\Entity\StationSchedule ssc + LEFT JOIN ssc.playlist sp + LEFT JOIN ssc.streamer sst + WHERE (sp.station = :station AND sp.is_jingle = 0 AND sp.is_enabled = 1) + OR (sst.station = :station AND sst.is_active = 1) + DQL + )->setParameter('station', $station) + ->execute(); + } + /** * @param Entity\Station $station * @param CarbonInterface|null $now @@ -102,19 +119,7 @@ class StationScheduleRepository extends Repository $events = []; - $scheduleItems = $this->em->createQuery( - <<<'DQL' - SELECT ssc, sp, sst - FROM App\Entity\StationSchedule ssc - LEFT JOIN ssc.playlist sp - LEFT JOIN ssc.streamer sst - WHERE (sp.station = :station AND sp.is_jingle = 0 AND sp.is_enabled = 1) - OR (sst.station = :station AND sst.is_active = 1) - DQL - )->setParameter('station', $station) - ->execute(); - - foreach ($scheduleItems as $scheduleItem) { + foreach ($this->getAllScheduledItemsForStation($station) as $scheduleItem) { /** @var Entity\StationSchedule $scheduleItem */ $i = $startDate; @@ -139,26 +144,12 @@ class StationScheduleRepository extends Repository continue; } - $row = new Entity\Api\StationSchedule(); - $row->id = $scheduleItem->getIdRequired(); - $row->start_timestamp = $start->getTimestamp(); - $row->start = $start->toIso8601String(); - $row->end_timestamp = $end->getTimestamp(); - $row->end = $end->toIso8601String(); - $row->is_now = $start->lessThanOrEqualTo($now); - - $playlist = $scheduleItem->getPlaylist(); - $streamer = $scheduleItem->getStreamer(); - - if ($playlist instanceof Entity\StationPlaylist) { - $row->type = Entity\Api\StationSchedule::TYPE_PLAYLIST; - $row->name = $playlist->getName(); - } elseif ($streamer instanceof Entity\StationStreamer) { - $row->type = Entity\Api\StationSchedule::TYPE_STREAMER; - $row->name = $streamer->getDisplayName(); - } - - $events[] = $row; + $events[] = ($this->scheduleApiGenerator)( + $scheduleItem, + $start, + $end, + $now + ); } $i = $i->addDay();