2023-01-29 06:19:20 +01:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com ) .
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
2024-04-12 06:15:41 +02:00
* @ copyright Copyright ( c ) 2024. Invoice Ninja LLC ( https :// invoiceninja . com )
2023-01-29 06:19:20 +01:00
*
* @ license https :// www . elastic . co / licensing / elastic - license
*/
namespace App\Jobs\Util ;
use App\Libraries\MultiDB ;
use App\Models\Company ;
use App\Models\SystemLog ;
use App\Models\Webhook ;
use App\Transformers\ArraySerializer ;
use GuzzleHttp\Client ;
use GuzzleHttp\Exception\BadResponseException ;
2023-01-31 15:06:21 +01:00
use GuzzleHttp\Exception\ClientException ;
use GuzzleHttp\Exception\ServerException ;
2023-01-29 06:19:20 +01:00
use GuzzleHttp\RequestOptions ;
use Illuminate\Bus\Queueable ;
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
use Illuminate\Queue\SerializesModels ;
use League\Fractal\Manager ;
use League\Fractal\Resource\Item ;
class WebhookSingle implements ShouldQueue
{
2024-01-14 05:05:00 +01:00
use Dispatchable ;
use InteractsWithQueue ;
use Queueable ;
use SerializesModels ;
2023-01-29 06:19:20 +01:00
private $entity ;
private string $db ;
private int $subscription_id ;
public $tries = 5 ; //number of retries
public $deleteWhenMissingModels = true ;
private string $includes ;
private Company $company ;
2024-01-14 05:05:00 +01:00
2023-01-29 06:19:20 +01:00
/**
* Create a new job instance .
*
* @ param $event_id
* @ param $entity
*/
public function __construct ( $subscription_id , $entity , $db , $includes = '' )
{
$this -> entity = $entity ;
$this -> db = $db ;
$this -> includes = $includes ;
$this -> subscription_id = $subscription_id ;
}
public function backoff ()
{
2023-05-20 03:06:14 +02:00
return [ rand ( 10 , 15 ), rand ( 30 , 40 ), rand ( 60 , 79 ), rand ( 160 , 200 ), rand ( 3000 , 5000 )];
2023-01-29 06:19:20 +01:00
}
/**
* Execute the job .
*
*/
public function handle ()
{
MultiDB :: setDb ( $this -> db );
2023-08-07 07:33:40 +02:00
$subscription = Webhook :: query () -> with ( 'company' ) -> find ( $this -> subscription_id );
2024-01-14 05:05:00 +01:00
2023-02-16 02:36:09 +01:00
if ( ! $subscription ) {
2023-01-29 08:31:10 +01:00
$this -> fail ();
2023-01-31 15:06:21 +01:00
nlog ( " failed to fire event, could not find webhook ID { $this -> subscription_id } " );
2023-01-29 08:31:10 +01:00
return ;
}
2023-01-29 06:19:20 +01:00
$this -> company = $subscription -> company ;
$this -> entity -> refresh ();
// generate JSON data
$manager = new Manager ();
$manager -> setSerializer ( new ArraySerializer ());
$manager -> parseIncludes ( $this -> includes );
$class = sprintf ( 'App\\Transformers\\%sTransformer' , class_basename ( $this -> entity ));
$transformer = new $class ();
$resource = new Item ( $this -> entity , $transformer , $this -> entity -> getEntityType ());
$data = $manager -> createData ( $resource ) -> toArray ();
2024-01-14 05:05:00 +01:00
2023-03-17 08:27:26 +01:00
$headers = is_array ( $subscription -> headers ) ? $subscription -> headers : [];
2023-01-29 06:19:20 +01:00
2023-03-17 08:27:26 +01:00
$this -> postData ( $subscription , $data , $headers );
2023-01-29 06:19:20 +01:00
}
private function postData ( $subscription , $data , $headers = [])
{
$base_headers = [
'Content-Length' => strlen ( json_encode ( $data )),
'Accept' => 'application/json' ,
];
$client = new Client ([ 'headers' => array_merge ( $base_headers , $headers )]);
try {
2023-06-25 08:54:36 +02:00
$verb = $subscription -> rest_method ? ? 'post' ;
$response = $client -> { $verb }( $subscription -> target_url , [
2023-01-29 06:19:20 +01:00
RequestOptions :: JSON => $data , // or 'json' => [...]
]);
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-10 11:28:50 +01:00
[ 'message' => $response -> getHeaders (), 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_SUCCESS ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-02-16 02:36:09 +01:00
} catch ( \GuzzleHttp\Exception\ConnectException $e ) {
2023-01-29 06:19:20 +01:00
nlog ( " connection problem " );
nlog ( $e -> getCode ());
nlog ( $e -> getMessage ());
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => " Error connecting to " . $subscription -> target_url , 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-02-16 02:36:09 +01:00
} catch ( BadResponseException $e ) {
if ( $e -> getResponse () -> getStatusCode () >= 400 && $e -> getResponse () -> getStatusCode () < 500 ) {
2023-11-10 02:06:49 +01:00
/* Some 400's should never be repeated */
if ( in_array ( $e -> getResponse () -> getStatusCode (), [ 404 , 410 , 405 ])) {
$message = " There was a problem when connecting to { $subscription -> target_url } => status code " . $e -> getResponse () -> getStatusCode () . " This webhook call will be suspended until further action is taken. " ;
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $message , 'body' => $data ],
2023-11-10 02:06:49 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
)) -> handle ();
$subscription -> delete ();
$this -> fail ();
return ;
}
2023-02-17 04:47:52 +01:00
$message = " There was a problem when connecting to { $subscription -> target_url } => status code " . $e -> getResponse () -> getStatusCode ();
2024-01-14 05:05:00 +01:00
2023-01-29 06:19:20 +01:00
nlog ( $message );
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $message , 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-11-10 02:06:49 +01:00
if ( in_array ( $e -> getResponse () -> getStatusCode (), [ 400 ])) {
2023-02-17 04:47:52 +01:00
$this -> fail ();
return ;
}
2023-01-29 06:19:20 +01:00
2024-01-14 05:05:00 +01:00
$this -> release ( $this -> backoff ()[ $this -> attempts () - 1 ]);
2023-01-29 06:19:20 +01:00
}
2023-02-16 02:36:09 +01:00
if ( $e -> getResponse () -> getStatusCode () >= 500 ) {
2023-05-18 01:32:04 +02:00
nlog ( " { $subscription -> target_url } returned a 500, failing " );
2023-01-29 06:19:20 +01:00
2023-05-18 01:32:04 +02:00
$message = " There was a problem when connecting to { $subscription -> target_url } => status code " . $e -> getResponse () -> getStatusCode () . " no retry attempted. " ;
2023-01-29 06:19:20 +01:00
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $message , 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-01-29 06:19:20 +01:00
$this -> fail ();
return ;
}
2023-02-16 02:36:09 +01:00
} catch ( ServerException $e ) {
2023-01-29 06:19:20 +01:00
nlog ( " Server exception " );
$error = json_decode ( $e -> getResponse () -> getBody () -> getContents ());
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $error , 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-02-16 02:36:09 +01:00
} catch ( ClientException $e ) {
2023-01-29 06:19:20 +01:00
nlog ( " Client exception " );
$error = json_decode ( $e -> getResponse () -> getBody () -> getContents ());
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $error , 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-02-16 02:36:09 +01:00
} catch ( \Exception $e ) {
2023-01-29 06:19:20 +01:00
nlog ( " Exception handler => " . $e -> getMessage ());
nlog ( $e -> getCode ());
2023-02-17 04:47:52 +01:00
( new SystemLogger (
2024-03-09 00:21:55 +01:00
[ 'message' => $e -> getMessage (), 'body' => $data ],
2023-01-29 06:19:20 +01:00
SystemLog :: CATEGORY_WEBHOOK ,
SystemLog :: EVENT_WEBHOOK_FAILURE ,
SystemLog :: TYPE_WEBHOOK_RESPONSE ,
$this -> resolveClient (),
$this -> company ,
2023-02-17 04:47:52 +01:00
)) -> handle ();
2023-01-29 06:19:20 +01:00
2023-04-01 01:35:10 +02:00
//add some entropy to the retry
sleep ( rand ( 0 , 3 ));
2024-01-14 05:05:00 +01:00
$this -> release ( $this -> backoff ()[ $this -> attempts () - 1 ]);
2023-01-29 06:19:20 +01:00
}
}
private function resolveClient ()
2023-02-16 02:36:09 +01:00
{
2023-01-29 06:19:20 +01:00
//make sure it isn't an instance of the Client Model
2023-02-16 02:36:09 +01:00
if ( ! $this -> entity instanceof \App\Models\Client &&
! $this -> entity instanceof \App\Models\Vendor &&
! $this -> entity instanceof \App\Models\Product &&
2023-02-01 05:00:45 +01:00
! $this -> entity instanceof \App\Models\PurchaseOrder &&
$this -> entity -> client () -> exists ()) {
2023-01-29 06:19:20 +01:00
return $this -> entity -> client ;
}
2023-01-31 22:43:32 +01:00
return null ;
2023-01-29 06:19:20 +01:00
}
2023-01-30 01:12:28 +01:00
public function failed ( $exception = null )
2023-01-29 06:19:20 +01:00
{
2023-02-01 03:46:39 +01:00
config ([ 'queue.failed.driver' => null ]);
2023-01-29 06:19:20 +01:00
}
}