Skip to content

Commit

Permalink
[Timeline] clean event data with user context
Browse files Browse the repository at this point in the history
  • Loading branch information
ottaviano committed May 1, 2024
1 parent 558afc0 commit 42729b4
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 27 deletions.
6 changes: 6 additions & 0 deletions config/services.yaml
Expand Up @@ -121,6 +121,8 @@ services:
tags: ['app.acquisition_statistics.calculator']
App\Adherent\Tag\TagGenerator\TagGeneratorInterface:
tags: ['app.adherent.tag.generator']
App\JeMengage\Timeline\FeedProcessor\FeedProcessorInterface:
tags: ['app.timeline.feed_processor']

# Imports
App\:
Expand Down Expand Up @@ -189,6 +191,10 @@ services:
arguments:
$senders: !tagged_iterator 'app.adherent_message.sender'

App\JeMengage\Timeline\DataProvider:
arguments:
$processors: !tagged_iterator 'app.timeline.feed_processor'

App\Adherent\Tag\TagAggregator:
arguments:
$generators: !tagged_iterator {tag: 'app.adherent.tag.generator'}
Expand Down
Expand Up @@ -28,7 +28,8 @@
"time_zone",
"title",
"type",
"url"
"url",
"visibility"
],
"unretrievableAttributes": [],
"optionalWords": null,
Expand Down
12 changes: 3 additions & 9 deletions src/Controller/Api/JeMengage/GetTimelineFeedsController.php
Expand Up @@ -2,10 +2,9 @@

namespace App\Controller\Api\JeMengage;

use App\Algolia\SearchService;
use App\Entity\Adherent;
use App\Entity\Algolia\AlgoliaJeMengageTimelineFeed;
use App\Intl\FranceCitiesBundle;
use App\JeMengage\Timeline\DataProvider;
use App\JeMengage\Timeline\TimelineFeedTypeEnum;
use App\OAuth\Model\DeviceApiUser;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
Expand All @@ -19,7 +18,7 @@
#[IsGranted('ROLE_OAUTH_SCOPE_JEMARCHE_APP')]
class GetTimelineFeedsController extends AbstractController
{
public function __invoke(Request $request, SearchService $searchService): JsonResponse
public function __invoke(Request $request, DataProvider $dataProvider): JsonResponse
{
/** @var Adherent|DeviceApiUser $user */
$user = $this->getUser();
Expand Down Expand Up @@ -59,12 +58,7 @@ public function __invoke(Request $request, SearchService $searchService): JsonRe
$filters[] = 'adherent_ids:'.$user->getId();
}

$timelineFeeds = $searchService->rawSearch(AlgoliaJeMengageTimelineFeed::class, '', [
'page' => $page,
'attributesToHighlight' => [],
'filters' => implode(' OR ', $filters),
'tagFilters' => $tagFilters,
]);
$timelineFeeds = $dataProvider->findItems($user, $page, $filters, $tagFilters);

return $this->json($timelineFeeds, Response::HTTP_OK);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Event/BaseEvent.php
Expand Up @@ -827,7 +827,7 @@ public function setElectoral(bool $electoral): void

public function isIndexable(): bool
{
return $this->isPublished() && $this->isGeocoded() && AddressInterface::FRANCE === $this->getCountry();
return $this->isPublished() && !$this->isCancelled();
}

public function getIndexOptions(): array
Expand Down
17 changes: 17 additions & 0 deletions src/Event/EventCleaner.php
@@ -0,0 +1,17 @@
<?php

namespace App\Event;

class EventCleaner
{
public function cleanEventData(array $eventData): array
{
foreach ($eventData as $key => $value) {
if (!\in_array($key, ['name', 'title', 'objectID', 'uuid', 'slug', 'time_zone', 'begin_at', 'finish_at', 'status', 'visibility', 'image', 'image_url', 'link', 'category'])) {
$eventData[$key] = null;
}
}

return $eventData;
}
}
5 changes: 5 additions & 0 deletions src/Event/EventVisibilityEnum.php
Expand Up @@ -8,4 +8,9 @@ enum EventVisibilityEnum: string
case PRIVATE = 'private';
case ADHERENT = 'adherent';
case ADHERENT_DUES = 'adherent_dues';

public static function isForAdherent(string $visibility): bool
{
return \in_array($visibility, [self::ADHERENT->value, self::ADHERENT_DUES->value]);
}
}
51 changes: 51 additions & 0 deletions src/JeMengage/Timeline/DataProvider.php
@@ -0,0 +1,51 @@
<?php

