small fixes on sync from remote

This commit is contained in:
Julien Fastré 2022-06-09 17:06:57 +02:00
parent 6e48a042b3
commit f149b24802
5 changed files with 317 additions and 2 deletions

View File

@ -550,7 +550,6 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
public function setStartDate(DateTimeImmutable $startDate): self public function setStartDate(DateTimeImmutable $startDate): self
{ {
dump('startDate', $startDate);
$this->startDate = $startDate; $this->startDate = $startDate;
return $this; return $this;

View File

@ -23,6 +23,8 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
/** /**
* Handle notification of chagnes from MSGraph
*
* @AsMessageHandler * @AsMessageHandler
*/ */
class MSGraphChangeNotificationHandler implements MessageHandlerInterface class MSGraphChangeNotificationHandler implements MessageHandlerInterface
@ -43,6 +45,26 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface
private UserRepository $userRepository; private UserRepository $userRepository;
public function __construct(
CalendarRangeRepository $calendarRangeRepository,
CalendarRangeSyncer $calendarRangeSyncer,
CalendarRepository $calendarRepository,
EntityManagerInterface $em,
InviteRepository $inviteRepository,
LoggerInterface $logger,
MapCalendarToUser $mapCalendarToUser,
UserRepository $userRepository
) {
$this->calendarRangeRepository = $calendarRangeRepository;
$this->calendarRangeSyncer = $calendarRangeSyncer;
$this->calendarRepository = $calendarRepository;
$this->em = $em;
$this->inviteRepository = $inviteRepository;
$this->logger = $logger;
$this->mapCalendarToUser = $mapCalendarToUser;
$this->userRepository = $userRepository;
}
public function __invoke(MSGraphChangeNotificationMessage $changeNotificationMessage): void public function __invoke(MSGraphChangeNotificationMessage $changeNotificationMessage): void
{ {
$user = $this->userRepository->find($changeNotificationMessage->getUserId()); $user = $this->userRepository->find($changeNotificationMessage->getUserId());
@ -74,6 +96,8 @@ class MSGraphChangeNotificationHandler implements MessageHandlerInterface
} elseif (null !== $invite = $this->inviteRepository->findOneBy(['remoteId' => $remoteId])) { } elseif (null !== $invite = $this->inviteRepository->findOneBy(['remoteId' => $remoteId])) {
$this->remoteToLocalSyncer->handleInviteSync($invite, $notification, $user); $this->remoteToLocalSyncer->handleInviteSync($invite, $notification, $user);
$this->em->flush(); $this->em->flush();
} else {
$this->logger->info(__CLASS__." id not found in any calendar, calendar range nor invite");
} }
} }
} }

View File

@ -63,13 +63,15 @@ class CalendarRangeSyncer
try { try {
$new = $this->machineHttpClient->request( $new = $this->machineHttpClient->request(
'GET', 'GET',
'v1.0/' . $notification['resource'] $notification['resource']
)->toArray(); )->toArray();
} catch (ClientExceptionInterface $clientException) { } catch (ClientExceptionInterface $clientException) {
$this->logger->warning(__CLASS__ . ' could not retrieve event from ms graph. Already deleted ?', [ $this->logger->warning(__CLASS__ . ' could not retrieve event from ms graph. Already deleted ?', [
'calendarRangeId' => $calendarRange->getId(), 'calendarRangeId' => $calendarRange->getId(),
'remoteEventId' => $notification['resource'], 'remoteEventId' => $notification['resource'],
]); ]);
return;
} }
$lastModified = RemoteEventConverter::convertStringDateWithTimezone($new['lastModifiedDateTime']); $lastModified = RemoteEventConverter::convertStringDateWithTimezone($new['lastModifiedDateTime']);
@ -94,6 +96,9 @@ class CalendarRangeSyncer
]) ])
->preventEnqueueChanges = true ->preventEnqueueChanges = true
; ;
break;
default:
throw new \RuntimeException('This changeType is not suppored: '.$notification['changeType']);
} }
} }
} }

View File

