Skip to content

Commit 52323ed

Browse files
committed
Fix token issus
1 parent 265f36e commit 52323ed

3 files changed

Lines changed: 151 additions & 69 deletions

File tree

src/Controllers/AdminController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ private function getInvalidTokenSlugs(array $streams): array
4242
$validSlugs = $this->accessTokenRepository->getValidOrganizationSlugs();
4343
$invalidSlugs = [];
4444
foreach ($streams as $stream) {
45-
if ($stream->organization_slug && !in_array($stream->organization_slug, $validSlugs)) {
46-
$invalidSlugs[] = $stream->organization_slug;
45+
$slug = $stream->organization_slug;
46+
if ($slug && !in_array(strtolower($slug), array_map('strtolower', $validSlugs))) {
47+
$invalidSlugs[] = $slug;
4748
}
4849
}
4950
return array_unique($invalidSlugs);

src/Controllers/WidgetController.php

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,23 @@ public function widgetEventDonation(Request $request, Response $response, array
309309
throw new Exception("Aucun widget trouvé pour le Event ID fourni.");
310310
}
311311

312-
$data = $this->fetchEventDonationData($eventGuid);
312+
try {
313+
$data = $this->fetchEventDonationData($eventGuid);
314+
$currentAmount = $data['cacheData']['amount'];
315+
$event = $data['event'];
316+
} catch (Exception $e) {
317+
error_log('[WidgetEventDonation] Erreur API init pour event ' . $eventGuid . ' : ' . $e->getMessage());
318+
$event = $this->eventRepository->selectByGuid($eventGuid);
319+
$cacheData = $this->widgetRepository->selectEventDonationWidgetCacheData($event);
320+
$currentAmount = $cacheData['amount'] ?? 0;
321+
}
313322

314323
return $this->view->render($response, 'widget/donation.html.twig', [
315324
'donationGoalWidget' => $donationGoalWidget,
316-
'currentAmount' => $data['cacheData']['amount'],
317-
'goal' => $data['event']->goal,
325+
'currentAmount' => $currentAmount,
326+
'goal' => $event->goal,
318327
'event' => 1,
319-
'isTestMode' => (bool) $data['event']->is_test_mode,
328+
'isTestMode' => (bool) $event->is_test_mode,
320329
]);
321330
}
322331

@@ -362,21 +371,27 @@ public function widgetAlert(Request $request, Response $response, array $args):
362371
?? ['continuation_token' => ''];
363372

364373
if (!$this->widgetRepository->isCacheFresh($cacheData, $this->cacheTtl)) {
365-
$result = $this->apiWrapper->getAllOrders(
366-
$charityStream->organization_slug,
367-
$charityStream->form_slug,
368-
0,
369-
$cacheData['continuation_token'],
370-
);
371-
372-
if ($cacheData['continuation_token'] !== $result['continuation_token']) {
373-
$this->widgetRepository->updateAlertWidgetCacheData($charityStream->guid, [
374-
'continuation_token' => $result['continuation_token'],
375-
]);
376-
} else {
377-
$this->widgetRepository->updateAlertWidgetCacheData($charityStream->guid, [
378-
'continuation_token' => $cacheData['continuation_token'],
379-
]);
374+
try {
375+
$result = $this->apiWrapper->getAllOrders(
376+
$charityStream->organization_slug,
377+
$charityStream->form_slug,
378+
0,
379+
$cacheData['continuation_token'],
380+
);
381+
382+
if ($cacheData['continuation_token'] !== $result['continuation_token']) {
383+
$this->widgetRepository->updateAlertWidgetCacheData($charityStream->guid, [
384+
'continuation_token' => $result['continuation_token'],
385+
]);
386+
} else {
387+
$this->widgetRepository->updateAlertWidgetCacheData($charityStream->guid, [
388+
'continuation_token' => $cacheData['continuation_token'],
389+
]);
390+
}
391+
} catch (Exception $e) {
392+
// Token invalide ou erreur API : on rend le widget avec le cache existant
393+
// Le polling (fetch) réessaiera automatiquement
394+
error_log('[WidgetAlert] Erreur API init pour stream ' . $charityStream->guid . ' : ' . $e->getMessage());
380395
}
381396
}
382397
}
@@ -443,7 +458,12 @@ public function widgetAlertFetch(Request $request, Response $response, array $ar
443458

444459
return $this->jsonResponse($response, $result);
445460
} catch (Exception $e) {
446-
return $this->jsonError($response, 'Impossible de récupérer les commandes.', 500);
461+
$status = $e->getCode() === 401 ? 401 : 500;
462+
$message = $status === 401
463+
? 'Token invalide ou expiré pour ce stream. Reconnectez l\'association.'
464+
: 'Impossible de récupérer les commandes.';
465+
error_log('[WidgetAlertFetch] Erreur pour stream ' . $charityStreamId . ' : ' . $e->getMessage());
466+
return $this->jsonError($response, $message, $status);
447467
}
448468
}
449469