namespace App\JeMengage\Timeline;

use App\Algolia\SearchService;
use App\Entity\Adherent;
use App\Entity\Algolia\AlgoliaJeMengageTimelineFeed;
use App\JeMengage\Timeline\FeedProcessor\FeedProcessorInterface;

class DataProvider
{
public function __construct(
private readonly SearchService $search,
private readonly iterable $processors,
) {
}

public function findItems(Adherent $user, int $page, array $filters, array $tagFilters): array
{
$timelineFeeds = $this->search->rawSearch(AlgoliaJeMengageTimelineFeed::class, '', [
'page' => $page,
'attributesToHighlight' => [],
'filters' => implode(' OR ', $filters),
'tagFilters' => $tagFilters,
]);

$timelineFeeds['hits'] = $this->processItems($user, $timelineFeeds['hits']);

return $timelineFeeds;
}

private function processItems(Adherent $user, array $items): array
{
$context = [];
foreach ($items as &$item) {
$item = $this->getProcessor($item)->process($item, $context);
}

return $items;
}

private function getProcessor(array $item): FeedProcessorInterface
{
/** @var FeedProcessorInterface $processor */
foreach ($this->processors as $processor) {

Check failure on line 45 in src/JeMengage/Timeline/DataProvider.php

View workflow job for this annotation

GitHub Actions / Lint

Method App\JeMengage\Timeline\DataProvider::getProcessor() should return App\JeMengage\Timeline\FeedProcessor\FeedProcessorInterface but return statement is missing.
if ($processor->supports($item)) {
return $processor;
}
}
}
}
11 changes: 11 additions & 0 deletions src/JeMengage/Timeline/FeedProcessor/AbstractFeedProcessor.php
@@ -0,0 +1,11 @@
<?php

namespace App\JeMengage\Timeline\FeedProcessor;

abstract class AbstractFeedProcessor implements FeedProcessorInterface
{
public static function getDefaultPriority(): int
{
return 0;
}
}
56 changes: 56 additions & 0 deletions src/JeMengage/Timeline/FeedProcessor/EventProcessor.php
@@ -0,0 +1,56 @@
<?php

namespace App\JeMengage\Timeline\FeedProcessor;

use App\Adherent\Tag\TagEnum;
use App\Entity\Adherent;
use App\Event\EventCleaner;
use App\Event\EventVisibilityEnum;
use App\JeMengage\Timeline\TimelineFeedTypeEnum;
use Symfony\Component\Security\Core\Security;

class EventProcessor extends AbstractFeedProcessor
{
private const CONTEXT_CLEANER_ENABLED_KEY = 'event:cleaner_enabled';

public function __construct(
private readonly EventCleaner $eventCleaner,
private readonly Security $security,
) {
}

public function process(array $item, array &$context): array
{
return $this->cleanEventDataIfNeed($item, $context);
}

public function supports(array $item): bool
{
return ($item['type'] ?? null) === TimelineFeedTypeEnum::EVENT;
}

private function cleanEventDataIfNeed(array $item, array &$context): array
{
$needClean = $context[self::CONTEXT_CLEANER_ENABLED_KEY] ?? null;

if (null === $needClean) {
$user = $this->security->getUser();
$needClean = $context[self::CONTEXT_CLEANER_ENABLED_KEY] =
EventVisibilityEnum::isForAdherent($visibility = $item['visibility'] ?? EventVisibilityEnum::ADHERENT_DUES->value)
&& (
!$user instanceof Adherent
|| (EventVisibilityEnum::ADHERENT->value === $visibility && !$user->hasTag(TagEnum::ADHERENT))
|| (EventVisibilityEnum::ADHERENT_DUES->value === $visibility && !$user->hasTag(TagEnum::getAdherentYearTag()))
);
}

if ($needClean) {
$item = $this->eventCleaner->cleanEventData($item);
$item['object_state'] = 'partial';
} else {
$item['object_state'] = 'full';
}

return $item;
}
}
10 changes: 10 additions & 0 deletions src/JeMengage/Timeline/FeedProcessor/FeedProcessorInterface.php
@@ -0,0 +1,10 @@
<?php

