handling sync for calendar invite

This commit is contained in:
Julien Fastré 2022-06-08 13:06:31 +02:00
parent c329862e96
commit e75b258e44
9 changed files with 254 additions and 4 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,48 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Messenger\Handler;
use Chill\CalendarBundle\Messenger\Message\InviteUpdateMessage;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\CalendarBundle\Repository\InviteRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/**
* @AsMessageHandler
*/
class InviteUpdateHandler implements MessageHandlerInterface
{
private EntityManagerInterface $em;
private InviteRepository $inviteRepository;
private RemoteCalendarConnectorInterface $remoteCalendarConnector;
public function __construct(EntityManagerInterface $em, InviteRepository $inviteRepository, RemoteCalendarConnectorInterface $remoteCalendarConnector)
{
$this->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();
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Messenger\Message;
use Chill\CalendarBundle\Entity\Invite;
use Chill\MainBundle\Entity\User;
class InviteUpdateMessage
{
private int $byUserId;
private int $inviteId;
public function __construct(Invite $invite, User $byUser)
{
$this->inviteId = $invite->getId();
$this->byUserId = $byUser->getId();
}
public function getByUserId(): int
{
return $this->byUserId;
}
public function getInviteId(): int
{
return $this->inviteId;
}
}

View File

@ -112,6 +112,7 @@ class RemoteEventConverter
['calendar' => $calendar]
),
],
'responseRequested' => true,
],
$this->calendarToEventAttendeesOnly($calendar)
);

View File

@ -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,

View File

@ -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
{
}
}

View File

@ -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;
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Calendar;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220608084052 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->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)');
}
}