src/Controller/Webhook/GoogleWebhookController.php line 180

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Webhook;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Routing\Annotation\Route;
  6. use Psr\Log\LoggerInterface
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Google_Client;
  9. use Google_Service_Calendar;
  10. use App\Entity\SynchronisationSetting;
  11. use App\Entity\GoogleAgenda;
  12. use Symfony\Component\Mercure\PublisherInterface
  13. use Symfony\Component\Mercure\Update;            
  14. use App\Service\Synchronisation\SynchronisationLogger;
  15. use Google\Service\Exception as GoogleServiceException;
  16. class GoogleWebhookController extends AbstractController
  17. {
  18.     public $logger;
  19.     private $publisher
  20.     private SynchronisationLogger $syncLogger;
  21.     public function __construct(LoggerInterface $logger,PublisherInterface $publisher,SynchronisationLogger $syncLogger)
  22.     {
  23.         $this->logger $logger;
  24.         $this->publisher $publisher
  25.         $this->syncLogger $syncLogger;
  26.     }
  27.      /**
  28.      * @Route("/webhook/google-calendar", methods={"POST"})
  29.      */
  30.     public function handleGoogleCalendarWebhook(Request $request)
  31. {
  32.     // Verify the request is coming from Google (implement security measures).
  33.     
  34.     // Extract channel information from request headers.
  35.     $resourceId $request->headers->get('x-goog-resource-id');
  36.     $channelId $request->headers->get('x-goog-channel-id');
  37.   $this->logger->info('New syhro', ['event status' => $request->headers->all() ]);
  38.     
  39.     // Check if the request is valid.
  40.     if (!$resourceId || !$channelId) {
  41.         return new Response('Invalid request'Response::HTTP_BAD_REQUEST);
  42.     }
  43.     $entityManager $this->getDoctrine()->getManager();
  44.     // Perform incremental synchronization to fetch changed events.
  45.     $changedEvents $this->fetchChangedEvents($channelId$resourceId);
  46.     
  47.     // Handle the changed events based on your application's logic.
  48.     foreach ($changedEvents as $event) {
  49.         $synchronisation $this->getDoctrine()->getRepository(SynchronisationSetting::class)
  50.         ->findOneBy(array('channelGoogleNotif' => $channelId));
  51.         if($event->status == "cancelled")
  52.         {
  53.             $googleAgenda $this->getDoctrine()->getRepository(GoogleAgenda::class)
  54.             ->findOneBy(array('eventId' => $event->id));
  55.             if($googleAgenda){
  56.                 // 1. Get data BEFORE deleting (to get the ID)
  57.                 $publishedData $this->formatAgendaData($googleAgenda'google_deleted'); 
  58.                 $update = new Update('rdv-calendar-topic'json_encode($publishedData));
  59.                                 $this->logger->info('Mercure', ['action' => 'publish_deleted''eventId' => $event->id]); 
  60. $this->publisher->__invoke($update);
  61.             $entityManager->remove($googleAgenda);
  62.             $entityManager->flush();
  63.             }
  64.         }
  65.         else 
  66.         {
  67.             //if(strpos($event->getDescription(), 'Nom du patient: ') === false){
  68.             $agendaGoogle $this->getDoctrine()->getRepository(GoogleAgenda::class)
  69.             ->findOneBy(array('eventId' => $event->id));
  70.             if(!$agendaGoogle)
  71.             {
  72.                 $agendaGoogle = new GoogleAgenda();
  73.             }
  74.             $organizer $event->getOrganizer();
  75.             $organizerEmail $event->getOrganizer()->getEmail();
  76. $agendaGoogle->setEventId($event->getId());
  77. $agendaGoogle->setSummary($event->getSummary());
  78.     
  79. $startDateTime = new \DateTime($event->getStart()->getDateTime());
  80. $agendaGoogle->setStart($startDateTime);
  81. $endDateTime = new \DateTime($event->getEnd()->getDateTime());
  82. $agendaGoogle->setEnd($endDateTime);
  83. $meetingUrl $event->getHangoutLink();
  84. if (!empty($meetingUrl)) {
  85. $agendaGoogle->setMeetingUrl($meetingUrl);
  86. }
  87. $attendees $event->getAttendees();
  88. if (!empty($attendees)) {
  89. $attendeeEmails = array();
  90. foreach ($attendees as $attendee) {
  91. $attendeeEmails[] = $attendee->getEmail();
  92. }
  93. $serializedAttend json_encode(['invited' => $attendeeEmails]);
  94. $agendaGoogle->setAttendees($serializedAttend);
  95. }
  96. $attachments $event->getAttachments();
  97. if (!empty($attachments)) {
  98. $attachementsUrl = array();
  99. $attachementsName = array();
  100. $attachementsIcon = array();
  101. foreach ($attachments as $attachment) {
  102. $attachementsUrl[] = $attachment->fileUrl;
  103. $attachementsName[] = $attachment->title;
  104. //$attachementsName[] = $attachment->mimeType;
  105. $attachementsIcon[] = $attachment->iconLink;
  106. }
  107. $serializedattAchements json_encode(['attachementUrl' => $attachementsUrl,"attachementName" => $attachementsName,"attachementsIcon" => $attachementsIcon]);
  108. $agendaGoogle->setAttachement($serializedattAchements);
  109. }
  110. $agendaGoogle->setColor($event->getColorId());
  111. $agendaGoogle->setLocation($event->getLocation());
  112. $agendaGoogle->setDescription($event->getDescription());
  113. $agendaGoogle->setAudio($synchronisation->getAudio());
  114. $agendaGoogle->setSynchronisationSetting($synchronisation);     
  115. $agendaGoogle->setOrganiserEmail($organizerEmail);
  116.      
  117. $entityManager $this->getDoctrine()->getManager();
  118. $entityManager->persist($agendaGoogle);
  119. $entityManager->flush();
  120.             // Determine if it was a create or update action
  121.             $action $agendaGoogle->getId() ? 'google_updated' 'google_created';
  122.             // 1. Publish the update with the full event data
  123.             $publishedData $this->formatAgendaData($agendaGoogle$action);
  124.                         $this->logger->info('Mercure', ['action' => 'publish_'.$action'eventId' => $event->id]);
  125.             $update = new Update('rdv-calendar-topic'json_encode($publishedData));
  126. $this->publisher->__invoke($update);
  127. //}
  128. }
  129.         //if event is deleted $event->status = "cancelled"; we delete in our database $event->id;
  130.       //  $this->logger->info('New syhro', ['event status' => $event->status ]);
  131.        
  132.     }
  133.     
  134.     
  135.     // Return a response to acknowledge receipt of the webhook.
  136.     return new Response(''Response::HTTP_OK);
  137. }
  138. public function fetchChangedEvents($channelId$resourceId)
  139. {
  140.     try {
  141.         $client = new \Google_Client();
  142.         $em $this->getDoctrine()->getManager();
  143.         $syncRepo $em->getRepository(SynchronisationSetting::class);
  144.         $agendaRepo $em->getRepository(GoogleAgenda::class);
  145.         // 1️⃣ Find the synchronisation by channel ID
  146.         $synchronisation $syncRepo->findOneBy(['channelGoogleNotif' => $channelId]);
  147.         if (!$synchronisation) {
  148.             $this->syncLogger->log(
  149.                 'GoogleWebhook',
  150.                 "No synchronisation found for channelId: $channelId"
  151.             );
  152.             return [];
  153.         }
  154.         // 2️⃣ Initialize async token if null
  155.         if ($synchronisation->getAsynchToken() === null) {
  156.             $this->logger->info('New synchro', ['channelId' => $channelId]);
  157.             $googleAgendaList $agendaRepo->findBy(['synchronisationSetting' => $synchronisation]);
  158.             $tokenSynch null;
  159.             foreach ($googleAgendaList as $google) {
  160.                 if ($google->getSynchToken() !== null) {
  161.                     $tokenSynch $google->getSynchToken();
  162.                     break;
  163.                 }
  164.             }
  165.             if ($tokenSynch !== null) {
  166.                 $synchronisation->setAsynchToken($tokenSynch);
  167.                 $em->persist($synchronisation);
  168.                 $em->flush();
  169.             }
  170.         }
  171.         // 3️⃣ Prepare client and service
  172.         $accessToken $synchronisation->getAccessToken();
  173.         $client->setAccessToken($accessToken);
  174.         $service = new \Google_Service_Calendar($client);
  175.         $syncToken $synchronisation->getAsynchToken();
  176.         // 4️⃣ Call Google API safely
  177.         try {
  178.             $events $service->events->listEvents('primary', [
  179.                 'syncToken' => $syncToken,
  180.             ]);
  181.         } catch (\Google\Service\Exception $e) {
  182.             $errorDetails json_decode($e->getMessage(), true);
  183.             if (
  184.                 $errorDetails
  185.                 && isset($errorDetails['error']['errors'][0]['reason'])
  186.                 && $errorDetails['error']['errors'][0]['reason'] === 'fullSyncRequired'
  187.             ) {
  188.                 $this->syncLogger->log(
  189.                     'GoogleCalendarService',
  190.                     'Full sync required — resync triggered',
  191.                     $synchronisation,
  192.                     $e->getMessage()
  193.                 );
  194.                 $events $service->events->listEvents('primary');
  195.             } else {
  196.                 // log and rethrow
  197.                 $this->syncLogger->log(
  198.                     'GoogleCalendarService',
  199.                     'Google API error in fetchChangedEvents',
  200.                     $synchronisation,
  201.                     $e->getMessage()
  202.                 );
  203.                 throw $e;
  204.             }
  205.         }
  206.         // 5️⃣ Update sync token
  207.         $updatedSyncToken $events->getNextSyncToken();
  208.         $synchronisation->setAsynchToken($updatedSyncToken);
  209.         $em->persist($synchronisation);
  210.         $em->flush();
  211.         $this->logger->info('New synchro', ['synchroToken' => $events->getItems()]);
  212.         return $events->getItems();
  213.     } catch (\Throwable $e) {
  214.         // 6️⃣ Catch any unexpected error (DB issue, bad token, etc.)
  215.         $this->syncLogger->log(
  216.             'GoogleWebhook',
  217.             'Unexpected error during fetchChangedEvents',
  218.             null,
  219.             $e->getMessage()
  220.         );
  221.         return [];
  222.     }
  223. }
  224. public function fetchNewlyCreatedEvents($channelId$resourceId)
  225. {
  226.     // Initialize the Google Client and set the user's OAuth token.
  227.     $client = new Google_Client();
  228.     $accessToken "ya29.a0AfB_byCszSh4hIkDJDOMQQ6sLgq3X2HTS7_zIwuv_7Eur2D8x3fsQqdOGukDUyxrmYYWacK_U9ZDjflymb3aEWWZHYlYn2JQ2DZbgQwZvwPIY_RlNP-EK0vpPq4nA0EU_87pzrElgV4q6J2nsdDKKZNMAzWJRPXTJwaCgYKATYSARESFQGOcNnCzMT5hVsmwi90IUr1KWL4gA0169";
  229.     $client->setAccessToken($accessToken);
  230.     // Authenticate and set up the Calendar service for the user.
  231.     $service = new Google_Service_Calendar($client);
  232.     // Fetch the specific event associated with the provided $resourceId.
  233.     $event $service->events->get('primary'$resourceId);
  234.     // Check if the event's resource state indicates it's a new event.
  235.     if ($event->getHtmlLink() && $event->getEtag() && $event->getUpdated()) {
  236.         // This is a newly created event.
  237.         $this->logger->info('New event created', ['event' => $event->getId()]);
  238.         return $event;
  239.     } else {
  240.         // The event is not new.
  241.         $this->logger->info('Event is not new', ['event' => $event->getId()]);
  242.         return null;
  243.     }
  244. }
  245. // Inside App\Controller\Webhook\GoogleWebhookController
  246. private function formatAgendaData(GoogleAgenda $agendastring $action): array
  247. {
  248.     // The event ID from Google Calendar
  249.     $id $agenda->getEventId();
  250.     // Deserialize attendee/attachment data if needed
  251.     $invited $agenda->getAttendees() ? json_decode($agenda->getAttendees(), true) : [];
  252.     $attachement $agenda->getAttachement() ? json_decode($agenda->getAttachement(), true) : null;
  253.     $data = [
  254.         "action" => $action// <-- IMPORTANT: The specific action
  255.         "id" => $id,
  256.         "idEvent" => $id,
  257.         "eventId" => $id// Use the Google Event ID for unique identification
  258.         "centerName" => $agenda->getSummary(),
  259.         "start" => $agenda->getStart() ? $agenda->getStart()->format(\DateTimeInterface::ATOM) : null,
  260.         "end" => $agenda->getEnd() ? $agenda->getEnd()->format(\DateTimeInterface::ATOM) : null,
  261.         "startDate" => $agenda->getStart() ? $agenda->getStart()->format('Y-m-d H:i') : null,
  262.         "endDate" => $agenda->getEnd() ? $agenda->getEnd()->format('Y-m-d H:i') : null,
  263.         "meet" => $agenda->getMeetingUrl(),
  264.         "invited" => $invited['invited'] ?? [],
  265.         "attachement" => $attachement,
  266.         "location" => $agenda->getLocation(),
  267.         "organiser" => $agenda->getOrganiserEmail(),
  268.         "backgroundColor" => '#F9D04F'// Yellow Color for Google Events
  269.         "borderColor" => '#F9D04F'// Yellow Color
  270.         "comment" => $agenda->getDescription(),
  271.         "editable" => false,
  272.         "durationEditable" => true,
  273.         "resourceEditable" => true,
  274.         "cache" => false
  275.         "google" => true
  276.         "event_title" => "<b>" $agenda->getSummary() . "</b> ",
  277.     ];
  278.     
  279.     // Ensure the event data ID is unique for FullCalendar
  280.     $data['id'] = "google_" $id
  281.     return $data;
  282. }
  283. }