Compare commits

..

No commits in common. "03e1105a8efcc3495ac34e9b60f68173b1ea37b2" and "e75e0c4c0b70696f48d969e96e641bad0be302e1" have entirely different histories.

7 changed files with 55 additions and 103 deletions

View file

@ -15,7 +15,7 @@
"api-platform/core": "^3.1",
"beberlei/doctrineextensions": "^1.2",
"brick/math": "0.12.1 as 0.11.0",
"composer/ca-bundle": "^1.5",
"composer/ca-bundle": "^1.3",
"composer/package-versions-deprecated": "^1.11.99.5",
"doctrine/data-fixtures": "^2.0.0",
"doctrine/dbal": "^4.0.0",

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7fb73581b0074c5a79afb3ffa614ed8e",
"content-hash": "75643d42e05fce4684644d375bff2d0a",
"packages": [
{
"name": "amphp/amp",

View file

@ -156,10 +156,8 @@ class EntityURLGenerator
public function viewURL(Attachment $entity): string
{
//If the underlying file path is invalid, null gets returned, which is not allowed here.
//We still have the chance to use an external path, if it is set.
if ($entity->hasInternal() && ($url = $this->attachmentURLGenerator->getInternalViewURL($entity)) !== null) {
return $url;
if ($entity->hasInternal()) {
return $this->attachmentURLGenerator->getInternalViewURL($entity);
}
if($entity->hasExternal()) {

View file

@ -108,15 +108,12 @@ class DigikeyProvider implements InfoProviderInterface
{
$request = [
'Keywords' => $keyword,
'Limit' => 50,
'Offset' => 0,
'FilterOptionsRequest' => [
'MarketPlaceFilter' => 'ExcludeMarketPlace',
],
'RecordCount' => 50,
'RecordStartPosition' => 0,
'ExcludeMarketPlaceProducts' => 'true',
];
//$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
$response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [
$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
'json' => $request,
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
]);
@ -127,21 +124,18 @@ class DigikeyProvider implements InfoProviderInterface
$result = [];
$products = $response_array['Products'];
foreach ($products as $product) {
foreach ($product['ProductVariations'] as $variation) {
$result[] = new SearchResultDTO(
provider_key: $this->getProviderKey(),
provider_id: $variation['DigiKeyProductNumber'],
name: $product['ManufacturerProductNumber'],
description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'],
category: $this->getCategoryString($product),
manufacturer: $product['Manufacturer']['Name'] ?? null,
mpn: $product['ManufacturerProductNumber'],
preview_image_url: $product['PhotoUrl'] ?? null,
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']),
provider_url: $product['ProductUrl'],
footprint: $variation['PackageType']['Name'], //Use the footprint field, to show the user the package type (Tape & Reel, etc., as digikey has many different package types)
);
}
$result[] = new SearchResultDTO(
provider_key: $this->getProviderKey(),
provider_id: $product['DigiKeyPartNumber'],
name: $product['ManufacturerPartNumber'],
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
category: $this->getCategoryString($product),
manufacturer: $product['Manufacturer']['Value'] ?? null,
mpn: $product['ManufacturerPartNumber'],
preview_image_url: $product['PrimaryPhoto'] ?? null,
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
provider_url: $product['ProductUrl'],
);
}
return $result;
@ -149,79 +143,62 @@ class DigikeyProvider implements InfoProviderInterface
public function getDetails(string $id): PartDetailDTO
{
$response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [
$response = $this->digikeyClient->request('GET', '/Search/v3/Products/' . urlencode($id), [
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
]);
$response_array = $response->toArray();
$product = $response_array['Product'];
$product = $response->toArray();
$footprint = null;
$parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint);
$media = $this->mediaToDTOs($id);
// Get the price_breaks of the selected variation
$price_breaks = [];
foreach ($product['ProductVariations'] as $variation) {
if ($variation['DigiKeyProductNumber'] == $id) {
$price_breaks = $variation['StandardPricing'] ?? [];
break;
}
}
$media = $this->mediaToDTOs($product['MediaLinks']);
return new PartDetailDTO(
provider_key: $this->getProviderKey(),
provider_id: $id,
name: $product['ManufacturerProductNumber'],
description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'],
provider_id: $product['DigiKeyPartNumber'],
name: $product['ManufacturerPartNumber'],
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
category: $this->getCategoryString($product),
manufacturer: $product['Manufacturer']['Name'] ?? null,
mpn: $product['ManufacturerProductNumber'],
preview_image_url: $product['PhotoUrl'] ?? null,
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']),
manufacturer: $product['Manufacturer']['Value'] ?? null,
mpn: $product['ManufacturerPartNumber'],
preview_image_url: $product['PrimaryPhoto'] ?? null,
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
provider_url: $product['ProductUrl'],
footprint: $footprint,
datasheets: $media['datasheets'],
images: $media['images'],
parameters: $parameters,
vendor_infos: $this->pricingToDTOs($price_breaks, $id, $product['ProductUrl']),
vendor_infos: $this->pricingToDTOs($product['StandardPricing'] ?? [], $product['DigiKeyPartNumber'], $product['ProductUrl']),
);
}
/**
* Converts the product status from the Digikey API to the manufacturing status used in Part-DB
* @param int|null $dk_status
* @param string|null $dk_status
* @return ManufacturingStatus|null
*/
private function productStatusToManufacturingStatus(?int $dk_status): ?ManufacturingStatus
private function productStatusToManufacturingStatus(?string $dk_status): ?ManufacturingStatus
{
// The V4 can use strings to get the status, but if you have changed the PROVIDER_DIGIKEY_LANGUAGE it will not match.
// Using the Id instead which should be fixed.
//
// The API is not well documented and the ID are not there yet, so were extracted using "trial and error".
// The 'Preliminary' id was not found in several categories so I was unable to extract it. Disabled for now.
return match ($dk_status) {
null => null,
0 => ManufacturingStatus::ACTIVE,
1 => ManufacturingStatus::DISCONTINUED,
2, 4 => ManufacturingStatus::EOL,
7 => ManufacturingStatus::NRFND,
//'Preliminary' => ManufacturingStatus::ANNOUNCED,
'Active' => ManufacturingStatus::ACTIVE,
'Obsolete' => ManufacturingStatus::DISCONTINUED,
'Discontinued at Digi-Key', 'Last Time Buy' => ManufacturingStatus::EOL,
'Not For New Designs' => ManufacturingStatus::NRFND,
'Preliminary' => ManufacturingStatus::ANNOUNCED,
default => ManufacturingStatus::NOT_SET,
};
}
private function getCategoryString(array $product): string
{
$category = $product['Category']['Name'];
$sub_category = current($product['Category']['ChildCategories']);
$category = $product['Category']['Value'];
$sub_category = $product['Family']['Value'];
if ($sub_category) {
//Replace the ' - ' category separator with ' -> '
$category = $category . ' -> ' . str_replace(' - ', ' -> ', $sub_category["Name"]);
}
//Replace the ' - ' category separator with ' -> '
$sub_category = str_replace(' - ', ' -> ', $sub_category);
return $category;
return $category . ' -> ' . $sub_category;
}
/**
@ -238,18 +215,18 @@ class DigikeyProvider implements InfoProviderInterface
foreach ($parameters as $parameter) {
if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint"
$footprint_name = $parameter['ValueText'];
$footprint_name = $parameter['Value'];
}
if (in_array(trim((string) $parameter['ValueText']), ['', '-'], true)) {
if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) {
continue;
}
//If the parameter was marked as text only, then we do not try to parse it as a numerical value
if (in_array($parameter['ParameterId'], self::TEXT_ONLY_PARAMETERS, true)) {
$results[] = new ParameterDTO(name: $parameter['ParameterText'], value_text: $parameter['ValueText']);
$results[] = new ParameterDTO(name: $parameter['Parameter'], value_text: $parameter['Value']);
} else { //Otherwise try to parse it as a numerical value
$results[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterText'], $parameter['ValueText']);
$results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']);
}
}
@ -277,22 +254,16 @@ class DigikeyProvider implements InfoProviderInterface
}
/**
* @param string $id The Digikey product number, to get the media for
* @param array $media_links
* @return FileDTO[][]
* @phpstan-return array<string, FileDTO[]>
*/
private function mediaToDTOs(string $id): array
private function mediaToDTOs(array $media_links): array
{
$datasheets = [];
$images = [];
$response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/media', [
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
]);
$media_array = $response->toArray();
foreach ($media_array['MediaLinks'] as $media_link) {
foreach ($media_links as $media_link) {
$file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']);
switch ($media_link['MediaType']) {

View file

@ -29,7 +29,6 @@ use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
use Composer\CaBundle\CaBundle;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class Element14Provider implements InfoProviderInterface
@ -44,19 +43,9 @@ class Element14Provider implements InfoProviderInterface
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode'];
private readonly HttpClientInterface $element14Client;
public function __construct(HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id)
public function __construct(private readonly HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id)
{
/* We use the mozilla CA from the composer ca bundle directly, as some debian systems seems to have problems
* with the SSL.COM CA, element14 uses. See https://github.com/Part-DB/Part-DB-server/issues/866
*
* This is a workaround until the issue is resolved in debian (or never).
* As this only affects this provider, this should have no negative impact and the CA bundle is still secure.
*/
$this->element14Client = $element14Client->withOptions([
'cafile' => CaBundle::getBundledCaBundlePath(),
]);
}
public function getProviderInfo(): array

View file

@ -94,7 +94,6 @@ class MouserProvider implements InfoProviderInterface
From the startingRecord, the number of records specified will be returned up to the end of the recordset.
This is useful for paging through the complete recordset of parts matching keyword.
searchOptions string
Optional.
If not provided, the default is None.
@ -177,16 +176,11 @@ class MouserProvider implements InfoProviderInterface
throw new \RuntimeException('No part found with ID '.$id);
}
//Manually filter out the part with the correct ID
$tmp = array_filter($tmp, fn(PartDetailDTO $part) => $part->provider_id === $id);
if (count($tmp) === 0) {
throw new \RuntimeException('No part found with ID '.$id);
}
if (count($tmp) > 1) {
throw new \RuntimeException('Multiple parts found with ID '.$id);
throw new \RuntimeException('Multiple parts found with ID '.$id . ' ('.count($tmp).' found). This is basically a bug in Mousers API response. See issue #616.');
}
return reset($tmp);
return $tmp[0];
}
public function getCapabilities(): array

View file

@ -38,7 +38,7 @@
{% if not app.user.theme is defined or app.user.theme is null %}
{% if not app.user.theme is defined %}
{% set theme = global_theme %}
{% else %}
{% set theme = app.user.theme %}