Browse Source

add the RefreshHelloassoTokens cron job

Olivier Massot 2 tháng trước cách đây
mục cha
commit
d41eb24dcf

+ 2 - 1
src/Entity/HelloAsso/HelloAsso.php

@@ -4,6 +4,7 @@ namespace App\Entity\HelloAsso;
 
 use App\Entity\Organization\Organization;
 use App\Entity\Traits\CreatedOnAndByTrait;
+use App\Repository\HelloAsso\HelloAssoRepository;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -11,7 +12,7 @@ use Doctrine\ORM\Mapping as ORM;
  *
  * @see https://dev.helloasso.com/docs/mire-authorisation
  */
-#[ORM\Entity]
+#[ORM\Entity(repositoryClass: HelloAssoRepository::class)]
 #[ORM\Table]
 class HelloAsso
 {

+ 31 - 0
src/Repository/HelloAsso/HelloAssoRepository.php

@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Repository\HelloAsso;
+
+use App\Entity\Education\Cycle;
+use App\Entity\HelloAsso\HelloAsso;
+use App\Service\Utils\DatesUtils;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+class HelloAssoRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, HelloAsso::class);
+    }
+
+    public function findOldRefreshTokens(int $age): array
+    {
+        $dateLimite = DatesUtils::new('-'.$age.' days');
+
+        return $this->createQueryBuilder('h')
+            ->where('h.refreshTokenCreatedAt <= :date')
+            ->setParameter('date', $dateLimite)
+            ->orderBy('h.refreshTokenCreatedAt', 'ASC')
+            ->getQuery()
+            ->getResult();
+    }
+}

+ 128 - 0
src/Service/Cron/Job/RefreshHelloassoTokens.php

@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Cron\Job;
+
+use App\Entity\Core\File;
+use App\Entity\HelloAsso\HelloAsso;
+use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileStatusEnum;
+use App\Repository\Core\FileRepository;
+use App\Service\Cron\BaseCronJob;
+use App\Service\File\Storage\LocalStorage;
+use App\Service\HelloAsso\HelloAssoService;
+use App\Service\Utils\DatesUtils;
+use Doctrine\DBAL\Connection;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\QueryBuilder;
+use JetBrains\PhpStorm\Pure;
+
+/**
+ * Cronjob that refresh the tokens of the HelloAsso accounts.
+ *
+ * >>> ot:cron run refresh-helloasso-tokens --preview
+ * >>> ot:cron run refresh-helloasso-tokens
+ *
+ * /!\ Attention aux limites d'appels à l'API HelloAsso : https://dev.helloasso.com/docs/limitation-api
+ */
+class RefreshHelloassoTokens extends BaseCronJob
+{
+    /**
+     * Age (en jours) à partir duquel on rafraichit les tokens.
+     */
+    private const REFRESH_OLDER_THAN = 24;
+
+    /**
+     * Limite à poser au nombre d'appels API par heure.
+     */
+    private const HOURLY_LIMIT = 50;
+
+    #[Pure]
+    public function __construct(
+        private EntityManagerInterface $entityManager,
+        private HelloAssoService $helloAssoService,
+    ) {
+        parent::__construct();
+    }
+
+    /**
+     * Preview the result of the execution, without actually deleting anything.
+     *
+     * @throws \Exception
+     */
+    public function preview(): void
+    {
+        $helloAssoEntities = $this->getHelloassoAccounts();
+
+        if (count($helloAssoEntities) === 0) {
+            $this->ui->print('No tokens to refresh');
+            return;
+        }
+
+        $this->ui->print("Tokens to refresh :");
+
+        foreach ($helloAssoEntities as $helloAssoEntity) {
+            $this->ui->print(
+                ' * Organization '.$helloAssoEntity->getOrganization()->getId().' : '.$helloAssoEntity->getRefreshTokenCreatedAt()->format('Y-m-d H:i:s')
+            );
+        }
+    }
+
+    /**
+     * Proceed to the deletion of the files and the purge of the DB.
+     *
+     * @throws \Exception
+     */
+    public function execute(): void
+    {
+        $helloAssoEntities = $this->getHelloassoAccounts();
+        $amount = count($helloAssoEntities);
+
+        if ($amount === 0) {
+            $this->ui->print('No tokens to refresh');
+            return;
+        }
+
+        $sleepingTime = $this->sleepingTime($amount);
+
+        $this->ui->print($amount . ' tokens to refresh');
+
+        foreach ($helloAssoEntities as $helloAssoEntity) {
+            $this->ui->print(
+                ' * Refresh token for organization '.$helloAssoEntity->getOrganization()->getId().' : '.$helloAssoEntity->getRefreshTokenCreatedAt()->format('Y-m-d H:i:s')
+            );
+
+            $this->helloAssoService->refreshTokens($helloAssoEntity);
+
+            sleep($sleepingTime);
+        }
+    }
+
+    public function getHelloassoAccounts(): array
+    {
+        $helloassoRepository = $this->entityManager->getRepository(HelloAsso::class);
+
+        return $helloassoRepository->findOldRefreshTokens(self::REFRESH_OLDER_THAN);
+    }
+
+    protected function sleepingTime($amount): int
+    {
+        if ($amount >= 50) {
+            // Une attente de 80s entre chaque appel donne un nombre d'appels max de 45 par heure (max 50 par heure).
+            return 80;
+        }
+
+        if ($amount >= 20) {
+            // Une attente de 35s entre chaque appel donne un nombre d'appels max de 17 par 10 minutes (max 20 par 10 minutes).
+            return 35;
+        }
+
+        if ($amount >= 10) {
+            // Une attente de 2s entre chaque appel donne un nombre d'appels max de 5 par 10s (max 10 par 10s).
+            return 2;
+        }
+
+        return 1;
+    }
+}

+ 9 - 1
src/Service/HelloAsso/HelloAssoService.php

@@ -340,7 +340,7 @@ class HelloAssoService extends ApiRequestService
         }
     }
 
-    protected function refreshTokenIfNeeded(HelloAsso $helloAssoEntity): HelloAsso {
+    public function refreshTokenIfNeeded(HelloAsso $helloAssoEntity): HelloAsso {
         if (!$helloAssoEntity->getRefreshToken() || !$helloAssoEntity->getRefreshTokenCreatedAt()) {
             throw new \RuntimeException('HelloAsso entity incomplete');
         }
@@ -351,6 +351,14 @@ class HelloAssoService extends ApiRequestService
             return $helloAssoEntity;
         }
 
+        return $this->refreshToken($helloAssoEntity);
+    }
+
+    public function refreshTokens(HelloAsso $helloAssoEntity): HelloAsso {
+        if (!$helloAssoEntity->getRefreshToken() || !$helloAssoEntity->getRefreshTokenCreatedAt()) {
+            throw new \RuntimeException('HelloAsso entity incomplete');
+        }
+
         $body = [
             'grant_type' => 'refresh_token',
             'refresh_token' => $helloAssoEntity->getRefreshToken(),