@@ -458,14 +478,24 @@ public function widgetDonation(Request $request, Response $response, array $args
458478
throw new Exception("Aucun widget trouvé pour le Charity Stream ID fourni.");
459479
}
460480

461-
$data = $this->fetchStreamDonationData($streamGuid);
481+
try {
482+
$data = $this->fetchStreamDonationData($streamGuid);
483+
$currentAmount = $data['result']['amount'];
484+
$stream = $data['stream'];
485+
} catch (Exception $e) {
486+
// Token invalide ou erreur API : on rend le widget avec le cache existant
487+
error_log('[WidgetDonation] Erreur API init pour stream ' . $streamGuid . ' : ' . $e->getMessage());
488+
$stream = $this->streamRepository->selectByGuid($streamGuid);
489+
$cacheData = $this->widgetRepository->selectStreamDonationWidgetCacheData($stream);
490+
$currentAmount = $cacheData['amount'] ?? 0;
491+
}
462492

463493
return $this->view->render($response, 'widget/donation.html.twig', [
464494
'donationGoalWidget' => $donationGoalWidget,
465-
'currentAmount' => $data['result']['amount'],
466-
'goal' => $data['stream']->goal,
495+
'currentAmount' => $currentAmount,
496+
'goal' => $stream->goal,
467497
'stream' => 1,
468-
'isTestMode' => (bool) $data['stream']->is_test_mode,
498+
'isTestMode' => (bool) $stream->is_test_mode,
469499
]);
470500
}
471501

@@ -485,7 +515,12 @@ public function widgetDonationFetch(Request $request, Response $response, array
485515
$data = $this->fetchStreamDonationData($charityStreamId);
486516
return $this->jsonResponse($response, $data['result']);
487517
} catch (Exception $e) {
488-
return $this->jsonError($response, 'Impossible de récupérer les commandes.', 500);
518+
$status = $e->getCode() === 401 ? 401 : 500;
519+
$message = $status === 401
520+
? 'Token invalide ou expiré pour ce stream. Reconnectez l\'association.'
521+
: 'Impossible de récupérer les commandes.';
522+
error_log('[WidgetDonationFetch] Erreur pour stream ' . $charityStreamId . ' : ' . $e->getMessage());
523+
return $this->jsonError($response, $message, $status);
489524
}
490525
}
491526

@@ -500,17 +535,29 @@ public function widgetStreamCard(Request $request, Response $response, array $ar
500535
throw new Exception("Aucun widget card trouvé pour le Charity Stream ID fourni.");
501536
}
502537

