Begin refactoring WebhookRunner into WebhookService

The plan is to enable multiple webhooks to be called for different
events which will allow integration with other services without having
to poll for changes.
This commit is contained in:
Mark Garratt 2025-01-15 20:36:40 +00:00
parent c05181aa53
commit 6b833d178d
9 changed files with 165 additions and 111 deletions

View File

@ -18,6 +18,7 @@ use Grocy\Services\StockService;
use Grocy\Services\TasksService;
use Grocy\Services\UserfieldsService;
use Grocy\Services\UsersService;
use Grocy\Services\WebhookService;
use DI\Container;
class BaseController
@ -116,6 +117,11 @@ class BaseController
return UsersService::getInstance();
}
protected function getWebhookService()
{
return WebhookService::getInstance();
}
protected function render($response, $viewName, $data = [])
{
$container = $this->AppContainer;

View File

@ -3,8 +3,8 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
use Grocy\Services\WebhookService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -71,15 +71,12 @@ class BatteriesApiController extends BaseApiController
{
$battery = $this->getDatabase()->batteries()->where('id', $args['batteryId'])->fetch();
$webhookData = array_merge([
$webhookData = [
'battery' => $battery->name,
'grocycode' => (string)(new Grocycode(Grocycode::BATTERY, $args['batteryId'])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_LABEL_PRINTER_RUN_SERVER)
{
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
$this->getWebhookService()->run(WebhookService::EVENT_BATTERY_PRINT_LABEL, $webhookData);
return $this->ApiResponse($response, $webhookData);
}

View File

@ -3,8 +3,8 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
use Grocy\Services\WebhookService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -122,15 +122,12 @@ class ChoresApiController extends BaseApiController
{
$chore = $this->getDatabase()->chores()->where('id', $args['choreId'])->fetch();
$webhookData = array_merge([
$webhookData = [
'chore' => $chore->name,
'grocycode' => (string)(new Grocycode(Grocycode::CHORE, $args['choreId'])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_LABEL_PRINTER_RUN_SERVER)
{
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
$this->getWebhookService()->run(WebhookService::EVENT_CHORE_PRINT_LABEL, $webhookData);
return $this->ApiResponse($response, $webhookData);
}

View File

@ -3,8 +3,8 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Helpers\WebhookRunner;
use Grocy\Helpers\Grocycode;
use Grocy\Services\WebhookService;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -87,15 +87,12 @@ class RecipesApiController extends BaseApiController
{
$recipe = $this->getDatabase()->recipes()->where('id', $args['recipeId'])->fetch();
$webhookData = array_merge([
$webhookData = [
'recipe' => $recipe->name,
'grocycode' => (string)(new Grocycode(Grocycode::RECIPE, $args['recipeId'])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_LABEL_PRINTER_RUN_SERVER)
{
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
$this->getWebhookService()->run(WebhookService::EVENT_RECIPE_PRINT_LABEL, $webhookData);
return $this->ApiResponse($response, $webhookData);
}

View File

@ -4,7 +4,7 @@ namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Services\StockService;
use Grocy\Helpers\WebhookRunner;
use Grocy\Services\WebhookService;
use Grocy\Helpers\Grocycode;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -674,15 +674,12 @@ class StockApiController extends BaseApiController
{
$product = $this->getDatabase()->products()->where('id', $args['productId'])->fetch();
$webhookData = array_merge([
$webhookData = [
'product' => $product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $product->id)),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_LABEL_PRINTER_RUN_SERVER)
{
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
$this->getWebhookService()->run(WebhookService::EVENT_PRODUCT_PRINT_LABEL, $webhookData);
return $this->ApiResponse($response, $webhookData);
}
@ -699,20 +696,17 @@ class StockApiController extends BaseApiController
$stockEntry = $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch();
$product = $this->getDatabase()->products()->where('id', $stockEntry->product_id)->fetch();
$webhookData = array_merge([
$webhookData = [
'product' => $product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $stockEntry->product_id, [$stockEntry->stock_id])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $stockEntry->best_before_date;
}
if (GROCY_LABEL_PRINTER_RUN_SERVER)
{
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
$this->getWebhookService()->run(WebhookService::EVENT_STOCK_ENTRY_PRINT_LABEL, $webhookData);
return $this->ApiResponse($response, $webhookData);
}

View File

@ -1,48 +0,0 @@
<?php
namespace Grocy\Helpers;
use GuzzleHttp\Client;
use GuzzleHttp\ExceptionRequestException;
use Psr\Http\Message\ResponseInterface;
class WebhookRunner
{
public function __construct()
{
$this->client = new Client(['timeout' => 2.0]);
}
private $client;
public function run($url, $args, $json = false)
{
$reqArgs = [];
if ($json)
{
$reqArgs = ['json' => $args];
}
else
{
$reqArgs = ['form_params' => $args];
}
try
{
file_put_contents('php://stderr', 'Running Webhook: ' . $url . "\n" . print_r($reqArgs, true));
$this->client->request('POST', $url, $reqArgs);
}
catch (RequestException $e)
{
file_put_contents('php://stderr', 'Webhook failed: ' . $url . "\n" . $e->getMessage());
}
}
public function runAll($urls, $args)
{
foreach ($urls as $url)
{
$this->run($url, $args);
}
}
}

View File

@ -76,4 +76,9 @@ class BaseService
{
return ApplicationService::getInstance();
}
protected function getWebhookService()
{
return WebhookService::getInstance();
}
}

View File

@ -3,7 +3,7 @@
namespace Grocy\Services;
use Grocy\Helpers\Grocycode;
use Grocy\Helpers\WebhookRunner;
use Grocy\Services\WebhookService;
use GuzzleHttp\Client;
class StockService extends BaseService
@ -220,21 +220,17 @@ class StockService extends BaseService
]);
$stockRow->save();
if (GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER)
$webhookData = [
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockId])),
];
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData = array_merge([
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockId])),
], GROCY_LABEL_PRINTER_PARAMS);
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate;
}
$runner = new WebhookRunner();
$runner->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate;
}
$this->getWebhookService()->run(WebhookService::EVENT_ADD_PRODUCT, $webhookData);
}
}
else
@ -271,20 +267,19 @@ class StockService extends BaseService
]);
$stockRow->save();
if ($stockLabelType == 1 && GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER)
if ($stockLabelType == 1)
{
$webhookData = array_merge([
$webhookData = [
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockId])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate;
}
$runner = new WebhookRunner();
$runner->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
$this->getWebhookService()->run(WebhookService::EVENT_ADD_PRODUCT, $webhookData);
}
}
@ -996,20 +991,19 @@ class StockService extends BaseService
$newBestBeforeDate = $stockEntry->best_before_date;
}
if (GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER && $productDetails->product->auto_reprint_stock_label == 1 && $newBestBeforeDate != $stockEntry->best_before_date)
if ($productDetails->product->auto_reprint_stock_label == 1 && $newBestBeforeDate != $stockEntry->best_before_date)
{
$webhookData = array_merge([
$webhookData = [
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockEntry->stock_id])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $newBestBeforeDate;
}
$runner = new WebhookRunner();
$runner->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
$this->getWebhookService()->run(WebhookService::EVENT_OPEN_PRODUCT, $webhookData);
}
}
@ -1308,20 +1302,19 @@ class StockService extends BaseService
$newBestBeforeDate = date('Y-m-d', strtotime('+' . $productDetails->product->default_best_before_days_after_thawing . ' days'));
}
if (GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER && $productDetails->product->auto_reprint_stock_label == 1 && $stockEntry->best_before_date != $newBestBeforeDate)
if ($productDetails->product->auto_reprint_stock_label == 1 && $stockEntry->best_before_date != $newBestBeforeDate)
{
$webhookData = array_merge([
$webhookData = [
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockEntry->stock_id])),
], GROCY_LABEL_PRINTER_PARAMS);
];
if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$webhookData['due_date'] = $this->getLocalizationService()->__t('DD') . ': ' . $newBestBeforeDate;
}
$runner = new WebhookRunner();
$runner->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
$this->getWebhookService()->run(WebhookService::EVENT_TRANSFER_PRODUCT, $webhookData);
}
}

