Commit ccf136c0 authored by Patrick Barroca's avatar Patrick Barroca 😁
Browse files

dev #152400 : remote providers refactoring + tests maintenance

parent c3b9aec9
Pipeline #16816 passed with stage
in 35 seconds
......@@ -51,26 +51,6 @@ class FileSystem {
}
public function download($media) {
$url = $media->getUrl();
$path = $media->getFullsize();
if (!$url || !$path)
return;
$request = (new RequestFactory())->createRequest('GET', $url);
$request = $this->_injectProviderHeaders($request, $media);
$client = $this->newFileGetContents();
$response = $client->sendRequest($request);
if (StatusCodeInterface::STATUS_OK != ($status = $response->getStatusCode()))
return;
$this->writeMediaFrom($media, $response);
}
public function writeMediaFrom($media, $response) {
if (!$path = $media->getFullsize())
return false;
......@@ -97,12 +77,4 @@ class FileSystem {
if (false === $this->fileExists($directory))
$this->mkdir($directory, 0755, true);
}
protected function _injectProviderHeaders($request, $media) {
foreach($media->getProviderHeaders() as $key => $value)
$request = $request->withHeader($key, $value);
return $request;
}
}
......@@ -21,20 +21,7 @@ class Media extends ModelAbstract {
$_default_attribute_values = ['fullsize' => '',
'created_at' => '',
'updated_at' => ''],
$_provider_headers = [];
public function getProviderHeaders() {
return $this->_provider_headers;
}
public function setProviderHeaders($headers) {
$this->_provider_headers = $headers;
return $this;
}
'updated_at' => ''];
public function getJsonAttributes() {
$attribs = array_intersect_key($this->getRawAttributes(),
......
......@@ -8,9 +8,10 @@ use \Pellicule\Models\Record;
use \Pellicule\FileSystem;
class ElectreNg extends Provider {
class ElectreNg extends RemoteProvider {
const
ID = 'electre_ng',
TOKEN_URL = 'https://login.electre-ng.com/auth/realms/electre/protocol/openid-connect/token',
API_URL = 'https://api.electre-ng.com/notices/ean/{ean}',
CLIENT_ID = 'api-client',
......@@ -18,63 +19,52 @@ class ElectreNg extends Provider {
ID_KEY = 'client_id',
SECRET_KEY = 'client_secret';
protected
$_id,
$_secret;
public static function handles(string $provider_id) : bool {
// handle previous electre provider code
return in_array($provider_id, [static::ID, 'electre_rest_2']);
}
public function __construct($credentials) {
public function __construct(array $credentials, array $args) {
parent::__construct($credentials, $args);
$this->_id = $credentials[static::ID_KEY] ?? '';
$this->_secret = $credentials[static::SECRET_KEY] ?? '';
}
public function fetchRecord() {
if (!$this->_id || !$this->_secret)
return $this->_newError(StatusCodeInterface::STATUS_UNAUTHORIZED, 'invalid_credentials');
public function providerName() : string {
return 'Electre';
}
protected function _isValidCredentials() : bool {
return $this->_id && $this->_secret;
}
if (!$identifier = $this->_getIsbnOrEan())
return $this->_newError(StatusCodeInterface::STATUS_BAD_REQUEST, 'missing_param');
protected function _coverUrl(string $identifier) {
$token_response = $this->_getToken();
if ($token_response instanceof FetchRecordResult)
return $token_response;
$cover_url = $this->_getCoverUrl($identifier, $token_response);
if ($cover_url instanceof FetchRecordResult)
return $cover_url;
$api_url = str_replace('{ean}', $identifier, static::API_URL);
$headers = ['Authorization' => (($token_response->token_type ?? 'Bearer')
. ' ' . $token_response->access_token)];
$response = $this->_withHttpDo(fn($http) => $http->get($cover_url));
$response = $this->_withHttpDo(fn($http) => $http->get($api_url, $headers));
if ($response instanceof FetchRecordResult)
return $response;
$media = Media::newInstance(['provider' => $this->providerName(),
'type' => Media::TYPE_COVER,
'url' => $cover_url,
'fullsize_from_identifier' => $identifier,
]);
if (!(new FileSystem)->writeMediaFrom($media, $response))
return $this->_newError(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR,
'error_writing_cover');
$record = Record::newInstance(array_merge($this->_search_args, ['media' => [$media]]));
return $record->save()
? (new FetchRecordResult)->beSuccess($record)
: $this->_newError(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR,
'error_saving_record');
}
public function providerName() {
return 'Electre';
return (($json_response = $this->_parseJsonResponse($response))
&& ($records = $json_response->notices ?? [])
&& ($cover_url = $records[0]->imageCouverture ?? false))
? $cover_url
: $this->_providerError();
}
......@@ -92,39 +82,6 @@ class ElectreNg extends Provider {
return (($json_response = $this->_parseJsonResponse($response))
&& isset($json_response->access_token))
? $json_response
: $this->_newError(StatusCodeInterface::STATUS_BAD_GATEWAY, 'provider_error');
}
protected function _getCoverUrl($ean, $token_response) {
$api_url = str_replace('{ean}', $ean, static::API_URL);
$headers = ['Authorization' => (($token_response->token_type ?? 'Bearer')
. ' ' . $token_response->access_token)];
$response = $this->_withHttpDo(fn($http) => $http->get($api_url, $headers));
if ($response instanceof FetchRecordResult)
return $response;
return (($json_response = $this->_parseJsonResponse($response))
&& ($records = $json_response->notices ?? [])
&& ($cover_url = $records[0]->imageCouverture ?? false))
? $cover_url
: $this->_newError(StatusCodeInterface::STATUS_BAD_GATEWAY, 'provider_error');
}
protected function _withHttpDo($callback) {
try {
$response = $callback(static::newHttpClient());
} catch(\Exception $e) {
return $this->_newError(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT,
'no_answer_from_gateway');
}
return ($response->getStatusCode() === StatusCodeInterface::STATUS_OK
&& ($body = $response->getBody())
&& 0 < $body->getSize())
? $response
: $this->_newError(StatusCodeInterface::STATUS_BAD_GATEWAY, 'provider_error');
: $this->_providerError();
}
}
<?php
namespace Pellicule\Providers;
use \Fig\Http\Message\StatusCodeInterface;
class Error extends Provider {
protected $_code, $_label;
const
NO_VALID_PARAMS = 'no_valid_params_provided',
NOT_FOUND = 'record_not_found',
NO_CREDENTIALS = 'no_valid_authentication_information_provided',
INVALID_CREDENTIALS = 'invalid_credentials',
MISSING_PARAM = 'missing_param',
CANNOT_WRITE_COVER = 'error_writing_cover',
CANNOT_SAVE_RECORD = 'error_saving_record',
PROVIDER_ERROR = 'provider_error',
NO_ANSWER = 'no_answer_from_gateway';
protected
$_code,
$_label;
public static function noValidParams() {
return new static(StatusCodeInterface::STATUS_BAD_REQUEST, static::NO_VALID_PARAMS);
}
public static function notFound() {
return new static(StatusCodeInterface::STATUS_NOT_FOUND, static::NOT_FOUND);
}
public static function noCredentials() {
return new static(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED,
static::NO_CREDENTIALS);
}
public static function invalidCredentials() {
return new static(StatusCodeInterface::STATUS_UNAUTHORIZED, static::INVALID_CREDENTIALS);
}
public static function missingParam() {
return new static(StatusCodeInterface::STATUS_BAD_REQUEST, static::MISSING_PARAM);
}
public static function cannotWriteCover() {
return new static(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR,
static::CANNOT_WRITE_COVER);
}
public static function cannotSaveRecord() {
return new static(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR,
static::CANNOT_SAVE_RECORD);
}
public static function providerError() {
return new static(StatusCodeInterface::STATUS_BAD_GATEWAY, static::PROVIDER_ERROR);
}
public static function noAnswerFromGateway() {
return new static(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT, static::NO_ANSWER);
}
public function __construct($code, $label) {
public function __construct(int $code, string $label) {
$this->_code = $code;
$this->_label = $label;
}
public function fetchRecord() {
return (new FetchRecordResult())->beError($this->_code, $this->_label);
public function fetchRecord() : FetchRecordResult {
return (new FetchRecordResult)->beError($this->_code, $this->_label);
}
}
......@@ -15,9 +15,8 @@ class FetchRecordResult {
}
public function beSuccess($record){
public function beSuccess($record) {
$this->_record = $record;
$this->_record->save();
$this->_status_code = 200;
return $this;
}
......@@ -30,7 +29,7 @@ class FetchRecordResult {
}
public function getStatusCode(){
public function getStatusCode() {
return $this->_status_code;
}
}
\ No newline at end of file
......@@ -10,7 +10,7 @@ class Local extends Provider {
}
public function fetchRecord() {
return (new FetchRecordResult())->beSuccess($this->_record);
public function fetchRecord() : FetchRecordResult {
return (new FetchRecordResult)->beSuccess($this->_record);
}
}
<?php
namespace Pellicule\Providers;
use \Pellicule\Models\Record;
use \Fig\Http\Message\StatusCodeInterface;
use \Pellicule\Models\Record;
use \Pellicule\Models\Media;
use \Pellicule\FileSystem;
class Orb extends Provider {
class Orb extends RemoteProvider {
const
END_POINT = 'https://api.base-orb.fr',
ID = 'orb';
ID = 'orb',
API_URL = 'https://api.base-orb.fr/v1/products?sort=ean_asc&eans={ean}',
USERNAME_KEY = 'username',
SECRET_KEY = 'secret';
protected
$_username,
$_secret;
public function __construct($data = []) {
if (!array_key_exists('username',$data))
return $this;
$this->_username = $data['username'];
if (!array_key_exists('secret',$data))
return $this;
$this->_secret = $data['secret'];
public function __construct(array $credentials, array $args) {
parent::__construct($credentials, $args);
$this->_username = $credentials[static::USERNAME_KEY] ?? '';
$this->_secret = $credentials[static::SECRET_KEY] ?? '';
}
public function providerName() {
public function providerName() : string {
return 'Orb';
}
public function fetchRecord() {
$browser = static::newHttpClient();
$ean = $this->_getIsbnOrEan();
try{
$response = $browser->get( static::END_POINT . '/v1/products?sort=ean_asc&eans=' . $ean,
['Authorization' => 'Basic '. base64_encode($this->_username. ':'. $this->_secret),
'Accept-Encoding' => 'deflate, gzip'
]);
} catch(\Exception $exception) {
return $this->_newError(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT,
'no_answer_from_gateway');
}
if ($response->getStatusCode() != StatusCodeInterface::STATUS_OK)
return $this->_newError($response->getStatusCode(),
$response->getReasonPhrase());
$response->getBody()->rewind();
$json_content = json_decode(zlib_decode($response->getBody()->getContents()), TRUE);
if (! $json_content)
return $this->_newError(StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY,
'cannot_parse_response');
if (!isset($json_content['data']))
return $this->_newError(StatusCodeInterface::STATUS_NOT_ACCEPTABLE,
'no_expected_attribute_in_data');
protected function _isValidCredentials() : bool {
return $this->_username && $this->_secret;
}
$images = $json_content['data'][0]['images'];
$medialist =[];
$imagetype_translate = ['front' => 'cover',
'back '=> 'back_cover',
'additional' => 'extract'];
foreach (array_keys($images) as $image_type) {
$medialist[] = (new \Pellicule\Models\Media())
->setProvider($this->providerName())
->setType($imagetype_translate[$image_type])
->setUrl($images[$image_type]['original']['src'])
->setFullsizeFromIdentifier($this->_getIsbnOrEan());
}
protected function _coverUrl(string $identifier) {
$api_url = str_replace('{ean}', $identifier, static::API_URL);
$headers = ['Authorization' => 'Basic '. base64_encode($this->_username. ':'. $this->_secret),
'Accept-Encoding' => 'deflate, gzip'];
$record = (new Record())
->updateAttributes($this->_search_args)
->setMedia($medialist);
$response = $this->_withHttpDo(fn($http) => $http->get($api_url, $headers));
if ($response instanceof FetchRecordResult)
return $response;
return (new FetchRecordResult())->beSuccess($record);
return (($json_content = json_decode(zlib_decode((string)$response->getBody()), true))
&& ($images = $json_content['data'][0]['images'] ?? [])
&& ($cover_url = $images['front']['original']['src'] ?? false))
? $cover_url
: $this->_providerError();
}
}
......@@ -13,32 +13,21 @@ abstract class Provider {
const ID = '';
protected
$_search_args = [],
$_headers = [];
public static function handles(string $provider_id) : bool {
return static::ID === $provider_id;
}
public static function newProvider($request, $args) {
return static::_realNewProvider($request, $args)
->setSearchArgs($args);
return static::_realNewProvider($request, $args);
}
protected static function _realNewProvider($request, $args) {
if (!$args)
return new Error(StatusCodeInterface::STATUS_BAD_REQUEST,
'no valid params provided');
return Error::noValidParams();
if ($record = Record::findFirstBy($args))
return new Local($record);
if (!$request->hasHeader('Authorization'))
return new Error(StatusCodeInterface::STATUS_NOT_FOUND, 'record not found');
return Error::notFound();
$credentials = array_filter((new Collection($request->getHeader('Authorization')))
->select(function($header)
......@@ -52,65 +41,18 @@ abstract class Provider {
->getArrayCopy());
if (!$credentials)
return new Error(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED,
'no_valid_authentication_information_provided');
return Error::noCredentials();
return ($provider = (new RemoteProviders)->providerFor(array_shift($credentials)))
return ($provider = (new RemoteProviders)->providerFor(array_shift($credentials), $args))
? $provider
: new Error(StatusCodeInterface::STATUS_BAD_REQUEST,
'no valid params provided');
}
protected function _parseJsonResponse($response){
$response->getBody()->rewind();
return json_decode($response->getBody()->getContents());
}
protected function _getIsbnOrEan() {
if (isset($this->_search_args['ean']))
return $this->_search_args['ean'];
if (isset($this->_search_args['isbn']))
return $this->_search_args['isbn'];
: Error::noValidParams();
}
public function providerName() {
public function providerName() : string {
return static::class;
}
public function setSearchArgs($args) {
if (isset($args['isbn']) || isset($args['ean']))
$this->_search_args = $args;
return $this;
}
public function setHeaders($value) {
$this->_headers = $value;
}
public function getHeaders() {
return $this->_headers;
}
/**
* @param int $status StatusCodeInterface::STATUS_* constant
* @param string $code
*
* @return FetchRecordResult
*/
public function _newError($status, $code) {
return (new FetchRecordResult)->beError($status, $code);
}
/** @return FetchRecordResult */
abstract public function fetchRecord();
abstract public function fetchRecord() : FetchRecordResult ;
}
\ No newline at end of file
<?php
namespace Pellicule\Providers;
use \Fig\Http\Message\StatusCodeInterface;
use \Pellicule\Models\Record;
use \Pellicule\Models\Media;
use \Pellicule\FileSystem;
abstract class RemoteProvider extends Provider {
protected $_search_args = [];
public static function handles(string $provider_id) : bool {
return static::ID === $provider_id;
}
public function __construct(array $credentials, array $args) {
if (isset($args['isbn']) || isset($args['ean']))
$this->_search_args = $args;
}
public function fetchRecord() : FetchRecordResult {
if (!$this->_isValidCredentials())
return $this->_invalidCredentials();
if (!$identifier = $this->_getIsbnOrEan())
return $this->_missingParam();
$cover_url = $this->_coverUrl($identifier);
if ($cover_url instanceof FetchRecordResult)
return $cover_url;
$response = $this->_withHttpDo(fn($http) => $http->get($cover_url));
if ($response instanceof FetchRecordResult)
return $response;
$media = Media::newInstance(['provider' => $this->providerName(),
'type' => Media::TYPE_COVER,
'url' => $cover_url,
'fullsize_from_identifier' => $identifier,
]);
if (!(new FileSystem)->writeMediaFrom($media, $response))
return $this->_cannotWriteCover();
$record = Record::newInstance(array_merge($this->_search_args, ['media' => [$media]]));
return $record->save()
? (new FetchRecordResult)->beSuccess($record)
: $this->_cannotSaveRecord();
}
abstract protected function _isValidCredentials() : bool;
abstract protected function _coverUrl(string $identifier);
protected function _getIsbnOrEan() {
if (isset($this->_search_args['ean']))
return $this->_search_args['ean'];
return $this->_search_args['isbn'] ?? null;
}