namespace App\JeMengage\Timeline\FeedProcessor;

interface FeedProcessorInterface
{
public function process(array $item, array &$context): array;

public function supports(array $item): bool;
}
21 changes: 21 additions & 0 deletions src/JeMengage/Timeline/FeedProcessor/NullProcessor.php
@@ -0,0 +1,21 @@
<?php

namespace App\JeMengage\Timeline\FeedProcessor;

class NullProcessor extends AbstractFeedProcessor
{
public static function getDefaultPriority(): int
{
return -100;
}

public function process(array $item, array &$context): array
{
return $item;
}

public function supports(array $item): bool
{
return true;
}
}
15 changes: 3 additions & 12 deletions src/Normalizer/EventNormalizer.php
Expand Up @@ -6,6 +6,7 @@
use App\Api\Serializer\EventContextBuilder;
use App\Entity\Adherent;
use App\Entity\Event\BaseEvent;
use App\Event\EventCleaner;
use App\Event\EventVisibilityEnum;
use App\Repository\EventRegistrationRepository;
use App\Security\Voter\Event\CanManageEventVoter;
Expand All @@ -25,6 +26,7 @@ public function __construct(
private readonly Security $security,
private readonly EventRegistrationRepository $eventRegistrationRepository,
private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly EventCleaner $eventCleaner,
) {
}

Expand Down Expand Up @@ -59,17 +61,6 @@ public function supportsNormalization($data, $format = null, array $context = []
&& $data instanceof BaseEvent;
}

private function cleanPrivateEvent(array $event): array
{
foreach ($event as $key => $value) {
if (!\in_array($key, ['name', 'uuid', 'slug', 'time_zone', 'begin_at', 'finish_at', 'status', 'visibility', 'image_url', 'link'])) {
$event[$key] = null;
}
}

return $event;
}

private function cleanEventDataIfNeed(BaseEvent $event, ?Adherent $adherent, array $eventData, string $apiContext): array
{
if (EventContextBuilder::CONTEXT_PRIVATE === $apiContext) {
Expand All @@ -89,7 +80,7 @@ private function cleanEventDataIfNeed(BaseEvent $event, ?Adherent $adherent, arr
);

if ($needClean) {
$eventData = $this->cleanPrivateEvent($eventData);
$eventData = $this->eventCleaner->cleanEventData($eventData);
$eventData['object_state'] = 'partial';
} else {
$eventData['object_state'] = 'full';
Expand Down
Expand Up @@ -31,6 +31,7 @@ final public function normalize($object, $format = null, array $context = [])
'adherent_ids' => $this->getAdherentIds($object),
'deeplink' => $this->getDeepLink($object),
'mode' => $this->getMode($object),
'visibility' => $this->getVisibility($object),
'cta_label' => $this->getCtaLabel($object),
'cta_link' => $this->getCtaLink($object),
'_tags' => [$this->getType()],
Expand Down Expand Up @@ -127,6 +128,11 @@ protected function getCtaLink(object $object): ?string
return null;
}

protected function getVisibility(object $object): ?string
{
return null;
}

protected function getMediaType(object $object): ?string
{
return null;
Expand Down
11 changes: 7 additions & 4 deletions src/Normalizer/Indexer/EventNormalizer.php
Expand Up @@ -8,11 +8,8 @@

class EventNormalizer extends AbstractJeMengageTimelineFeedNormalizer
{
private UrlGeneratorInterface $urlGenerator;

public function __construct(UrlGeneratorInterface $urlGenerator)
public function __construct(private readonly UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}

protected function getClassName(): string
Expand Down Expand Up @@ -80,6 +77,12 @@ protected function getMode(object $object): ?string
return $object->getMode();
}

/** @param BaseEvent $object */
protected function getVisibility(object $object): ?string
{
return $object->visibility->value;
}

/** @param BaseEvent $object */
protected function getFinishAt(object $object): ?\DateTime
{
Expand Down

0 comments on commit 42729b4

Please sign in to comment.