113
services/WebhookService.php Normal file
View File

@ -0,0 +1,113 @@
<?php
namespace Grocy\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
// Get these from a database table later
const WEBHOOKS = [
[
'url' => GROCY_LABEL_PRINTER_WEBHOOK,
'default_args' => GROCY_LABEL_PRINTER_PARAMS,
'json' => GROCY_LABEL_PRINTER_HOOK_JSON,
'include_events' => [
WebhookService::EVENT_BATTERY_PRINT_LABEL,
WebhookService::EVENT_CHORE_PRINT_LABEL,
WebhookService::EVENT_RECIPE_PRINT_LABEL,
WebhookService::EVENT_PRODUCT_PRINT_LABEL,
WebhookService::EVENT_STOCK_ENTRY_PRINT_LABEL,
WebhookService::EVENT_ADD_PRODUCT,
WebhookService::EVENT_OPEN_PRODUCT,
WebhookService::EVENT_TRANSFER_PRODUCT,
],
'exclude_events' => [],
'enabled' => GROCY_FEATURE_FLAG_LABEL_PRINTER && GROCY_LABEL_PRINTER_RUN_SERVER,
],
];
class WebhookService extends BaseService
{
const EVENT_BATTERY_PRINT_LABEL = 'battery_print_label';
const EVENT_CHORE_PRINT_LABEL = 'chore_print_label';
const EVENT_RECIPE_PRINT_LABEL = 'recipe_print_label';
const EVENT_PRODUCT_PRINT_LABEL = 'product_print_label';
const EVENT_STOCK_ENTRY_PRINT_LABEL = 'stock_entry_print_label';
const EVENT_ADD_PRODUCT = 'add_product';
const EVENT_OPEN_PRODUCT = 'open_product';
const EVENT_TRANSFER_PRODUCT = 'transfer_product';
public function __construct()
{
$this->client = new Client(['timeout' => 2.0]);
}
private $client;
private function shouldFire($webhook, $event)
{
if (!$webhook['enabled'])
{
return false;
}
$includeEvents = $webhook['include_events'] ?? [];
$excludeEvents = $webhook['exclude_events'] ?? [];
// No restrictions
if (empty($includeEvents) && empty($excludeEvents))
{
return true;
}
// Only include events
if (!empty($includeEvents) && in_array($event, $includeEvents))
{
return true;
}
// Only exclude events
if (!empty($excludeEvents) && !in_array($event, $excludeEvents))
{
return true;
}
// No events match
return false;
}
public function fire($event, $args)
{
foreach (WEBHOOKS as $webhook)
{
if ($this->shouldFire($webhook, $event))
{
$webhookData = array_merge($webhook['default_args'], $args);
$this->run($webhook['url'], $webhookData, $webhook['json']);
}
}
}
private function run($url, $args, $json = false)
{
$reqArgs = [];
if ($json)
{
$reqArgs = ['json' => $args];
}
else
{
$reqArgs = ['form_params' => $args];
}
try
{
file_put_contents('php://stderr', 'Running Webhook: ' . $url . "\n" . print_r($reqArgs, true));
$this->client->request('POST', $url, $reqArgs);
}
catch (RequestException $e)
{
file_put_contents('php://stderr', 'Webhook failed: ' . $url . "\n" . $e->getMessage());
}
}
}