From e895da31d7c2ff2113f6b4fa13c6e3302805ac4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 11 May 2022 12:17:29 +0200 Subject: [PATCH] wip: synchro of calendar range --- composer.json | 1 + .../Entity/CalendarRange.php | 14 ++++- .../Entity/RemoteCalendarTrait.php | 56 +++++++++++++++++++ .../Doctrine/CalendarEntityListener.php | 22 ++++++++ .../Doctrine/CalendarRangeEntityListener.php | 53 ++++++++++++++++++ .../Handler/CalendarRangeToRemoteHandler.php | 50 +++++++++++++++++ .../Handler/CalendarToRemoteHandler.php | 32 +++++++++++ .../Messenger/Message/CalendarMessage.php | 41 ++++++++++++++ .../Message/CalendarRangeMessage.php | 53 ++++++++++++++++++ .../MSGraph/RemoteEventConverter.php | 34 +++++++++++ .../MSGraphRemoteCalendarConnector.php | 52 +++++++++++++++++ .../Connector/NullRemoteCalendarConnector.php | 5 ++ .../RemoteCalendarConnectorInterface.php | 3 + .../Resources/config/services.yml | 5 ++ .../Resources/config/services/event.yml | 15 ++++- .../migrations/Version20220510155609.php | 55 ++++++++++++++++++ .../translations/messages.fr.yml | 5 +- 17 files changed, 492 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarRangeEntityListener.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarMessage.php create mode 100644 src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php create mode 100644 src/Bundle/ChillCalendarBundle/migrations/Version20220510155609.php diff --git a/composer.json b/composer.json index 4c4169e1e..26bb5a517 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "symfony/http-foundation": "^4.4", "symfony/intl": "^4.4", "symfony/mailer": "^5.4", + "symfony/messenger": "^5.4", "symfony/mime": "^5.4", "symfony/monolog-bundle": "^3.5", "symfony/security-bundle": "^4.4", diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php index b29a9db08..a16c7b72b 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php @@ -12,6 +12,10 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Entity; use Chill\CalendarBundle\Repository\CalendarRangeRepository; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; use Chill\MainBundle\Entity\User; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; @@ -20,11 +24,17 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; /** - * @ORM\Table(name="chill_calendar.calendar_range") + * @ORM\Table(name="chill_calendar.calendar_range", indexes={@ORM\Index(name="idx_calendar_range_remote", columns={"remoteId"})}) * @ORM\Entity(repositoryClass=CalendarRangeRepository::class) */ -class CalendarRange +class CalendarRange implements TrackCreationInterface, TrackUpdateInterface { + use RemoteCalendarTrait; + + use TrackCreationTrait; + + use TrackUpdateTrait; + /** * @ORM\OneToMany(targetEntity=Calendar::class, * mappedBy="calendarRange") diff --git a/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php b/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php new file mode 100644 index 000000000..5e3b2fd22 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Entity/RemoteCalendarTrait.php @@ -0,0 +1,56 @@ +remoteAttributes = array_merge($this->remoteAttributes, $remoteAttributes); + + return $this; + } + + public function getRemoteAttributes(): array + { + return $this->remoteAttributes; + } + + public function getRemoteId(): string + { + return $this->remoteId; + } + + public function hasRemoteId(): bool + { + return '' !== $this->remoteId; + } + + public function setRemoteId(string $remoteId): self + { + $this->remoteId = $remoteId; + + return $this; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php new file mode 100644 index 000000000..4603db260 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php @@ -0,0 +1,22 @@ +messageBus = $messageBus; + $this->security = $security; + } + + public function postPersist(CalendarRange $calendarRange, LifecycleEventArgs $eventArgs): void + { + $this->messageBus->dispatch( + new CalendarRangeMessage( + $calendarRange, + CalendarRangeMessage::CALENDAR_RANGE_PERSIST, + $this->security->getUser() + ) + ); + } + + public function postUpdate(CalendarRange $calendarRange, LifecycleEventArgs $eventArgs): void + { + $this->messageBus->dispatch( + new CalendarRangeMessage( + $calendarRange, + CalendarRangeMessage::CALENDAR_RANGE_UPDATE, + $this->security->getUser() + ) + ); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php new file mode 100644 index 000000000..a22bd7cf8 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarRangeToRemoteHandler.php @@ -0,0 +1,50 @@ +calendarRangeRepository = $calendarRangeRepository; + $this->remoteCalendarConnector = $remoteCalendarConnector; + $this->entityManager = $entityManager; + } + + public function __invoke(CalendarRangeMessage $calendarRangeMessage): void + { + $range = $this->calendarRangeRepository->find($calendarRangeMessage->getCalendarRangeId()); + + $this->remoteCalendarConnector->syncCalendarRange($range); + + $this->entityManager->flush(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php new file mode 100644 index 000000000..c71f2deaf --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Handler/CalendarToRemoteHandler.php @@ -0,0 +1,32 @@ +calendarId = $calendar->getId(); + $this->action = $action; + } + + public function getAction(): string + { + return $this->action; + } + + public function getCalendarId(): ?int + { + return $this->calendarId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php new file mode 100644 index 000000000..b02697147 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRangeMessage.php @@ -0,0 +1,53 @@ +action = $action; + $this->calendarRangeId = $calendarRange->getId(); + + if (null !== $byUser) { + $this->byUserId = $byUser->getId(); + } + } + + public function getAction(): string + { + return $this->action; + } + + public function getByUserId(): ?int + { + return $this->byUserId; + } + + public function getCalendarRangeId(): ?int + { + return $this->calendarRangeId; + } +} diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php index 2ccb48683..409a30240 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/RemoteEventConverter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph; +use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent; use DateTimeImmutable; use DateTimeZone; @@ -22,6 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class RemoteEventConverter { + public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\\TH:i:s.u?P'; + private const REMOTE_DATE_FORMAT = 'Y-m-d\TH:i:s.u0'; private DateTimeZone $defaultDateTimeZone; @@ -37,6 +40,37 @@ class RemoteEventConverter $this->remoteDateTimeZone = self::getRemoteTimeZone(); } + /** + * Transform a CalendarRange into a representation suitable for storing into MSGraph. + * + * @return array an array representation for event in MS Graph + */ + public function calendarRangeToEvent(CalendarRange $calendarRange): array + { + return [ + 'subject' => $this->translator->trans('remote_calendar.calendar_range_title'), + 'start' => [ + 'dateTime' => $calendarRange->getStartDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'end' => [ + 'dateTime' => $calendarRange->getEndDate()->setTimezone($this->remoteDateTimeZone) + ->format(self::REMOTE_DATE_FORMAT), + 'timeZone' => 'UTC', + ], + 'attendees' => [ + [ + 'emailAddress' => [ + 'address' => $calendarRange->getUser()->getEmailCanonical(), + 'name' => $calendarRange->getUser()->getLabel(), + ], + ], + ], + 'isReminderOn' => false, + ]; + } + public function convertAvailabilityToRemoteEvent(array $event): RemoteEvent { $startDate = diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php index 914aece91..ade4baa28 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraphRemoteCalendarConnector.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector; +use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser; use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\OnBehalfOfUserHttpClient; @@ -18,6 +19,7 @@ use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\OnBehalfOfUserTokenSto use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter; use Chill\MainBundle\Entity\User; use DateTimeImmutable; +use Exception; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; @@ -105,6 +107,56 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface } } + public function syncCalendarRange(CalendarRange $calendarRange): void + { + if ($calendarRange->hasRemoteId()) { + throw new Exception('update existing not implemented'); + } + $this->createRemoteCalendarRange($calendarRange); + } + + private function createRemoteCalendarRange(CalendarRange $calendarRange): void + { + $userId = $this->mapCalendarToUser->getUserId($calendarRange->getUser()); + $calendarId = $this->mapCalendarToUser->getCalendarId($calendarRange->getUser()); + + if (null === $userId || null === $calendarId) { + $this->logger->warning('user does not have userId nor calendarId', [ + 'user_id' => $calendarRange->getUser()->getId(), + 'calendar_range_id' => $calendarRange->getId(), + ]); + + return; + } + + $eventData = $this->remoteEventConverter->calendarRangeToEvent($calendarRange); + + try { + $event = $this->machineHttpClient->request( + 'POST', + 'users/' . $userId . '/calendar/events', + [ + 'json' => $eventData, + ] + )->toArray(); + } catch (ClientExceptionInterface $e) { + $this->logger->warning('could not save calendar range to remote', [ + 'exception' => $e->getTraceAsString(), + 'calendarRangeId' => $calendarRange->getId(), + ]); + + throw $e; + } + + dump($event); + + $calendarRange->setRemoteId($event['id']) + ->addRemoteAttributes([ + 'lastModifiedDateTime' => (DateTimeImmutable::createFromFormat(RemoteEventConverter::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']))->getTimestamp(), + 'changeKey' => $event['changeKey'], + ]); + } + private function getScheduleTimesForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): array { $userId = $this->mapCalendarToUser->getUserId($user); diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php index c0901c6c5..800eb1369 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/NullRemoteCalendarConnector.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector; +use Chill\CalendarBundle\Entity\CalendarRange; use Chill\MainBundle\Entity\User; use DateTimeImmutable; use LogicException; @@ -32,4 +33,8 @@ class NullRemoteCalendarConnector implements RemoteCalendarConnectorInterface { return []; } + + public function syncCalendarRange(CalendarRange $calendarRange): void + { + } } diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php index 7a3fc6132..298334309 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/RemoteCalendarConnectorInterface.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\RemoteCalendar\Connector; +use Chill\CalendarBundle\Entity\CalendarRange; use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent; use Chill\MainBundle\Entity\User; use DateTimeImmutable; @@ -35,4 +36,6 @@ interface RemoteCalendarConnectorInterface * @return array|RemoteEvent[] */ public function listEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): array; + + public function syncCalendarRange(CalendarRange $calendarRange): void; } diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services.yml index 19fbebab6..bdfaf413f 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services.yml @@ -18,6 +18,11 @@ services: autoconfigure: true resource: '../../Command/' + Chill\CalendarBundle\Messenger\: + autowire: true + autoconfigure: true + resource: '../../Messenger/' + Chill\CalendarBundle\Command\AzureGrantAdminConsentAndAcquireToken: autoconfigure: true autowire: true diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml index 348677174..4a0ca3ca0 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml @@ -7,4 +7,17 @@ services: name: 'doctrine.orm.entity_listener' event: 'postPersist' entity: 'Chill\ActivityBundle\Entity\Activity' - \ No newline at end of file + + Chill\CalendarBundle\Messenger\Doctrine\CalendarRangeEntityListener: + autowire: true + autoconfigure: true + + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'Chill\CalendarBundle\Entity\CalendarRange' + - + name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'Chill\CalendarBundle\Entity\CalendarRange' diff --git a/src/Bundle/ChillCalendarBundle/migrations/Version20220510155609.php b/src/Bundle/ChillCalendarBundle/migrations/Version20220510155609.php new file mode 100644 index 000000000..30927836e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/migrations/Version20220510155609.php @@ -0,0 +1,55 @@ +addSql('ALTER TABLE chill_calendar.calendar_range DROP CONSTRAINT FK_38D57D0565FF1AEC'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP CONSTRAINT FK_38D57D053174800F'); + $this->addSql('DROP INDEX chill_calendar.IDX_38D57D0565FF1AEC'); + $this->addSql('DROP INDEX chill_calendar.IDX_38D57D053174800F'); + $this->addSql('DROP INDEX chill_calendar.idx_calendar_range_remote'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP remoteId'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP remoteAttributes'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP updatedAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP createdAt'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP updatedBy_id'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range DROP createdBy_id'); + } + + public function getDescription(): string + { + return 'Add columns on calendar range to handle remote'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD remoteId TEXT DEFAULT \'\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD remoteAttributes JSON DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD updatedBy_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD createdBy_id INT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD CONSTRAINT FK_38D57D0565FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_calendar.calendar_range ADD CONSTRAINT FK_38D57D053174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_38D57D0565FF1AEC ON chill_calendar.calendar_range (updatedBy_id)'); + $this->addSql('CREATE INDEX IDX_38D57D053174800F ON chill_calendar.calendar_range (createdBy_id)'); + $this->addSql('CREATE INDEX idx_calendar_range_remote ON chill_calendar.calendar_range (remoteId)'); + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index d8c8a5d2e..da96db414 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -14,7 +14,7 @@ start date: début du rendez-vous end date: fin du rendez-vous cancel reason: motif d'annulation status: Statut du rendez-vous -calendar location: Localistion du rendez-vous +calendar location: Localisation du rendez-vous calendar comment: Remarque sur le rendez-vous sendSMS: Envoi d'un SMS Send s m s: Envoi d'un SMS ? @@ -35,3 +35,6 @@ remote_ms_graph: oof: En dehors du bureau workingElsewhere: Travaille à l'extérieur unknown: Inconnu + +remote_calendar: + calendar_range_title: Plage de disponibilité Chill