Files
intotheeast-com-content/plugins/api/classes/Api/Controllers/WebhookController.php
T

218 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Grav\Plugin\Api\Controllers;
use Grav\Plugin\Api\Exceptions\NotFoundException;
use Grav\Plugin\Api\Exceptions\ValidationException;
use Grav\Plugin\Api\Response\ApiResponse;
use Grav\Plugin\Api\Webhooks\WebhookDispatcher;
use Grav\Plugin\Api\Webhooks\WebhookManager;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class WebhookController extends AbstractApiController
{
private const PERMISSION_READ = 'api.webhooks.read';
private const PERMISSION_WRITE = 'api.webhooks.write';
private const VALID_EVENTS = [
'*',
'page.created', 'page.updated', 'page.deleted', 'page.moved', 'page.translated',
'pages.reordered',
'media.uploaded', 'media.deleted',
'user.created', 'user.updated', 'user.deleted',
'config.updated',
'gpm.installed', 'gpm.removed', 'grav.upgraded',
];
private readonly WebhookManager $manager;
public function __construct(\Grav\Common\Grav $grav, \Grav\Common\Config\Config $config)
{
parent::__construct($grav, $config);
$this->manager = new WebhookManager();
}
/**
* GET /webhooks - List all configured webhooks.
*/
public function index(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_READ);
$webhooks = $this->manager->getAll();
// Redact secrets in listing
$data = array_map(function ($webhook) {
$webhook['secret'] = $this->redactSecret($webhook['secret'] ?? '');
return $webhook;
}, $webhooks);
return ApiResponse::create($data);
}
/**
* POST /webhooks - Create a new webhook.
*/
public function create(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_WRITE);
$body = $this->getRequestBody($request);
$this->requireFields($body, ['url']);
$url = $body['url'];
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new ValidationException("Invalid webhook URL: {$url}");
}
// Validate events if provided
if (isset($body['events'])) {
$this->validateEvents($body['events']);
}
$webhook = $this->manager->create($body);
$location = $this->getApiBaseUrl() . '/webhooks/' . $webhook['id'];
return ApiResponse::created($webhook, $location);
}
/**
* GET /webhooks/{id} - Get webhook details.
*/
public function show(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_READ);
$id = $this->getRouteParam($request, 'id');
$webhook = $this->manager->get($id);
if (!$webhook) {
throw new NotFoundException("Webhook '{$id}' not found.");
}
// Redact secret
$webhook['secret'] = $this->redactSecret($webhook['secret'] ?? '');
return $this->respondWithEtag($webhook);
}
/**
* PATCH /webhooks/{id} - Update a webhook.
*/
public function update(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_WRITE);
$id = $this->getRouteParam($request, 'id');
$body = $this->getRequestBody($request);
if (isset($body['url']) && !filter_var($body['url'], FILTER_VALIDATE_URL)) {
throw new ValidationException("Invalid webhook URL: {$body['url']}");
}
if (isset($body['events'])) {
$this->validateEvents($body['events']);
}
$webhook = $this->manager->update($id, $body);
if (!$webhook) {
throw new NotFoundException("Webhook '{$id}' not found.");
}
// Redact secret
$webhook['secret'] = $this->redactSecret($webhook['secret'] ?? '');
return $this->respondWithEtag($webhook);
}
/**
* DELETE /webhooks/{id} - Delete a webhook.
*/
public function delete(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_WRITE);
$id = $this->getRouteParam($request, 'id');
$deleted = $this->manager->delete($id);
if (!$deleted) {
throw new NotFoundException("Webhook '{$id}' not found.");
}
return ApiResponse::noContent();
}
/**
* GET /webhooks/{id}/deliveries - Get delivery log for a webhook.
*/
public function deliveries(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_READ);
$id = $this->getRouteParam($request, 'id');
if (!$this->manager->get($id)) {
throw new NotFoundException("Webhook '{$id}' not found.");
}
$pagination = $this->getPagination($request);
$result = $this->manager->getDeliveries($id, $pagination['limit'], $pagination['offset']);
$baseUrl = $this->getApiBaseUrl() . '/webhooks/' . $id . '/deliveries';
return ApiResponse::paginated(
data: $result['deliveries'],
total: $result['total'],
page: $pagination['page'],
perPage: $pagination['per_page'],
baseUrl: $baseUrl,
);
}
/**
* POST /webhooks/{id}/test - Send a test payload.
*/
public function test(ServerRequestInterface $request): ResponseInterface
{
$this->requirePermission($request, self::PERMISSION_WRITE);
$id = $this->getRouteParam($request, 'id');
$webhook = $this->manager->get($id);
if (!$webhook) {
throw new NotFoundException("Webhook '{$id}' not found.");
}
$dispatcher = new WebhookDispatcher($this->manager);
$delivery = $dispatcher->sendTest($webhook);
return ApiResponse::create($delivery, $delivery['success'] ? 200 : 502);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private function validateEvents(array $events): void
{
foreach ($events as $event) {
if (!in_array($event, self::VALID_EVENTS, true)) {
$valid = implode(', ', self::VALID_EVENTS);
throw new ValidationException("Invalid event '{$event}'. Valid events: {$valid}");
}
}
}
private function redactSecret(string $secret): string
{
if (strlen($secret) <= 10) {
return str_repeat('*', strlen($secret));
}
return substr($secret, 0, 6) . str_repeat('*', strlen($secret) - 10) . substr($secret, -4);
}
}