mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-28 20:39:35 +00:00
Compare commits
6 commits
e75e0c4c0b
...
03e1105a8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03e1105a8e | ||
|
|
059a9683db | ||
|
|
1daf6f01f4 | ||
|
|
d3b225771c | ||
|
|
7275db27e7 | ||
|
|
49ee9131d0 |
7 changed files with 103 additions and 55 deletions
|
|
@ -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.3",
|
||||
"composer/ca-bundle": "^1.5",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/data-fixtures": "^2.0.0",
|
||||
"doctrine/dbal": "^4.0.0",
|
||||
|
|
|
|||
2
composer.lock
generated
2
composer.lock
generated
|
|
@ -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": "75643d42e05fce4684644d375bff2d0a",
|
||||
"content-hash": "7fb73581b0074c5a79afb3ffa614ed8e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
|
|
|
|||
|
|
@ -156,8 +156,10 @@ class EntityURLGenerator
|
|||
|
||||
public function viewURL(Attachment $entity): string
|
||||
{
|
||||
if ($entity->hasInternal()) {
|
||||
return $this->attachmentURLGenerator->getInternalViewURL($entity);
|
||||
//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->hasExternal()) {
|
||||
|
|
|
|||
|
|
@ -108,12 +108,15 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
{
|
||||
$request = [
|
||||
'Keywords' => $keyword,
|
||||
'RecordCount' => 50,
|
||||
'RecordStartPosition' => 0,
|
||||
'ExcludeMarketPlaceProducts' => 'true',
|
||||
'Limit' => 50,
|
||||
'Offset' => 0,
|
||||
'FilterOptionsRequest' => [
|
||||
'MarketPlaceFilter' => 'ExcludeMarketPlace',
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
|
||||
//$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [
|
||||
$response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [
|
||||
'json' => $request,
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
|
|
@ -124,18 +127,21 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
$result = [];
|
||||
$products = $response_array['Products'];
|
||||
foreach ($products as $product) {
|
||||
$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'],
|
||||
);
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
|
@ -143,62 +149,79 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$response = $this->digikeyClient->request('GET', '/Search/v3/Products/' . urlencode($id), [
|
||||
$response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [
|
||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||
]);
|
||||
|
||||
$product = $response->toArray();
|
||||
$response_array = $response->toArray();
|
||||
$product = $response_array['Product'];
|
||||
|
||||
$footprint = null;
|
||||
$parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint);
|
||||
$media = $this->mediaToDTOs($product['MediaLinks']);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $product['DigiKeyPartNumber'],
|
||||
name: $product['ManufacturerPartNumber'],
|
||||
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
|
||||
provider_id: $id,
|
||||
name: $product['ManufacturerProductNumber'],
|
||||
description: $product['Description']['DetailedDescription'] ?? $product['Description']['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']),
|
||||
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: $footprint,
|
||||
datasheets: $media['datasheets'],
|
||||
images: $media['images'],
|
||||
parameters: $parameters,
|
||||
vendor_infos: $this->pricingToDTOs($product['StandardPricing'] ?? [], $product['DigiKeyPartNumber'], $product['ProductUrl']),
|
||||
vendor_infos: $this->pricingToDTOs($price_breaks, $id, $product['ProductUrl']),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the product status from the Digikey API to the manufacturing status used in Part-DB
|
||||
* @param string|null $dk_status
|
||||
* @param int|null $dk_status
|
||||
* @return ManufacturingStatus|null
|
||||
*/
|
||||
private function productStatusToManufacturingStatus(?string $dk_status): ?ManufacturingStatus
|
||||
private function productStatusToManufacturingStatus(?int $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,
|
||||
'Active' => ManufacturingStatus::ACTIVE,
|
||||
'Obsolete' => ManufacturingStatus::DISCONTINUED,
|
||||
'Discontinued at Digi-Key', 'Last Time Buy' => ManufacturingStatus::EOL,
|
||||
'Not For New Designs' => ManufacturingStatus::NRFND,
|
||||
'Preliminary' => ManufacturingStatus::ANNOUNCED,
|
||||
0 => ManufacturingStatus::ACTIVE,
|
||||
1 => ManufacturingStatus::DISCONTINUED,
|
||||
2, 4 => ManufacturingStatus::EOL,
|
||||
7 => ManufacturingStatus::NRFND,
|
||||
//'Preliminary' => ManufacturingStatus::ANNOUNCED,
|
||||
default => ManufacturingStatus::NOT_SET,
|
||||
};
|
||||
}
|
||||
|
||||
private function getCategoryString(array $product): string
|
||||
{
|
||||
$category = $product['Category']['Value'];
|
||||
$sub_category = $product['Family']['Value'];
|
||||
$category = $product['Category']['Name'];
|
||||
$sub_category = current($product['Category']['ChildCategories']);
|
||||
|
||||
//Replace the ' - ' category separator with ' -> '
|
||||
$sub_category = str_replace(' - ', ' -> ', $sub_category);
|
||||
if ($sub_category) {
|
||||
//Replace the ' - ' category separator with ' -> '
|
||||
$category = $category . ' -> ' . str_replace(' - ', ' -> ', $sub_category["Name"]);
|
||||
}
|
||||
|
||||
return $category . ' -> ' . $sub_category;
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,18 +238,18 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
|
||||
foreach ($parameters as $parameter) {
|
||||
if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint"
|
||||
$footprint_name = $parameter['Value'];
|
||||
$footprint_name = $parameter['ValueText'];
|
||||
}
|
||||
|
||||
if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) {
|
||||
if (in_array(trim((string) $parameter['ValueText']), ['', '-'], 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['Parameter'], value_text: $parameter['Value']);
|
||||
$results[] = new ParameterDTO(name: $parameter['ParameterText'], value_text: $parameter['ValueText']);
|
||||
} else { //Otherwise try to parse it as a numerical value
|
||||
$results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']);
|
||||
$results[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterText'], $parameter['ValueText']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,16 +277,22 @@ class DigikeyProvider implements InfoProviderInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array $media_links
|
||||
* @param string $id The Digikey product number, to get the media for
|
||||
* @return FileDTO[][]
|
||||
* @phpstan-return array<string, FileDTO[]>
|
||||
*/
|
||||
private function mediaToDTOs(array $media_links): array
|
||||
private function mediaToDTOs(string $id): array
|
||||
{
|
||||
$datasheets = [];
|
||||
$images = [];
|
||||
|
||||
foreach ($media_links as $media_link) {
|
||||
$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) {
|
||||
$file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']);
|
||||
|
||||
switch ($media_link['MediaType']) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ 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
|
||||
|
|
@ -43,9 +44,19 @@ class Element14Provider implements InfoProviderInterface
|
|||
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
|
||||
'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode'];
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id)
|
||||
{
|
||||
private readonly HttpClientInterface $element14Client;
|
||||
|
||||
public function __construct(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
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ 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.
|
||||
|
|
@ -176,11 +177,16 @@ 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 . ' ('.count($tmp).' found). This is basically a bug in Mousers API response. See issue #616.');
|
||||
throw new \RuntimeException('Multiple parts found with ID '.$id);
|
||||
}
|
||||
|
||||
return $tmp[0];
|
||||
return reset($tmp);
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
|
||||
|
||||
{% if not app.user.theme is defined %}
|
||||
{% if not app.user.theme is defined or app.user.theme is null %}
|
||||
{% set theme = global_theme %}
|
||||
{% else %}
|
||||
{% set theme = app.user.theme %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue