diff --git a/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php b/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php index 62c3dc521..05ab52718 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/InviteApiController.php @@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Controller; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Invite; +use Chill\CalendarBundle\Messenger\Message\InviteUpdateMessage; use Chill\CalendarBundle\Security\Voter\InviteVoter; use Chill\MainBundle\Entity\User; use Doctrine\ORM\EntityManagerInterface; @@ -20,6 +21,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use function in_array; @@ -28,12 +30,15 @@ class InviteApiController { private EntityManagerInterface $entityManager; + private MessageBusInterface $messageBus; + private Security $security; - public function __construct(Security $security, EntityManagerInterface $entityManager) + public function __construct(EntityManagerInterface $entityManager, MessageBusInterface $messageBus, Security $security) { - $this->security = $security; $this->entityManager = $entityManager; + $this->messageBus = $messageBus; + $this->security = $security; } /** @@ -64,6 +69,8 @@ class InviteApiController $invite->setStatus($answer); $this->entityManager->flush(); + $this->messageBus->dispatch(new InviteUpdateMessage($invite, $this->security->getUser())); + return new JsonResponse(null, Response::HTTP_ACCEPTED, [], false); } } diff --git a/src/Bundle/ChillCalendarBundle/Entity/Invite.php b/src/Bundle/ChillCalendarBundle/Entity/Invite.php index be80a3e56..e7f25d932 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Invite.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Invite.php @@ -21,11 +21,20 @@ use LogicException; use Symfony\Component\Serializer\Annotation as Serializer; /** - * @ORM\Table(name="chill_calendar.invite") + * An invitation for another user to a Calendar. + * + * The event/calendar in the user may have a different id than the mainUser. We add then fields to store the + * remote id of this event in the remote calendar. + * + * @ORM\Table(name="chill_calendar.invite", indexes={ + * @ORM\Index(name="idx_calendar_invite_remote", columns={"remoteId"}) + * }) * @ORM\Entity */ class Invite implements TrackUpdateInterface, TrackCreationInterface { + use RemoteCalendarTrait; + use TrackCreationTrait; use TrackUpdateTrait; diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php new file mode 100644 index 000000000..ece0b6929 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/InviteUpdateHandler.php @@ -0,0 +1,48 @@ +em = $em; + $this->inviteRepository = $inviteRepository; + $this->remoteCalendarConnector = $remoteCalendarConnector; + } + + public function __invoke(InviteUpdateMessage $inviteUpdateMessage): void + { + if (null === $invite = $this->inviteRepository->find($inviteUpdateMessage->getInviteId())) { + return; + } + + $this->remoteCalendarConnector->syncInvite($invite); + + $this->em->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php new file mode 100644 index 000000000..ddb5d9028 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/InviteUpdateMessage.php @@ -0,0 +1,38 @@ +inviteId = $invite->getId(); + $this->byUserId = $byUser->getId(); + } + + public function getByUserId(): int + { + return $this->byUserId; + } + + public function getInviteId(): int + { + return $this->inviteId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php index fcc74cb1c..afd68cdb4 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php @@ -112,6 +112,7 @@ class RemoteEventConverter ['calendar' => $calendar] ), ], + 'responseRequested' => true, ], $this->calendarToEventAttendeesOnly($calendar) ); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php index 872904877..d346d67bc 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\RemoteCalendar\Connector; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\CalendarRange; +use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\OnBehalfOfUserHttpClient; @@ -209,6 +210,67 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } } + public function syncInvite(Invite $invite): void + { + if ('' === $remoteId = $invite->getCalendar()->getRemoteId()) { + return; + } + + if (null === $invite->getUser()) { + return; + } + + if (null === $userId = $this->mapCalendarToUser->getUserId($invite->getUser())) { + return; + } + + if ($invite->hasRemoteId()) { + $remoteIdAttendeeCalendar = $invite->getRemoteId(); + } else { + $remoteIdAttendeeCalendar = $this->findRemoteIdOnUserCalendar($invite->getCalendar(), $invite->getUser()); + $invite->setRemoteId($remoteIdAttendeeCalendar); + } + + switch ($invite->getStatus()) { + case Invite::PENDING: + return; + + case Invite::ACCEPTED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/accept"; + + break; + + case Invite::TENTATIVELY_ACCEPTED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/tentativelyAccept"; + + break; + + case Invite::DECLINED: + $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/decline"; + + break; + + default: + throw new Exception('not supported'); + } + + try { + $this->machineHttpClient->request( + 'POST', + $url, + ['json' => ['sendResponse' => true]] + ); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not update calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'content' => $e->getResponse()->getContent(), + 'calendarRangeId' => 'invite_' . $invite->getId(), + ]); + + throw $e; + } + } + private function cancelOnRemote(string $remoteId, string $comment, User $user, string $identifier): void { $userId = $this->mapCalendarToUser->getUserId($user); @@ -333,6 +395,44 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface ]); } + /** + * the remoteId is not the same across different user calendars. This method allow to find + * the correct remoteId in another calendar. + * + * For achieving this, the iCalUid is used. + */ + private function findRemoteIdOnUserCalendar(Calendar $calendar, User $user): ?string + { + // find the icalUid on original user + $event = $this->getOnRemote($calendar->getMainUser(), $calendar->getRemoteId()); + $userId = $this->mapCalendarToUser->getUserId($user); + + if ('' === $iCalUid = ($event['iCalUId'] ?? '')) { + throw new Exception('no iCalUid for this event'); + } + + try { + $events = $this->machineHttpClient->request( + 'GET', + "/v1.0/users/{$userId}/calendar/events", + [ + 'query' => [ + '$select' => 'id', + '$filter' => "iCalUId eq '{$iCalUid}'", + ], + ] + )->toArray(); + } catch (ClientExceptionInterface $clientException) { + throw $clientException; + } + + if (1 !== count($events['value'])) { + throw new Exception('multiple events found with same iCalUid'); + } + + return $events['value'][0]['id']; + } + private function getOnRemote(User $user, string $remoteId): array { $userId = $this->mapCalendarToUser->getUserId($user); @@ -345,10 +445,13 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } try { - return $this->machineHttpClient->request( + $v = $this->machineHttpClient->request( 'GET', 'users/' . $userId . '/calendar/events/' . $remoteId )->toArray(); + dump($v); + + return $v; } catch (ClientExceptionInterface $e) { $this->logger->warning('Could not get event from calendar', [ 'remoteId' => $remoteId, diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php index ef0db2548..a1803d9c1 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php @@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\RemoteCalendar\Connector; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\CalendarRange; +use Chill\CalendarBundle\Entity\Invite; use Chill\MainBundle\Entity\User; use DateTimeImmutable; use LogicException; @@ -46,4 +47,8 @@ class NullRemoteCalendarConnector implements RemoteCalendarConnectorInterface public function syncCalendarRange(CalendarRange $calendarRange): void { } + + public function syncInvite(Invite $invite): void + { + } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php index c55c06f86..4eebb8863 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php @@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\RemoteCalendar\Connector; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\CalendarRange; +use Chill\CalendarBundle\Entity\Invite; use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent; use Chill\MainBundle\Entity\User; use DateTimeImmutable; @@ -46,4 +47,6 @@ interface RemoteCalendarConnectorInterface public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void; public function syncCalendarRange(CalendarRange $calendarRange): void; + + public function syncInvite(Invite $invite): void; } diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php new file mode 100644 index 000000000..390e1b743 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220608084052.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE chill_calendar.invite DROP remoteAttributes'); + $this->addSql('ALTER TABLE chill_calendar.invite DROP remoteId'); + } + + public function getDescription(): string + { + return 'Add remoteId for invitation'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.invite ADD remoteAttributes JSON DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.invite ADD remoteId TEXT DEFAULT \'\' NOT NULL'); + $this->addSql('CREATE INDEX idx_calendar_invite_remote ON chill_calendar.invite (remoteId)'); + } +}