mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-07-05 17:01: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",
|
"api-platform/core": "^3.1",
|
||||||
"beberlei/doctrineextensions": "^1.2",
|
"beberlei/doctrineextensions": "^1.2",
|
||||||
"brick/math": "0.12.1 as 0.11.0",
|
"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",
|
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||||
"doctrine/data-fixtures": "^2.0.0",
|
"doctrine/data-fixtures": "^2.0.0",
|
||||||
"doctrine/dbal": "^4.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",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "75643d42e05fce4684644d375bff2d0a",
|
"content-hash": "7fb73581b0074c5a79afb3ffa614ed8e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "amphp/amp",
|
"name": "amphp/amp",
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,10 @@ class EntityURLGenerator
|
||||||
|
|
||||||
public function viewURL(Attachment $entity): string
|
public function viewURL(Attachment $entity): string
|
||||||
{
|
{
|
||||||
if ($entity->hasInternal()) {
|
//If the underlying file path is invalid, null gets returned, which is not allowed here.
|
||||||
return $this->attachmentURLGenerator->getInternalViewURL($entity);
|
//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()) {
|
if($entity->hasExternal()) {
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,15 @@ class DigikeyProvider implements InfoProviderInterface
|
||||||
{
|
{
|
||||||
$request = [
|
$request = [
|
||||||
'Keywords' => $keyword,
|
'Keywords' => $keyword,
|
||||||
'RecordCount' => 50,
|
'Limit' => 50,
|
||||||
'RecordStartPosition' => 0,
|
'Offset' => 0,
|
||||||
'ExcludeMarketPlaceProducts' => 'true',
|
'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,
|
'json' => $request,
|
||||||
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||||
]);
|
]);
|
||||||
|
|
@ -124,18 +127,21 @@ class DigikeyProvider implements InfoProviderInterface
|
||||||
$result = [];
|
$result = [];
|
||||||
$products = $response_array['Products'];
|
$products = $response_array['Products'];
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
$result[] = new SearchResultDTO(
|
foreach ($product['ProductVariations'] as $variation) {
|
||||||
provider_key: $this->getProviderKey(),
|
$result[] = new SearchResultDTO(
|
||||||
provider_id: $product['DigiKeyPartNumber'],
|
provider_key: $this->getProviderKey(),
|
||||||
name: $product['ManufacturerPartNumber'],
|
provider_id: $variation['DigiKeyProductNumber'],
|
||||||
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
|
name: $product['ManufacturerProductNumber'],
|
||||||
category: $this->getCategoryString($product),
|
description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'],
|
||||||
manufacturer: $product['Manufacturer']['Value'] ?? null,
|
category: $this->getCategoryString($product),
|
||||||
mpn: $product['ManufacturerPartNumber'],
|
manufacturer: $product['Manufacturer']['Name'] ?? null,
|
||||||
preview_image_url: $product['PrimaryPhoto'] ?? null,
|
mpn: $product['ManufacturerProductNumber'],
|
||||||
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
|
preview_image_url: $product['PhotoUrl'] ?? null,
|
||||||
provider_url: $product['ProductUrl'],
|
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;
|
return $result;
|
||||||
|
|
@ -143,62 +149,79 @@ class DigikeyProvider implements InfoProviderInterface
|
||||||
|
|
||||||
public function getDetails(string $id): PartDetailDTO
|
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)
|
'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$product = $response->toArray();
|
$response_array = $response->toArray();
|
||||||
|
$product = $response_array['Product'];
|
||||||
|
|
||||||
$footprint = null;
|
$footprint = null;
|
||||||
$parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint);
|
$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(
|
return new PartDetailDTO(
|
||||||
provider_key: $this->getProviderKey(),
|
provider_key: $this->getProviderKey(),
|
||||||
provider_id: $product['DigiKeyPartNumber'],
|
provider_id: $id,
|
||||||
name: $product['ManufacturerPartNumber'],
|
name: $product['ManufacturerProductNumber'],
|
||||||
description: $product['DetailedDescription'] ?? $product['ProductDescription'],
|
description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'],
|
||||||
category: $this->getCategoryString($product),
|
category: $this->getCategoryString($product),
|
||||||
manufacturer: $product['Manufacturer']['Value'] ?? null,
|
manufacturer: $product['Manufacturer']['Name'] ?? null,
|
||||||
mpn: $product['ManufacturerPartNumber'],
|
mpn: $product['ManufacturerProductNumber'],
|
||||||
preview_image_url: $product['PrimaryPhoto'] ?? null,
|
preview_image_url: $product['PhotoUrl'] ?? null,
|
||||||
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']),
|
manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']),
|
||||||
provider_url: $product['ProductUrl'],
|
provider_url: $product['ProductUrl'],
|
||||||
footprint: $footprint,
|
footprint: $footprint,
|
||||||
datasheets: $media['datasheets'],
|
datasheets: $media['datasheets'],
|
||||||
images: $media['images'],
|
images: $media['images'],
|
||||||
parameters: $parameters,
|
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
|
* 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
|
* @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) {
|
return match ($dk_status) {
|
||||||
null => null,
|
null => null,
|
||||||
'Active' => ManufacturingStatus::ACTIVE,
|
0 => ManufacturingStatus::ACTIVE,
|
||||||
'Obsolete' => ManufacturingStatus::DISCONTINUED,
|
1 => ManufacturingStatus::DISCONTINUED,
|
||||||
'Discontinued at Digi-Key', 'Last Time Buy' => ManufacturingStatus::EOL,
|
2, 4 => ManufacturingStatus::EOL,
|
||||||
'Not For New Designs' => ManufacturingStatus::NRFND,
|
7 => ManufacturingStatus::NRFND,
|
||||||
'Preliminary' => ManufacturingStatus::ANNOUNCED,
|
//'Preliminary' => ManufacturingStatus::ANNOUNCED,
|
||||||
default => ManufacturingStatus::NOT_SET,
|
default => ManufacturingStatus::NOT_SET,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCategoryString(array $product): string
|
private function getCategoryString(array $product): string
|
||||||
{
|
{
|
||||||
$category = $product['Category']['Value'];
|
$category = $product['Category']['Name'];
|
||||||
$sub_category = $product['Family']['Value'];
|
$sub_category = current($product['Category']['ChildCategories']);
|
||||||
|
|
||||||
//Replace the ' - ' category separator with ' -> '
|
if ($sub_category) {
|
||||||
$sub_category = str_replace(' - ', ' -> ', $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) {
|
foreach ($parameters as $parameter) {
|
||||||
if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint"
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the parameter was marked as text only, then we do not try to parse it as a numerical value
|
//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)) {
|
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
|
} 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[][]
|
* @return FileDTO[][]
|
||||||
* @phpstan-return array<string, FileDTO[]>
|
* @phpstan-return array<string, FileDTO[]>
|
||||||
*/
|
*/
|
||||||
private function mediaToDTOs(array $media_links): array
|
private function mediaToDTOs(string $id): array
|
||||||
{
|
{
|
||||||
$datasheets = [];
|
$datasheets = [];
|
||||||
$images = [];
|
$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']);
|
$file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']);
|
||||||
|
|
||||||
switch ($media_link['MediaType']) {
|
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\PartDetailDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||||
|
use Composer\CaBundle\CaBundle;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
class Element14Provider implements InfoProviderInterface
|
class Element14Provider implements InfoProviderInterface
|
||||||
|
|
@ -43,9 +44,19 @@ class Element14Provider implements InfoProviderInterface
|
||||||
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
|
private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant',
|
||||||
'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode'];
|
'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
|
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.
|
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.
|
This is useful for paging through the complete recordset of parts matching keyword.
|
||||||
|
|
||||||
|
|
||||||
searchOptions string
|
searchOptions string
|
||||||
Optional.
|
Optional.
|
||||||
If not provided, the default is None.
|
If not provided, the default is None.
|
||||||
|
|
@ -176,11 +177,16 @@ class MouserProvider implements InfoProviderInterface
|
||||||
throw new \RuntimeException('No part found with ID '.$id);
|
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) {
|
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
|
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 %}
|
{% set theme = global_theme %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set theme = app.user.theme %}
|
{% set theme = app.user.theme %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue