src/Service/Client/ActivityFeedService.php line 48

Open in your IDE?
  1. <?php
  2. namespace App\Service\Client;
  3. use Frivol\Common\Dict\ActivityFeedType;
  4. use Frivol\Common\JSON;
  5. use Knp\Component\Pager\Pagination\PaginationInterface;
  6. use Symfony\Component\HttpFoundation\Response;
  7. use Symfony\Contracts\Cache\ItemInterface;
  8. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  9. use Twig\Error\Error;
  10. use Websocket\InfostreamHandler;
  11. use Websocket\TemplateRendererTwig;
  12. class ActivityFeedService extends AbstractClient
  13. {
  14.     public function getTimelineFeedForMember(int $memberint $pageint $perPage): PaginationInterface
  15.     {
  16.         $key = (new \ReflectionClass($this))->getShortName() . '_' __FUNCTION__ '_' $member '_' $page '_' $perPage;
  17.         return $this->getCacheService()->get($key, function (ItemInterface $item) use ($member$page$perPage) {
  18.             try {
  19.                 $response $this->requestJSON('GET''api/public/feeds/activity/timeline/' $member '/' $page, [
  20.                     'query' => [
  21.                         'limit' => $perPage,
  22.                     ]
  23.                 ]);
  24.             } catch (TransportExceptionInterface $exception) {
  25.                 return $this->getDefaultPagination([], $page$perPage);
  26.             }
  27.             if (is_array($response)) {
  28.                 $item->expiresAfter(600);
  29.                 $item->tag([
  30.                     'timelinefeed',
  31.                     'timelinefeed-' $member
  32.                 ]);
  33.             }
  34.             return $this->getDefaultPagination($response$page$perPage);
  35.         });
  36.     }
  37.     /**
  38.      * @param int $page
  39.      * @param int $limit
  40.      */
  41.     public function getGlobalTimelineFeed(int $pageint $limit): PaginationInterface {
  42.         $key = (new \ReflectionClass($this))->getShortName() . '_' __FUNCTION__ '_' $page '_' $limit;
  43.         return $this->getCacheService()->get($key, function (ItemInterface $item) use ($page$limit) {
  44.             try {
  45.                 $response $this->requestJSON('GET''api/public/feeds/activity/infostream/global/' $page, [
  46.                     'query' => [
  47.                         'limit' => $limit,
  48.                     ]
  49.                 ]);
  50.             } catch (TransportExceptionInterface $exception) {
  51.                 return $this->getDefaultPagination([], $page$limit);
  52.             }
  53.             if (is_array($response)) {
  54.                 $item->expiresAfter(30);
  55.                 $item->tag([
  56.                     'timelinefeed-global'
  57.                 ]);
  58.             }
  59.             return $this->getDefaultPagination($response$page$limit);
  60.         });
  61.     }
  62.     /**
  63.      * @param int $member
  64.      * @return PaginationInterface
  65.      * @throws \Psr\Cache\InvalidArgumentException
  66.      * @throws \ReflectionException
  67.      */
  68.     public function getProfileVisitsForMember(int $member): PaginationInterface
  69.     {
  70.         $key = (new \ReflectionClass($this))->getShortName() . '_' __FUNCTION__ '_' $member;
  71.         return $this->getCacheService()->get($key, function (ItemInterface $item) use ($member) {
  72.             $item->expiresAfter(60 5);
  73.             $response $this->requestJSON('GET''api/user/profilevisits/' $member);
  74.             // old code does not keep more than 30 items in the redis key (applied in api aswell).
  75.             return $this->getDefaultPagination($response130);
  76.         });
  77.     }
  78.     /**
  79.      * @param int $hostMemberId
  80.      * @param int $visitorMemberId
  81.      * @return bool
  82.      */
  83.     public function createProfileVisit(int $hostMemberIdint $visitorMemberId): bool
  84.     {
  85.         try {
  86.             $response $this->issuePost('api/user/profilevisits/visit/' $hostMemberId '/' $visitorMemberId, []);
  87.         } catch (TransportExceptionInterface $e) {
  88.             return false;
  89.         }
  90.         return $response->getStatusCode() === Response::HTTP_CREATED;
  91.     }
  92.     public function createProfileVisitFromPath(string $profileUriint $visitorMemberId): bool
  93.     {
  94.         if (preg_match('#^/profil/([a-z0-9\-]+)$#iu'$profileUri$matches) !== 1) {
  95.             return false;
  96.         }
  97.         try {
  98.             $response $this->issuePost('api/user/profilevisits/visitusername/' $matches[1] . '/' $visitorMemberId, []);
  99.         } catch (TransportExceptionInterface $e) {
  100.             return false;
  101.         }
  102.         return $response->getStatusCode() === Response::HTTP_CREATED;
  103.     }
  104.     public function updatePublicInfostream(TemplateRendererTwig $renderer\Redis $redis): array
  105.     {
  106.         $publicActivities $this->requestJSON('GET''api/public/feeds/activity/infostream/public');
  107.         $storedStream = [];
  108.         foreach ($publicActivities['data'] as $activity) {
  109.             try {
  110.                 $html $renderer->renderInfostreamItem($activity);
  111.             } catch (Error $e) {
  112.                 $this->logger->error($e->getMessage() . PHP_EOL $e->getTraceAsString());
  113.                 continue;
  114.             }
  115.             if (empty($html)) {
  116.                 continue;
  117.             }
  118.             $storedStream[$activity['id']] = $html;
  119.         }
  120.         krsort($storedStream);
  121.         $redis->set(InfostreamHandler::PUBLIC_INFOSTREAM_CACHE_KEYgzcompress(JSON::encode($storedStream)));
  122.         return $storedStream;
  123.     }
  124.     /**
  125.      * Returns list with new activities for ['all'] or [$memberId] to be published by websocket server.
  126.      * Need to use activityId as key, to be capable of loading only new events for each member.
  127.      * @param string $memberId2ActivityId
  128.      * @param TemplateRendererTwig $renderer
  129.      * @param \Redis $redis
  130.      * @return array[]
  131.      * @throws TransportExceptionInterface
  132.      * @throws \JsonException
  133.      * @throws \RedisException
  134.      * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
  135.      * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
  136.      * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
  137.      */
  138.     public function updateMemberInfostream(string $memberId2ActivityIdTemplateRendererTwig $renderer\Redis $redis): array
  139.     {
  140.         $memberActivities $this->requestJSON('POST''api/public/feeds/activity/infostream/members', [
  141.             'body' => [
  142.                 'member_activity_ids' => $memberId2ActivityId,
  143.             ]
  144.         ]);
  145.         $renderer->setLogger($this->logger);
  146.         $activityUpdates $visitorUpdates $infostreamUpdates = [];
  147.         foreach ($memberActivities['data'] as $memberId => $activities) {
  148.             if (empty($activities)) {
  149.                 continue;
  150.             }
  151.             [$infostreamItems$activityItems$visitItems] = self::getMemberLists($memberId$redis);
  152.             if (!is_array($infostreamItems)) {
  153.                 $infostreamItems = [];
  154.             }
  155.             if (!is_array($activityItems)) {
  156.                 $activityItems = [];
  157.             }
  158.             if (!is_array($visitItems)) {
  159.                 $visitItems = [];
  160.             }
  161.             foreach ($activities as $activity) {
  162.                 //#564 member infostream is now clone of sidebar activity items handled in app.js & HeaderApp!
  163.                 //$html = $renderer->renderInfostreamItem($activity, true);
  164.                 //if ('' !== trim($html)) {
  165.                 //    $infostreamItems[$activity['id']] = $html;
  166.                 //    $infostreamUpdates[$memberId][$activity['id']] = $html;
  167.                 //}
  168.                 $html $renderer->renderSidebarActivityItem($activity);
  169.                 if ('' !== trim($html)) {
  170.                     $activityItems[$activity['id']] = $html;
  171.                     $activityUpdates[$memberId][$activity['id']] = $html;
  172.                 }
  173.                 if ($activity['type'] === ActivityFeedType::PROFILE_VISIT && ('' !== $html $renderer->renderSidebarProfileVisitItem($activity))) {
  174.                     $visitorUpdates[$memberId][$activity['id']] = $visitItems[$activity['id']] = $html;
  175.                 }
  176.             }
  177.             krsort($infostreamItems);
  178.             krsort($activityItems);
  179.             krsort($visitItems);
  180.             if (!$this->setMemberLists($memberIdarray_slice($infostreamItems020true), array_slice($activityItems0100true), array_slice($visitItems0100true), $redis)) {
  181.                 $this->logger->critical('Failed to store infostream/activitySidebar/visitsSidebar items in redis.');
  182.             }
  183.         }
  184.         krsort($infostreamUpdates);
  185.         krsort($activityUpdates);
  186.         krsort($visitorUpdates);
  187.         return [
  188.             'infostreamUpdates' => $infostreamUpdates,
  189.             'activityUpdates' => $activityUpdates,
  190.             'visitorUpdates' => $visitorUpdates,
  191.         ];
  192.     }
  193.     /**
  194.      * @param int $memberId
  195.      * @param \Redis $redis
  196.      * @return array[]
  197.      * @throws \JsonException
  198.      * @throws \RedisException
  199.      */
  200.     public static function getMemberLists(int $memberId\Redis $redis): array
  201.     {
  202.         [$infostreamJson$activitiesJson$visitsJson] = $redis->mGet([
  203.             InfostreamHandler::MEMBER_INFOSTREAM_CACHE_KEY $memberId,
  204.             InfostreamHandler::ACTIVITIES_SIDEBAR_CACHE_KEY $memberId,
  205.             InfostreamHandler::PROFILE_VISIT_SIDEBAR_CACHE_KEY $memberId,
  206.         ]);
  207.         return [
  208.             => is_string($infostreamJson) ? JSON::decode(gzuncompress($infostreamJson)) : [],
  209.             => is_string($activitiesJson) ? JSON::decode(gzuncompress($activitiesJson)) : [],
  210.             => is_string($visitsJson) ? JSON::decode(gzuncompress($visitsJson)) : [],
  211.         ];
  212.     }
  213.     public function getPublicInfostream(\Redis $redisint $initialEntries 8): array
  214.     {
  215.         $returnStruct = ['initial' => [], 'delayed' => []];
  216.         $feedJson $redis->get(InfostreamHandler::PUBLIC_INFOSTREAM_CACHE_KEY);
  217.         if (empty($feedJson)) {
  218.             return $returnStruct;
  219.         }
  220.         $feedItems JSON::decode(gzuncompress($feedJson));
  221.         ksort($feedItems);
  222.         $processed $sendDelay 0;
  223.         foreach ($feedItems as $itemHtml) {
  224.             $processed++;
  225.             if ($processed <= $initialEntries) {
  226.                 $returnStruct['initial'][] = $itemHtml;
  227.                 continue;
  228.             }
  229.             $sendDelay += random_int(13);//Fake constant stream of activity, so users can keep up with reading.
  230.             $returnStruct['delayed'][$sendDelay] = $itemHtml;
  231.         }
  232.         return $returnStruct;
  233.     }
  234.     private function setMemberLists(int $memberId, array $infostreamItems, array $activityItems, array $visitItems\Redis $redis): bool
  235.     {
  236.         return $redis->mSet([
  237.             InfostreamHandler::MEMBER_INFOSTREAM_CACHE_KEY $memberId => gzcompress(JSON::encode($infostreamItems), 6),
  238.             InfostreamHandler::ACTIVITIES_SIDEBAR_CACHE_KEY $memberId => gzcompress(JSON::encode($activityItems), 6),
  239.             InfostreamHandler::PROFILE_VISIT_SIDEBAR_CACHE_KEY $memberId => gzcompress(JSON::encode($visitItems), 6),
  240.         ]);
  241.     }
  242. }