@ -0,0 +1,93 @@
<?php
namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter;
use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class CalendarSyncer
{
private LoggerInterface $logger;
private HttpClientInterface $machineHttpClient;
/**
* @param LoggerInterface $logger
* @param HttpClientInterface $machineHttpClient
*/
public function __construct(LoggerInterface $logger, HttpClientInterface $machineHttpClient)
{
$this->logger = $logger;
$this->machineHttpClient = $machineHttpClient;
}
public function handleCalendarSync(Calendar $calendar, array $notification, User $user): void
{
switch ($notification['changeType']) {
case 'deleted':
$this->handleDeleteCalendar($calendar, $notification, $user);
break;
case 'updated':
$this->handleUpdateCalendar($calendar, $notification, $user);
break;
default:
throw new \RuntimeException("this change type is not supported: ".$notification['changeType']);
}
}
private function handleDeleteCalendar(Calendar $calendar, array $notification, User $user): void
{
$calendar
->setStatus(Calendar::STATUS_CANCELED)
->setCalendarRange(null)
;
$calendar->preventEnqueueChanges = true;
}
private function handleUpdateCalendar(Calendar $calendar, array $notification, User $user): void
{
try {
$new = $this->machineHttpClient->request(
'GET',
$notification['resource']
)->toArray();
} catch (ClientExceptionInterface $clientException) {
$this->logger->warning(__CLASS__ . ' could not retrieve event from ms graph. Already deleted ?', [
'calendarId' => $calendar->getId(),
'remoteEventId' => $notification['resource'],
]);
}
$lastModified = RemoteEventConverter::convertStringDateWithTimezone(
$new['lastModifiedDateTime']
);
if ($calendar->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) {
$this->logger->info(__CLASS__ . ' change key is equals. Source is probably a local update', [
'calendarRangeId' => $calendar->getId(),
'remoteEventId' => $notification['resource'],
]);
return;
}
$startDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['start']['dateTime']);
$endDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['end']['dateTime']);
$calendar
->setStartDate($startDate)->setEndDate($endDate)
->setStatus(Calendar::STATUS_MOVED)
->addRemoteAttributes([
'lastModifiedDateTime' => $lastModified->getTimestamp(),
'changeKey' => $new['changeKey'],
])
->preventEnqueueChanges = true
;
}
}

View File

