Skip to content

Commit

Permalink
added joining and leaving an event and emitting an event via mercure,…
Browse files Browse the repository at this point in the history
… as well as subscribing to that event
  • Loading branch information
Felix Ruf committed Jan 16, 2024
1 parent 6ed4ed2 commit 83645f7
Show file tree
Hide file tree
Showing 18 changed files with 226 additions and 46 deletions.
1 change: 1 addition & 0 deletions config/packages/mercure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ mercure:
- participation-updates
- meal-offer-updates
- slot-allocation-updates
- event-participation-updates
- keep-alive-connection
37 changes: 27 additions & 10 deletions src/Mealz/MealBundle/Controller/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace App\Mealz\MealBundle\Controller;

use App\Mealz\MealBundle\Entity\Day;
use App\Mealz\MealBundle\Entity\Event;
use App\Mealz\MealBundle\Event\EventParticipationUpdateEvent;
use App\Mealz\MealBundle\Repository\DayRepositoryInterface;
use App\Mealz\MealBundle\Repository\EventRepositoryInterface;
use App\Mealz\MealBundle\Service\EventParticipationService;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
Expand All @@ -17,21 +18,24 @@
use Symfony\Component\HttpFoundation\Request;

/**
* @Security("is_granted('ROLE_KITCHEN_STAFF')")
* @Security("is_granted('ROLE_USER')")
*/
class EventController extends BaseListController
{
private DayRepositoryInterface $dayRepo;
private EntityManagerInterface $em;
private EventRepositoryInterface $eventRepo;
private EventDispatcherInterface $eventDispatcher;
private EventParticipationService $eventPartSrv;

public function __construct(
DayRepositoryInterface $dayRepoInterface,
EntityManagerInterface $entityManager,
EventRepositoryInterface $eventRepository,
EventDispatcherInterface $eventDispatcher,
EventParticipationService $eventPartSrv
) {
$this->dayRepo = $dayRepoInterface;
$this->em = $entityManager;
$this->eventRepo = $eventRepository;
$this->eventDispatcher = $eventDispatcher;
Expand All @@ -45,6 +49,9 @@ public function getEventList(): JsonResponse
return new JsonResponse($events, 200);
}

/**
* @Security("is_granted('ROLE_KITCHEN_STAFF')")
*/
public function new(Request $request): JsonResponse
{
$parameters = json_decode($request->getContent(), true);
Expand All @@ -59,6 +66,9 @@ public function new(Request $request): JsonResponse
return new JsonResponse(null, 200);
}

/**
* @Security("is_granted('ROLE_KITCHEN_STAFF')")
*/
public function update(Request $request, Event $event): JsonResponse
{
try {
Expand All @@ -83,6 +93,9 @@ public function update(Request $request, Event $event): JsonResponse
}
}

/**
* @Security("is_granted('ROLE_KITCHEN_STAFF')")
*/
public function delete(Event $event): JsonResponse
{
try {
Expand All @@ -99,37 +112,41 @@ public function delete(Event $event): JsonResponse
}
}

public function join(Day $day): JsonResponse
public function join(DateTime $date): JsonResponse
{
$profile = $this->getProfile();
if (null === $profile) {
return new JsonResponse(['messasge' => '801: User is not allowed to join'], 403);
return new JsonResponse(['message' => '801: User is not allowed to join'], 403);
}

$day = $this->dayRepo->getDayByDate($date);

$eventParticipation = $this->eventPartSrv->join($profile, $day);
if (null === $eventParticipation) {
return new JsonResponse(['messasge' => '802: User could not join the event'], 500);
return new JsonResponse(['message' => '802: User could not join the event'], 500);
}

$this->eventDispatcher->dispatch(new EventParticipationUpdateEvent($eventParticipation));

return new JsonResponse(['isParticipating' => true], 200);
return new JsonResponse($this->eventPartSrv->getEventParticipationData($day, $profile), 200);
}

public function leave(Day $day): JsonResponse
public function leave(DateTime $date): JsonResponse
{
$profile = $this->getProfile();
if (null === $profile) {
return new JsonResponse(['messasge' => '801: User is not allowed to leave'], 403);
return new JsonResponse(['message' => '801: User is not allowed to leave'], 403);
}

$day = $this->dayRepo->getDayByDate($date);

$eventParticipation = $this->eventPartSrv->leave($profile, $day);
if (null === $eventParticipation) {
return new JsonResponse(['messasge' => '802: User could not leave the event'], 500);
return new JsonResponse(['message' => '802: User could not leave the event'], 500);
}

$this->eventDispatcher->dispatch(new EventParticipationUpdateEvent($eventParticipation));

return new JsonResponse(['isParticipating' => false], 200);
return new JsonResponse($this->eventPartSrv->getEventParticipationData($day, $profile), 200);
}
}
10 changes: 0 additions & 10 deletions src/Mealz/MealBundle/Entity/EventParticipation.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,4 @@ public function getParticipants(): ArrayCollection

