Переглянути джерело

Implements HelloAsso event forms retrieval

Adds functionality to retrieve HelloAsso event forms, replacing the previous organization form retrieval.
It introduces a new `EventForm` API resource and provider, and refactors the HelloAsso service to fetch and map event form data from the HelloAsso API, including fetching a form by event ID.
The changes also include updating the product configuration, adding HelloAsso slug and URL to the Event entity to link event with helloasso form.
Olivier Massot 2 місяців тому
батько
коміт
f804d9d0e2

+ 1 - 1
config/opentalent/products.yaml

@@ -38,7 +38,7 @@ parameters:
           - ConnectionRequest
           - UnlinkRequest
           - HelloAssoProfile
-          - OrganizationForm
+          - EventForm
         roles:
           - ROLE_IMPORT
           - ROLE_TAGG

+ 44 - 13
src/ApiResources/HelloAsso/EventForm.php

@@ -8,68 +8,99 @@ use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
 use App\State\Provider\HelloAsso\AuthUrlProvider;
 use App\State\Provider\HelloAsso\HelloAssoProfileProvider;
-use App\State\Provider\HelloAsso\OrganizationFormProvider;
+use App\State\Provider\HelloAsso\EventFormProvider;
 
 /**
  * Retourne les formulaires d'une organisation.
  */
 #[ApiResource(
     operations: [
+        new GetCollection(
+            uriTemplate: '/helloasso/forms',
+        ),
         new Get(
-            uriTemplate: '/helloasso/form/{eventId}',
+            uriTemplate: '/helloasso/form/{slug}',
+        ),
+        new Get(
+            uriTemplate: '/helloasso/form/by-event/{eventId}',
+            uriVariables: [
+                'eventId' => new Link(
+                    fromProperty: 'eventId',
+                    identifiers: ['eventId']
+                )
+            ]
         )
     ],
-    provider: OrganizationFormProvider::class,
+    provider: EventFormProvider::class,
     security: 'is_granted("ROLE_ORGANIZATION")'
 )]