@ -0,0 +1,194 @@
<?php
namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync\CalendarRangeSyncer;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync\CalendarSyncer;
use Chill\MainBundle\Entity\User;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
class CalendarSyncerTest extends TestCase
{
private const REMOTE_CALENDAR_NO_ATTENDEES = <<<'JSON'
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/events/$entity",
"@odata.etag": "W/\"B3bmsWoxX06b9JHIZPptRQAAJcswFA==\"",
"id": "AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA=",
"createdDateTime": "2022-06-08T15:22:24.0096697Z",
"lastModifiedDateTime": "2022-06-09T09:27:09.9223729Z",
"changeKey": "B3bmsWoxX06b9JHIZPptRQAAJcswFA==",
"categories": [],
"transactionId": "90c23105-a6b1-b594-1811-e4ffa612092a",
"originalStartTimeZone": "Romance Standard Time",
"originalEndTimeZone": "Romance Standard Time",
"iCalUId": "040000008200E00074C5B7101A82E00800000000A971DA8D4B7BD801000000000000000010000000BE3F4A21C9008E4FB35A4DE1F80E0118",
"reminderMinutesBeforeStart": 15,
"isReminderOn": true,
"hasAttachments": false,
"subject": "test notif",
"bodyPreview": "",
"importance": "normal",
"sensitivity": "normal",
"isAllDay": false,
"isCancelled": false,
"isOrganizer": true,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "busy",
"type": "singleInstance",
"webLink": "https://outlook.office365.com/owa/?itemid=AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk%2Bm1FAAAAAAENAAAHduaxajFfTpv0kchk%2Bm1FAAAl1BupAAA%3D&exvsurl=1&path=/calendar/item",
"onlineMeetingUrl": null,
"isOnlineMeeting": false,
"onlineMeetingProvider": "unknown",
"allowNewTimeProposals": true,
"occurrenceId": null,
"isDraft": false,
"hideAttendees": false,
"responseStatus": {
"response": "organizer",
"time": "0001-01-01T00:00:00Z"
},
"body": {
"contentType": "html",
"content": ""
},
"start": {
"dateTime": "2022-06-10T13:30:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2022-06-10T15:30:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "",
"locationType": "default",
"uniqueIdType": "unknown",
"address": {},
"coordinates": {}
},
"locations": [],
"recurrence": null,
"attendees": [],
"organizer": {
"emailAddress": {
"name": "Diego Siciliani",
"address": "DiegoS@2zy74l.onmicrosoft.com"
}
},
"onlineMeeting": null,
"calendar@odata.associationLink": "https://graph.microsoft.com/v1.0/Users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/calendar/$ref",
"calendar@odata.navigationLink": "https://graph.microsoft.com/v1.0/Users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/calendar"
}
JSON;
private const NOTIF_UPDATE = <<<'JSON'
{
"value": [
{
"subscriptionId": "739703eb-80c4-4c03-b15a-ca370f19624b",
"subscriptionExpirationDateTime": "2022-06-09T02:40:28-07:00",
"changeType": "updated",
"resource": "Users/4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4/Events/AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA=",
"resourceData": {
"@odata.type": "#Microsoft.Graph.Event",
"@odata.id": "Users/4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4/Events/AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA=",
"@odata.etag": "W/\"DwAAABYAAAAHduaxajFfTpv0kchk+m1FAAAlyzAU\"",
"id": "AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA="
},
"clientState": "2k05qlr3ds2KzvUP3Ps4A+642fYaI8ThxHGIGbNr2p0MnNkmzxLTNEMxpMc/UEuDkBHfID7OYWj4DQc94vlEkPBdsh9sGTTkHxIE68hqkKkDKhwvfvdj6lS6Dus=",
"tenantId": "421bf216-3f48-47bd-a7cf-8b1995cb24bd"
}
]
}
JSON;
private const NOTIF_DELETE = <<<'JSON'
{
"value": [
{
"subscriptionId": "077e8d19-68b3-4d8e-9b1e-8b4ba6733799",
"subscriptionExpirationDateTime": "2022-06-09T06:22:02-07:00",
"changeType": "deleted",
"resource": "Users/4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4/Events/AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA=",
"resourceData": {
"@odata.type": "#Microsoft.Graph.Event",
"@odata.id": "Users/4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4/Events/AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA=",
"@odata.etag": "W/\"CQAAAA==\"",
"id": "AAMkADM1MTdlMGIzLTZhZWUtNDQ0ZC05Y2M4LWViMjhmOWJlMDhhMQBGAAAAAAA5e3965gkBSLcU1p00sMSyBwAHduaxajFfTpv0kchk_m1FAAAAAAENAAAHduaxajFfTpv0kchk_m1FAAAl1BupAAA="
},
"clientState": "uds18apRCByqWIodFCHKeM0kJqhfr+qXL/rJWYn7xmtdQ4t03W2OHEOdGJ0Ceo52NAzOYVDpbfRM3TdrZDUiE09OxZkPX/vkpdcnipoiVnPPMFBQn05p8KhklOM=",
"tenantId": "421bf216-3f48-47bd-a7cf-8b1995cb24bd"
}
]
}
JSON;
public function testHandleDeleteCalendar(): void
{
$machineHttpClient = new MockHttpClient([]);
$calendarSyncer = new CalendarSyncer(
new NullLogger(),
$machineHttpClient
);
$calendar = new Calendar();
$calendar
->setMainUser($user = new User())
->setCalendarRange($calendarRange = new CalendarRange());
;
$notification = json_decode(self::NOTIF_DELETE, true);
$calendarSyncer->handleCalendarSync(
$calendar,
$notification['value'][0],
$user);
$this->assertEquals(Calendar::STATUS_CANCELED, $calendar->getStatus());
$this->assertNull($calendar->getCalendarRange());
$this->assertTrue($calendar->preventEnqueueChanges);
}
public function testHandleMoveCalendar(): void
{
$machineHttpClient = new MockHttpClient([
new MockResponse(self::REMOTE_CALENDAR_NO_ATTENDEES, ['http_code' => 200])
]);
$calendarSyncer = new CalendarSyncer(
new NullLogger(),
$machineHttpClient
);
$calendar = new Calendar();
$calendar
->setMainUser($user = new User())
->setStartDate(new \DateTimeImmutable('2020-01-01 10:00:00'))
->setEndDate(new \DateTimeImmutable('2020-01-01 12:00:00'))
->setCalendarRange(new CalendarRange())
->addRemoteAttributes([
'lastModifiedDateTime' => 0,
'changeKey' => 'abcd',
])
;
$notification = json_decode(self::NOTIF_UPDATE, true);
$calendarSyncer->handleCalendarSync(
$calendar,
$notification['value'][0],
$user);
$this->assertStringContainsString('2022-06-10T15:30:00',
$calendar->getStartDate()->format(\DateTimeImmutable::ATOM));
$this->assertStringContainsString('2022-06-10T17:30:00',
$calendar->getEndDate()->format(\DateTimeImmutable::ATOM));
$this->assertTrue($calendar->preventEnqueueChanges);
$this->assertEquals(Calendar::STATUS_MOVED, $calendar->getStatus());
}
}