diff --git a/src/Services/InfoProviderSystem/Providers/CanopyProvider.php b/src/Services/InfoProviderSystem/Providers/CanopyProvider.php index e6ca3961..f7683084 100644 --- a/src/Services/InfoProviderSystem/Providers/CanopyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/CanopyProvider.php @@ -30,6 +30,7 @@ use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Settings\InfoProviderSystem\BuerklinSettings; use App\Settings\InfoProviderSystem\CanopySettings; +use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\DependencyInjection\Attribute\When; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -45,7 +46,8 @@ class CanopyProvider implements InfoProviderInterface public const DISTRIBUTOR_NAME = 'Amazon'; - public function __construct(private readonly CanopySettings $settings, private readonly HttpClientInterface $httpClient) + public function __construct(private readonly CanopySettings $settings, + private readonly HttpClientInterface $httpClient, private readonly CacheItemPoolInterface $partInfoCache) { } @@ -76,6 +78,39 @@ class CanopyProvider implements InfoProviderInterface return "https://www.amazon.{$this->settings->domain}/dp/{$asin}"; } + /** + * Saves the given part to the cache. + * Everytime this function is called, the cache is overwritten. + * @param PartDetailDTO $part + * @return void + */ + private function saveToCache(PartDetailDTO $part): void + { + $key = 'canopy_part_'.$part->provider_id; + + $item = $this->partInfoCache->getItem($key); + $item->set($part); + $item->expiresAfter(3600 * 24); //Cache for 1 day + $this->partInfoCache->save($item); + } + + /** + * Retrieves a from the cache, or null if it was not cached yet. + * @param string $id + * @return PartDetailDTO|null + */ + private function getFromCache(string $id): ?PartDetailDTO + { + $key = 'canopy_part_'.$id; + + $item = $this->partInfoCache->getItem($key); + if ($item->isHit()) { + return $item->get(); + } + + return null; + } + public function searchByKeyword(string $keyword): array { $response = $this->httpClient->request('GET', self::SEARCH_API_URL, [ @@ -93,14 +128,20 @@ class CanopyProvider implements InfoProviderInterface $out = []; foreach ($results as $result) { - $out[] = new SearchResultDTO( + + + $dto = new PartDetailDTO( provider_key: $this->getProviderKey(), provider_id: $result['asin'], name: $result["title"], description: "", preview_image_url: $result["mainImageUrl"] ?? null, provider_url: $this->productPageFromASIN($result['asin']), + vendor_infos: [$this->priceToPurchaseInfo($result['price'], $result['asin'])] ); + + $out[] = $dto; + $this->saveToCache($dto); } return $out; @@ -125,11 +166,15 @@ class CanopyProvider implements InfoProviderInterface return $notes; } - private function priceToPurchaseInfo(array $price, string $asin): PurchaseInfoDTO + private function priceToPurchaseInfo(?array $price, string $asin): PurchaseInfoDTO { - $priceDto = new PriceDTO(minimum_discount_amount: 1, price: (string) $price['value'], currency_iso_code: $price['currency'], includes_tax: true); + $priceDtos = []; + if ($price !== null) { + $priceDtos[] = new PriceDTO(minimum_discount_amount: 1, price: (string) $price['value'], currency_iso_code: $price['currency'], includes_tax: true); + } - return new PurchaseInfoDTO(self::DISTRIBUTOR_NAME, order_number: $asin, prices: [$priceDto], product_url: $this->productPageFromASIN($asin)); + + return new PurchaseInfoDTO(self::DISTRIBUTOR_NAME, order_number: $asin, prices: $priceDtos, product_url: $this->productPageFromASIN($asin)); } public function getDetails(string $id): PartDetailDTO @@ -139,6 +184,11 @@ class CanopyProvider implements InfoProviderInterface throw new \InvalidArgumentException("The id must be a valid ASIN (10 characters, letters and numbers)"); } + //Use cached details if available and the settings allow it, to avoid unnecessary API requests, since the search results already contain most of the details + if(!$this->settings->alwaysGetDetails && ($cached = $this->getFromCache($id)) !== null) { + return $cached; + } + $response = $this->httpClient->request('GET', self::DETAIL_API_URL, [ 'query' => [ 'asin' => $id, diff --git a/src/Settings/InfoProviderSystem/CanopySettings.php b/src/Settings/InfoProviderSystem/CanopySettings.php index bc40bff1..f6a0494b 100644 --- a/src/Settings/InfoProviderSystem/CanopySettings.php +++ b/src/Settings/InfoProviderSystem/CanopySettings.php @@ -43,4 +43,9 @@ class CanopySettings public ?string $apiKey = null; public string $domain = "de"; + + /** + * @var bool If true, the provider will always retrieve details for a part, resulting in an additional API request + */ + public bool $alwaysGetDetails = false; }