-class OrganizationForm
+class EventForm
 {
     /**
      * Slug du formulaire
      */
-    private string $slug;
+    #[ApiProperty(identifier: true)]
+    private ?string $slug = null;
+
+    /**
+     * Id de l'Event auquel le formulaire est associé
+     * @var int | null
+     */
+    private ?int $eventId = null;
 
     /**
      * Titre du formulaire
      * @var bool
      */
-    private string $title;
+    private ?string $title = null;
 
     /**
      * Url du formulaire
      */
-    private string $widgetUrl;
+    private ?string $widgetUrl = null;
 
-    public function getSlug(): string
+    public function getSlug(): ?string
     {
         return $this->slug;
     }
 
-    public function setSlug(string $slug): self
+    public function setSlug(?string $slug): self
     {
         $this->slug = $slug;
         return $this;
     }
 
-    public function getTitle(): string
+    public function getEventId(): ?int
+    {
+        return $this->eventId;
+    }
+
+    public function setEventId(?int $eventId): self
+    {
+        $this->eventId = $eventId;
+        return $this;
+    }
+
+    public function getTitle(): ?string
     {
         return $this->title;
     }
 
-    public function setTitle(string $title): self
+    public function setTitle(?string $title): self
     {
         $this->title = $title;
         return $this;
     }
 
-    public function getWidgetUrl(): string
+    public function getWidgetUrl(): ?string
     {
         return $this->widgetUrl;
     }
 
-    public function setWidgetUrl(string $widgetUrl): self
+    public function setWidgetUrl(?string $widgetUrl): self
     {
         $this->widgetUrl = $widgetUrl;
         return $this;

+ 28 - 0
src/Entity/Booking/Event.php

@@ -117,6 +117,12 @@ class Event extends AbstractBooking
     #[Assert\Positive]
     protected ?float $priceMaxi = null;
 
+    #[ORM\Column(length: 255, nullable: true)]
+    private ?string $helloAssoSlug = null;
+
+    #[ORM\Column(length: 255, nullable: true)]
+    private ?string $helloAssoPublicUrl = null;
+
     public function __construct()
     {
         $this->eventRecur = new ArrayCollection();
@@ -474,4 +480,26 @@ class Event extends AbstractBooking
         $this->priceMaxi = $priceMaxi;
         return $this;
     }
+
+    public function getHelloAssoSlug(): ?string
+    {
+        return $this->helloAssoSlug;
+    }
+
+    public function setHelloAssoSlug(?string $helloAssoSlug): self
+    {
+        $this->helloAssoSlug = $helloAssoSlug;
+        return $this;
+    }
+
+    public function getHelloAssoPublicUrl(): ?string
+    {
+        return $this->helloAssoPublicUrl;
+    }
+
+    public function setHelloAssoPublicUrl(?string $helloAssoPublicUrl): self
+    {
+        $this->helloAssoPublicUrl = $helloAssoPublicUrl;
+        return $this;
+    }
 }

+ 102 - 30
src/Service/HelloAsso/HelloAssoService.php

@@ -6,9 +6,12 @@ namespace App\Service\HelloAsso;
 
 use App\ApiResources\HelloAsso\AuthUrl;
 use App\ApiResources\HelloAsso\HelloAssoProfile;
-use App\ApiResources\HelloAsso\OrganizationForm;
+use App\ApiResources\HelloAsso\EventForm;
+use App\Entity\Booking\Event;
 use App\Entity\HelloAsso\HelloAsso;
 use App\Entity\Organization\Organization;
+use App\Repository\Booking\EventRepository;
+use App\Repository\Organization\OrganizationRepository;
 use App\Service\Rest\ApiRequestService;
 use App\Service\Security\OAuthPkceGenerator;
 use App\Service\Utils\DatesUtils;
@@ -17,7 +20,9 @@ use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\EntityManagerInterface;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
 use Symfony\Contracts\HttpClient\HttpClientInterface;
+use Symfony\Contracts\HttpClient\ResponseInterface;
 
 /**
  * Service de connexion à HelloAsso.
@@ -29,6 +34,8 @@ class HelloAssoService extends ApiRequestService
 {
     public function __construct(
         HttpClientInterface $client,
+        private readonly OrganizationRepository $organizationRepository,
+        private readonly EventRepository $eventRepository,
         private readonly string $baseUrl,
         private readonly string $publicAppBaseUrl,
         private readonly string $helloAssoApiBaseUrl,
@@ -66,7 +73,7 @@ class HelloAssoService extends ApiRequestService
      */
     public function getAuthUrl(int $organizationId): AuthUrl
     {
-        $organization = $this->entityManager->getRepository(Organization::class)->find($organizationId);
+        $organization = $this->organizationRepository->find($organizationId);
 
         if (!$organization) {
             throw new \RuntimeException('Organization not found');
@@ -114,7 +121,7 @@ class HelloAssoService extends ApiRequestService
      */
     public function connect(int $organizationId, string $authorizationCode): HelloAsso
     {
-        $organization = $this->entityManager->getRepository(Organization::class)->find($organizationId);
+        $organization = $this->organizationRepository->find($organizationId);
         if (!$organization) {
             throw new \RuntimeException('Organization not found');
         }
@@ -154,7 +161,7 @@ class HelloAssoService extends ApiRequestService
      */
     public function makeHelloAssoProfile(int $organizationId): HelloAssoProfile
     {
-        $organization = $this->entityManager->getRepository(Organization::class)->find($organizationId);
+        $organization = $this->organizationRepository->find($organizationId);
         if (!$organization) {
             throw new \RuntimeException('Organization not found');
         }
@@ -175,7 +182,7 @@ class HelloAssoService extends ApiRequestService
 
     public function unlinkHelloAssoAccount(int $organizationId): void
     {
-        $organization = $this->entityManager->getRepository(Organization::class)->find($organizationId);
+        $organization = $this->organizationRepository->find($organizationId);
         if (!$organization) {
             throw new \RuntimeException('Organization not found');
         }
@@ -195,28 +202,20 @@ class HelloAssoService extends ApiRequestService
         $this->entityManager->flush();
     }
 
-    public function getHelloAssoForms(int $organizationId): array
-    {
-        $organization = $this->entityManager->getRepository(Organization::class)->find($organizationId);
-        if (!$organization) {
-            throw new \RuntimeException('Organization not found');
-        }
-
-        $helloAssoEntity = $organization->getHelloAsso();
-        if (!$helloAssoEntity) {
-            throw new \RuntimeException('HelloAsso entity not found');
-        }
+    public function getResource(HelloAsso $helloAssoEntity, array $routeParts): array {
         if (!$helloAssoEntity->getOrganizationSlug() || !$helloAssoEntity->getToken()) {
             throw new \RuntimeException('HelloAsso entity incomplete');
         }
 
         $helloAssoEntity = $this->refreshTokenIfNeeded($helloAssoEntity);
 
+        $url = UrlBuilder::concat(
+            $this->helloAssoApiBaseUrl,
+            array_merge(['/v5'], $routeParts),
+        );
+
         $response = $this->get(
-            UrlBuilder::concat(
-                $this->helloAssoApiBaseUrl,
-                ['/v5/organizations', $helloAssoEntity->getOrganizationSlug(), '/forms']
-            ),
+            $url,
             [],
             ['headers' =>
                 [
@@ -227,25 +226,98 @@ class HelloAssoService extends ApiRequestService
         );
 
         if ($response->getStatusCode() !== 200) {
-            throw new HttpException(500, 'Failed to fetch access token: '.$response->getContent(false));
+            throw new HttpException(
+                500,
+                'Failed to fetch resource: ['.$response->getStatusCode().'] '.$response->getContent(false)
+            );
         }
 
-        $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)['data'];
+        return json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
+    }
 
-        $forms = [];
+    public function getHelloAssoEventForms(int $organizationId): array
+    {
+        $helloAssoEntity = $this->getHelloAssoEntityFor($organizationId);
+
+        $data = $this->getResource(
+            $helloAssoEntity,
+            ['organizations', $helloAssoEntity->getOrganizationSlug(), 'forms']
+        );
 
-        foreach ($data as $formData) {
-            $form = new OrganizationForm();
-            $form->setSlug($formData['formSlug']);
-            $form->setTitle($formData['title']);
-            $form->setUrl($formData['url']);
+        $forms = [];
 
-            $forms[] = $form;
+        foreach ($data['data'] as $formData) {
+            $forms[] = $this->makeHelloAssoEventForm($formData);
         }
 
         return $forms;
     }
 
+    public function getHelloAssoEventForm(int $organizationId, string $formSlug): EventForm
+    {
+        $helloAssoEntity = $this->getHelloAssoEntityFor($organizationId);
+
+        if (!$helloAssoEntity->getOrganizationSlug() || !$helloAssoEntity->getToken()) {
+            throw new \RuntimeException('HelloAsso entity incomplete');
+        }
+
+        $formType = 'Event';
+
+        $data = $this->getResource(
+            $helloAssoEntity,
+            ['organizations', $helloAssoEntity->getOrganizationSlug(), 'forms', $formType, $formSlug, 'public']
+        );
+
+        return $this->makeHelloAssoEventForm($data);
+    }
+
+    public function getHelloAssoEventFormByEventId(int $organizationId, int $eventId): EventForm
+    {
+        $helloAssoEntity = $this->getHelloAssoEntityFor($organizationId);
+
+        if (!$helloAssoEntity->getOrganizationSlug() || !$helloAssoEntity->getToken()) {
+            throw new \RuntimeException('HelloAsso entity incomplete');
+        }
+
+        $event = $this->eventRepository->find($eventId);
+        if (!$event) {
+            throw new \RuntimeException('Event not found');
+        }
+
+        $helloAssoFormSlug = $event->getHelloAssoSlug();
+        if (!$helloAssoFormSlug) {
+            throw new \RuntimeException('HelloAsso form slug not found');
+        }
+
+        return $this->getHelloAssoEventForm($organizationId, $helloAssoFormSlug);
+    }
+
+    protected function getHelloAssoEntityFor(int $organizationId): HelloAsso
+    {
+        $organization = $this->organizationRepository->find($organizationId);
+        if (!$organization) {
+            throw new \RuntimeException('Organization not found');
+        }
+        $helloAssoEntity = $organization->getHelloAsso();
+        if (!$helloAssoEntity) {
+            throw new \RuntimeException('HelloAsso entity not found');
+        }
+        return $helloAssoEntity;
+    }
+
+    /**
+     * Construit un objet EventForm à partir des données retournées par l'api HelloAsso.
+     * @param array $formData
+     * @return EventForm
+     */
+    protected function makeHelloAssoEventForm(array $formData): EventForm {
+        $form = new EventForm();
+        $form->setSlug($formData['formSlug']);
+        $form->setTitle($formData['title']);
+        $form->setWidgetUrl($formData['widgetFullUrl']);
+
+        return $form;
+    }
 
     /**
      * Génère l'URL de rappel pour les callbacks suite à l'authentification HelloAsso
@@ -351,7 +423,7 @@ class HelloAssoService extends ApiRequestService
             return $helloAssoEntity;
         }
 
-        return $this->refreshToken($helloAssoEntity);
+        return $this->refreshTokens($helloAssoEntity);
     }
 
     public function refreshTokens(HelloAsso $helloAssoEntity): HelloAsso {

+ 5 - 3
src/State/Provider/HelloAsso/EventFormProvider.php

@@ -19,7 +19,7 @@ use Symfony\Component\HttpFoundation\Response;
 /**
  * Provider pour la ressource HelloAssoProfile.
  */
-final class OrganizationFormProvider implements ProviderInterface
+final class EventFormProvider implements ProviderInterface
 {
     public function __construct(
         private HelloAssoService $helloAssoService,
@@ -41,11 +41,13 @@ final class OrganizationFormProvider implements ProviderInterface
         $organizationId = $access->getOrganization()->getId();
 
         if ($operation instanceof GetCollection) {
-            $forms = $this->helloAssoService->getHelloAssoForms($organizationId);
+            $forms = $this->helloAssoService->getHelloAssoEventForms($organizationId);
 
             return new ArrayCollection($forms);
+        } else if (isset($uriVariables['eventId'])) {
+            return $this->helloAssoService->getHelloAssoEventFormByEventId($organizationId, $uriVariables['eventId']);
         } else {
-            $form = $this->helloAssoService->getHelloAssoEventForm($organizationId, $uriVariables['id']);
+            return $this->helloAssoService->getHelloAssoEventForm($organizationId, $uriVariables['slug']);
         }
     }
 }