503-
$data = $this->fetchStreamCardData($streamGuid);
538+
try {
539+
$data = $this->fetchStreamCardData($streamGuid);
540+
$currentAmount = $data['amount'];
541+
$donors = $data['donors'];
542+
$stream = $data['stream'];
543+
} catch (Exception $e) {
544+
// Token invalide ou erreur API : on rend le widget avec le cache existant
545+
error_log('[WidgetCard] Erreur API init pour stream ' . $streamGuid . ' : ' . $e->getMessage());
546+
$stream = $this->streamRepository->selectByGuid($streamGuid);
547+
$cacheData = $this->widgetRepository->selectStreamCardWidgetCacheData($stream);
548+
$currentAmount = $cacheData['amount'] ?? 0;
549+
$donors = $cacheData['donors'] ?? 0;
550+
}
504551

505552
return $this->view->render($response, 'widget/card.html.twig', [
506553
'cardWidget' => $cardWidget,
507554
'cardWidgetPictureUrl' => $cardWidget->image ? $this->fileManager->getPictureUrl($cardWidget->image) : null,
508-
'currentAmount' => $data['amount'],
509-
'donorCount' => $data['donors'],
510-
'percentage' => $this->calculatePercentage($data['amount'], $data['stream']->goal),
511-
'goal' => $data['stream']->goal ?: 1,
555+
'currentAmount' => $currentAmount,
556+
'donorCount' => $donors,
557+
'percentage' => $this->calculatePercentage($currentAmount, $stream->goal),
558+
'goal' => $stream->goal ?: 1,
512559
'stream' => 1,
513-
'isTestMode' => (bool) $data['stream']->is_test_mode,
560+
'isTestMode' => (bool) $stream->is_test_mode,
514561
]);
515562
}
516563

@@ -525,7 +572,12 @@ public function widgetStreamCardFetch(Request $request, Response $response, arra
525572
$data = $this->fetchStreamCardData($charityStreamId);
526573
return $this->jsonResponse($response, ['amount' => $data['amount'], 'donors' => $data['donors']]);
527574
} catch (Exception $e) {
528-
return $this->jsonError($response, 'Impossible de récupérer les données.', 500);
575+
$status = $e->getCode() === 401 ? 401 : 500;
576+
$message = $status === 401
577+
? 'Token invalide ou expiré pour ce stream. Reconnectez l\'association.'
578+
: 'Impossible de récupérer les données.';
579+
error_log('[WidgetCardFetch] Erreur pour stream ' . $charityStreamId . ' : ' . $e->getMessage());
580+
return $this->jsonError($response, $message, $status);
529581
}
530582
}
531583

@@ -540,17 +592,28 @@ public function widgetEventCard(Request $request, Response $response, array $arg
540592
throw new Exception("Aucun widget card trouvé pour le Event ID fourni.");
541593
}
542594

543-
$data = $this->fetchEventCardData($eventGuid);
595+
try {
596+
$data = $this->fetchEventCardData($eventGuid);
597+
$currentAmount = $data['amount'];
598+
$donors = $data['donors'];
599+
$event = $data['event'];
600+
} catch (Exception $e) {
601+
error_log('[WidgetEventCard] Erreur API init pour event ' . $eventGuid . ' : ' . $e->getMessage());
602+
$event = $this->eventRepository->selectByGuid($eventGuid);
603+
$cacheData = $this->widgetRepository->selectEventCardWidgetCacheData($event);
604+
$currentAmount = $cacheData['amount'] ?? 0;
605+
$donors = $cacheData['donors'] ?? 0;
606+
}
544607

545608
return $this->view->render($response, 'widget/card.html.twig', [
546609
'cardWidget' => $cardWidget,
547610
'cardWidgetPictureUrl' => $cardWidget->image ? $this->fileManager->getPictureUrl($cardWidget->image) : null,
548-
'currentAmount' => $data['amount'],
549-
'donorCount' => $data['donors'],
550-
'percentage' => $this->calculatePercentage($data['amount'], $data['event']->goal),
551-
'goal' => $data['event']->goal ?: 1,
611+
'currentAmount' => $currentAmount,
612+
'donorCount' => $donors,
613+
'percentage' => $this->calculatePercentage($currentAmount, $event->goal),
614+
'goal' => $event->goal ?: 1,
552615
'event' => 1,
553-
'isTestMode' => (bool) $data['event']->is_test_mode,
616+
'isTestMode' => (bool) $event->is_test_mode,
554617
]);
555618
}
556619