return new ArrayCollection($this->participants->toArray());
}

public function addParticipant(Participant $participant): void
{
$this->participants->add($participant);
}

public function removeParticipant(Participant $participant): bool
{
return $this->participants->removeElement($participant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class EventParticipationUpdateSubscriber implements EventSubscriberInterface
{
private const PUBLISH_TOPIC = 'event-participation-update';
private const PUBLISH_TOPIC = 'event-participation-updates';
private const PUBLISH_MSG_TYPE = 'eventParticipationUpdate';

private PublisherInterface $publisher;
Expand All @@ -28,11 +28,11 @@ public function __construct(
public static function getSubscribedEvents(): array
{
return [
EventParticipationUpdateEvent::class => 'onUpdate',
EventParticipationUpdateEvent::class => 'onEventParticipationUpdate',
];
}

public function onUpdate(EventParticipationUpdateEvent $event): void
public function onEventParticipationUpdate(EventParticipationUpdateEvent $event): void
{
$eventParticipation = $event->getEventParticipation();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,30 @@
use App\Mealz\MealBundle\Entity\Participant;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;

/**
* listener that ensures that there won't be duplicate entries for the same participant in the database.
*/
class ParticipantPersistenceListener
{
public function prePersist(LifecycleEventArgs $args): void
public function prePersist(PrePersistEventArgs $args): void
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$entity = $args->getObject();
$entityManager = $args->getObjectManager();

if ($entity instanceof Participant) {
$this->checkUniqueParticipant($entity, $entityManager);
}
}

public function preUpdate(LifecycleEventArgs $args): void
public function preUpdate(PreUpdateEventArgs $args): void
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$entity = $args->getObject();
$entityManager = $args->getObjectManager();

if ($entity instanceof Participant) {
$this->checkUniqueParticipant($entity, $entityManager);
Expand All @@ -43,6 +46,19 @@ private function participantExists(Participant $participant, EntityManager $enti
{
$queryBuilder = $entityManager->createQueryBuilder();

if (null !== $participant->getMeal()) {
$query = $this->buildQueryMealParticipantExists($participant, $queryBuilder);
} elseif (null !== $participant->getEvent()) {
$query = $this->buildQueryEventParticipantExists($participant, $queryBuilder);
} else {
return false;
}

return $query->execute(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) > 0;
}

private function buildQueryMealParticipantExists(Participant $participant, QueryBuilder $queryBuilder): Query
{
$queryBuilder
->select('COUNT(p.id)')
->from('MealzMealBundle:Participant', 'p')
Expand All @@ -58,8 +74,30 @@ private function participantExists(Participant $participant, EntityManager $enti
$query = $queryBuilder->getQuery();
$query->setParameter('meal', $participant->getMeal()->getId());
$query->setParameter('profile', $participant->getProfile()->getUsername());
$query->useResultCache(false);
$query->disableResultCache();

return $query->execute(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) > 0;
return $query;
}

private function buildQueryEventParticipantExists(Participant $participant, QueryBuilder $queryBuilder): Query
{
$queryBuilder
->select('COUNT(p.id)')
->from('MealzMealBundle:Participant', 'p')
->join('p.event', 'e')
->join('p.profile', 'u')
->where('e = :event AND u = :profile')
;
if ($participant->getId()) {
$queryBuilder->andWhere('p.id != :id');
$queryBuilder->setParameter('id', $participant->getId());
}

$query = $queryBuilder->getQuery();
$query->setParameter('event', $participant->getEvent()->getId());
$query->setParameter('profile', $participant->getProfile()->getUsername());
$query->disableResultCache();

return $query;
}
}
4 changes: 2 additions & 2 deletions src/Mealz/MealBundle/Resources/config/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,12 @@ MealzMealBundle_api_events_delete:
methods: [ DELETE ]

MealzMealBundle_api_events_join:
path: /api/events/participation
path: /api/events/participation/{date}
defaults: { _controller: App\Mealz\MealBundle\Controller\EventController::join }
methods: [ POST ]

MealzMealBundle_api_events_leave:
path: /api/events/participation
path: /api/events/participation/{date}
defaults: { _controller: App\Mealz\MealBundle\Controller\EventController::leave }
methods: [ DELETE ]

Expand Down
6 changes: 2 additions & 4 deletions src/Mealz/MealBundle/Service/EventParticipationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function getEventParticipationData(Day $day, Profile $profile): ?array

return [
'eventId' => $eventParticipation->getEvent()->getId(),
'participationId' => $eventParticipation->getId(),
'participations' => count($eventParticipation->getParticipants()),
'isParticipating' => null !== $eventParticipation->getParticipant($profile),
];
Expand All @@ -68,8 +69,6 @@ public function join(Profile $profile, Day $day): ?EventParticipation
$this->em->persist($participation);
$this->em->flush();

$eventParticipation->addParticipant($participation);

return $eventParticipation;
}
}
Expand All @@ -81,8 +80,7 @@ public function leave(Profile $profile, Day $day): ?EventParticipation
{
$eventParticipation = $day->getEvent();
$participation = $eventParticipation->getParticipant($profile);

$eventParticipation->removeParticipant($participation);

$this->em->remove($participation);
$this->em->flush();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
namespace App\Mealz\MealBundle\Tests\Controller;

use App\Mealz\AccountingBundle\Entity\Transaction;
use App\Mealz\MealBundle\Entity\Day;
use App\Mealz\MealBundle\Entity\Event;
use App\Mealz\MealBundle\Entity\EventParticipation;
use App\Mealz\MealBundle\Entity\Meal;
use App\Mealz\MealBundle\Entity\Participant;
use App\Mealz\MealBundle\Repository\MealRepositoryInterface;
Expand Down Expand Up @@ -155,6 +158,15 @@ protected function createParticipant(Profile $profile, Meal $meal): Participant
return $participant;
}

protected function createEventParticipation(Day $day, Event $event): EventParticipation
{
$eventParticipation = new EventParticipation($day, $event);
$day->setEvent($eventParticipation);
$this->persistAndFlushAll([$eventParticipation, $day]);

return $eventParticipation;
}

/**
* Helper method to get the recent meal.
*/
Expand Down
33 changes: 33 additions & 0 deletions src/Mealz/MealBundle/Tests/Controller/EventControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

namespace App\Mealz\MealBundle\Tests\Controller;

use App\Mealz\MealBundle\DataFixtures\ORM\LoadDays;
use App\Mealz\MealBundle\DataFixtures\ORM\LoadEvents;
use App\Mealz\MealBundle\DataFixtures\ORM\LoadWeeks;
use App\Mealz\MealBundle\Entity\Day;
use App\Mealz\MealBundle\Entity\Event;
use App\Mealz\MealBundle\Repository\DayRepositoryInterface;
use App\Mealz\UserBundle\DataFixtures\ORM\LoadRoles;
use App\Mealz\UserBundle\DataFixtures\ORM\LoadUsers;
use DateTime;
use Doctrine\ORM\EntityManager;

class EventControllerTest extends AbstractControllerTestCase
Expand All @@ -16,9 +21,11 @@ protected function setUp(): void

$this->clearAllTables();
$this->loadFixtures([
new LoadDays(),
new LoadEvents(),
new LoadRoles(),
new LoadUsers(self::$container->get('security.user_password_encoder.generic')),
new LoadWeeks(),
]);

$this->loginAs(self::USER_KITCHEN_STAFF);
Expand Down Expand Up @@ -113,4 +120,30 @@ public function testDelete(): void

$this->assertTrue($event->isDeleted());
}

public function testJoin(): void
{
$newEvent = $this->createEvent();
$this->persistAndFlushAll([$newEvent]);

$dayRepo = self::$container->get(DayRepositoryInterface::class);

$criteria = new \Doctrine\Common\Collections\Criteria();
$criteria->where(\Doctrine\Common\Collections\Criteria::expr()->gt('lockParticipationDateTime', new DateTime()));

/** @var Day $day */
$day = $dayRepo->matching($criteria)->get(0);
$this->assertNotNull($day);

$eventParticipation = $this->createEventParticipation($day, $newEvent);

$url = '/api/events/participation/' . $day->getDateTime()->format('Y-m-d') . '%20' . $day->getDateTime()->format('H:i:s');
$this->client->request('POST', $url);
$this->assertEquals(200, $this->client->getResponse()->getStatusCode(), $this->client->getResponse());

/** @var Day $changedDay */
$changedDay = $dayRepo->getDayByDate($day->getDateTime());
$this->assertCount(1, $changedDay->getEvent()->getParticipants());
$this->assertEquals(self::USER_KITCHEN_STAFF, $changedDay->getEvent()->getParticipants()->get(0)->getProfile()->getUsername());
}
}
14 changes: 14 additions & 0 deletions src/Resources/src/api/deleteLeaveEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import useApi from "./api";
import { IMessage } from "@/interfaces/IMessage";
import { EventParticipationResponse } from "./postJoinEvent";

export async function deleteLeaveEvent(date: string) {
const { response, request, error } = useApi<IMessage | EventParticipationResponse>(
'DELETE',
`api/events/participation/${date}`
);

await request();

return { error, response };
}
Loading

0 comments on commit 83645f7

Please sign in to comment.