src/Services/ApiWrapper.php

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ public function refreshToken(string $refreshToken, string $organizationSlug): ?A
151151
$obj->organization_slug = $organizationSlug;
152152
$obj->access_token_expires_at = $accessTokenExpiresAt;
153153
$obj->refresh_token_expires_at = $refreshTokenExpiresAt;
154-
$this->apiLogger->info('New organisation access token generated successfully. it will expires at ' . $obj->access_token_expires_at->format('Y-m-d H:i:s'));
155154
return $this->accessTokenRepository->update($obj);
156155
}
157156

@@ -164,26 +163,21 @@ public function getGlobalAccessToken(): AccessToken
164163
{
165164
$tokenData = $this->accessTokenRepository->selectBySlug(null);
166165

167-
$expiration_access_date = $tokenData->access_token_expires_at ?? false;
168-
169-
// si null ou expiré, on génère un nouveau token global
170-
$this->apiLogger->info('Check expiration for global access token');
171-
if ($this->isExpired($expiration_access_date) || $tokenData == null) {
172-
$this->apiLogger->debug('Global access token is invalid. Attempting to generate new one.');
166+
if ($tokenData == null || $this->isExpired($tokenData->access_token_expires_at ?? false)) {
167+
$this->apiLogger->info('Global access token absent ou expiré, génération d\'un nouveau.');
173168
$tokenData = $this->generateGlobalAccessToken();
174169
}
175-
$this->apiLogger->info('Global access token is valid. Expiry time: ' .
176-
($tokenData->access_token_expires_at instanceof DateTime ? $tokenData->access_token_expires_at->format('Y-m-d H:i:s') : $tokenData->access_token_expires_at));
177170

178171
return $tokenData;
179-
180172
}
181173

182174
/**
183175
* Récupère le token d'accès pour une organisation donnée.
176+
* Gère les accès concurrents : si le refresh échoue (token déjà utilisé par un autre process),
177+
* on re-lit la DB pour récupérer le token fraîchement rafraîchi par l'autre process.
184178
*
185-
* @param string $organization_slug
186-
* @return AccessToken|null
179+
* @param string $organizationSlug
180+
* @return AccessToken
187181
*/
188182
public function getOrganizationAccessToken(string $organizationSlug): AccessToken
189183
{
@@ -194,39 +188,56 @@ public function getOrganizationAccessToken(string $organizationSlug): AccessToke
194188
throw new Exception('Aucun token trouvé pour l\'organisation: ' . $organizationSlug);
195189
}
196190

197-
$this->apiLogger->info('Check expiration for access token of organization_slug: ' . $organizationSlug);
198-
199191
if ($this->isExpired($tokenData->refresh_token_expires_at ?? false)) {
200-
$this->apiLogger->error('Refresh token is expired for organization_slug: ' . $organizationSlug);
192+
$this->apiLogger->error('Refresh token expiré pour organization_slug: ' . $organizationSlug);
201193
throw new Exception('Invalid token data: refresh_token is expired');
202194
}
203195

204-
if ($this->isExpired($tokenData->access_token_expires_at ?? false)) {
205-
$this->apiLogger->debug('Access token for organization_slug: ' . $organizationSlug . ' is expired. Attempting to refresh token.');
206-
$tokenData = $this->refreshToken($tokenData->refresh_token, $organizationSlug);
207-
$this->apiLogger->info('Access token refreshed for organization_slug: ' . $organizationSlug . '. New expiry time: ' .
208-
($tokenData->access_token_expires_at instanceof \DateTime ? $tokenData->access_token_expires_at->format('Y-m-d H:i:s') : $tokenData->access_token_expires_at));
196+
// Access token encore valide → on l'utilise directement
197+
if (!$this->isExpired($tokenData->access_token_expires_at ?? false)) {
198+
return $tokenData;
209199
}
210200

211-
return $tokenData;
201+
// Access token expiré — re-lire la DB au cas où un autre process vient de le rafraîchir
202+
$freshToken = $this->accessTokenRepository->selectBySlug($organizationSlug);
203+
if ($freshToken && !$this->isExpired($freshToken->access_token_expires_at ?? false)) {
204+
$this->apiLogger->debug('Token déjà rafraîchi par un autre process pour ' . $organizationSlug);
205+
return $freshToken;
206+
}
207+
208+
// Toujours expiré → on rafraîchit
209+
try {
210+
$this->apiLogger->info('Rafraîchissement du token pour organization_slug: ' . $organizationSlug);
211+
$tokenData = $this->refreshToken(($freshToken ?? $tokenData)->refresh_token, $organizationSlug);
212+
$this->apiLogger->info('Token rafraîchi pour ' . $organizationSlug . '. Nouvelle expiration : ' .
213+
($tokenData->access_token_expires_at instanceof \DateTime
214+
? $tokenData->access_token_expires_at->format('Y-m-d H:i:s')
215+
: $tokenData->access_token_expires_at));
216+
return $tokenData;
217+
} catch (Exception $e) {
218+
// Le refresh a échoué — peut-être qu'un autre process a déjà utilisé le refresh token.
219+
// On re-lit la DB une dernière fois.
220+
$retryToken = $this->accessTokenRepository->selectBySlug($organizationSlug);
221+
if ($retryToken && !$this->isExpired($retryToken->access_token_expires_at ?? false)) {
222+
$this->apiLogger->info('Token récupéré après échec refresh (rafraîchi par un autre process) pour ' . $organizationSlug);
223+
return $retryToken;
224+
}
225+
226+
$this->apiLogger->error('Échec du refresh token pour ' . $organizationSlug . ' : ' . $e->getMessage());
227+
throw $e;
228+
}
212229
}
213230

214231
/**
215232
* Vérifie si une date d'expiration est dépassée par rapport à la date actuelle.
216-
*
217-
* @param [type] $expirationDate
218-
* @return boolean
219233
*/
220234
private function isExpired(string|\DateTime|false $expirationDate): bool
221235
{
222236
if (!$expirationDate) {
223237
return true;
224238
}
225239
$expiration = is_string($expirationDate) ? new \DateTime($expirationDate) : $expirationDate;
226-
$now = new \DateTime();
227-
$this->apiLogger->debug('Current time: ' . $now->format('Y-m-d H:i:s'));
228-
$this->apiLogger->debug('Token expiry time: ' . $expiration->format('Y-m-d H:i:s'));
229-
return $expiration < $now;
240+
return $expiration < new \DateTime();
230241
}
231242

232243
/**
@@ -419,7 +430,14 @@ public function getAllOrders(string $organizationSlug, string $formSlug, int $cu
419430
try {
420431
$organizationAccessToken = $this->getOrganizationAccessToken($organizationSlug);
421432
} catch (Exception $e) {
422-
throw new Exception('Votre token d\'accès pour l\'organisation ' . $organizationSlug . ' est expiré ou invalide. Veuillez vous reconnecter pour renouveler votre token.', 401, $e);
433+
$isTokenError = str_contains($e->getMessage(), 'token')
434+
|| str_contains($e->getMessage(), 'Token')
435+
|| str_contains($e->getMessage(), 'expired')
436+
|| $e->getCode() === 401;
437+
if ($isTokenError) {
438+
throw new Exception('Votre token d\'accès pour l\'organisation ' . $organizationSlug . ' est expiré ou invalide. Veuillez vous reconnecter pour renouveler votre token.', 401, $e);
439+
}
440+
throw new Exception('Erreur lors de la récupération du token pour l\'organisation ' . $organizationSlug . ' : ' . $e->getMessage(), 0, $e);
423441
}
424442

425443
if (!$organizationAccessToken || !isset($organizationAccessToken->access_token)) {

0 commit comments

Comments
 (0)