From 69beb451b595fe2f053383e76850913dceb1abdf Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 18:32:39 +0000 Subject: [PATCH] feat(api): expose webhook secret routes Co-Authored-By: Virgil --- src/php/src/Api/Routes/api.php | 36 +++++++++++++++++++ .../Tests/Feature/WebhookSecretRoutesTest.php | 28 +++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/php/src/Api/Tests/Feature/WebhookSecretRoutesTest.php diff --git a/src/php/src/Api/Routes/api.php b/src/php/src/Api/Routes/api.php index e643a7f..26f6a69 100644 --- a/src/php/src/Api/Routes/api.php +++ b/src/php/src/Api/Routes/api.php @@ -5,6 +5,7 @@ declare(strict_types=1); use Core\Api\Controllers\Api\UnifiedPixelController; use Core\Api\Controllers\Api\EntitlementApiController; use Core\Api\Controllers\Api\SeoReportController; +use Core\Api\Controllers\Api\WebhookSecretController; use Core\Api\Controllers\McpApiController; use Core\Api\Middleware\PublicApiCors; use Core\Mcp\Middleware\McpApiKeyAuth; @@ -57,6 +58,41 @@ Route::middleware(['auth.api', 'api.scope.enforce']) ->name('show'); }); +// ───────────────────────────────────────────────────────────────────────────── +// Webhook secret rotation (authenticated) +// ───────────────────────────────────────────────────────────────────────────── + +Route::middleware(['auth.api', 'api.scope.enforce']) + ->prefix('webhooks') + ->name('api.webhooks.') + ->group(function () { + Route::prefix('social/{uuid}/secret') + ->name('social.') + ->group(function () { + Route::post('/rotate', [WebhookSecretController::class, 'rotateSocialSecret']) + ->name('rotate-secret'); + Route::get('/', [WebhookSecretController::class, 'socialSecretStatus']) + ->name('status'); + Route::delete('/previous', [WebhookSecretController::class, 'invalidateSocialPreviousSecret']) + ->name('invalidate-previous'); + Route::patch('/grace-period', [WebhookSecretController::class, 'updateSocialGracePeriod']) + ->name('grace-period'); + }); + + Route::prefix('content/{uuid}/secret') + ->name('content.') + ->group(function () { + Route::post('/rotate', [WebhookSecretController::class, 'rotateContentSecret']) + ->name('rotate-secret'); + Route::get('/', [WebhookSecretController::class, 'contentSecretStatus']) + ->name('status'); + Route::delete('/previous', [WebhookSecretController::class, 'invalidateContentPreviousSecret']) + ->name('invalidate-previous'); + Route::patch('/grace-period', [WebhookSecretController::class, 'updateContentGracePeriod']) + ->name('grace-period'); + }); + }); + // ───────────────────────────────────────────────────────────────────────────── // MCP HTTP Bridge (API key auth) // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/php/src/Api/Tests/Feature/WebhookSecretRoutesTest.php b/src/php/src/Api/Tests/Feature/WebhookSecretRoutesTest.php new file mode 100644 index 0000000..79a9066 --- /dev/null +++ b/src/php/src/Api/Tests/Feature/WebhookSecretRoutesTest.php @@ -0,0 +1,28 @@ +getByName('api.webhooks.social.rotate-secret'); + $socialStatus = Route::getRoutes()->getByName('api.webhooks.social.status'); + $contentRotate = Route::getRoutes()->getByName('api.webhooks.content.rotate-secret'); + $contentStatus = Route::getRoutes()->getByName('api.webhooks.content.status'); + + expect($socialRotate)->not->toBeNull(); + expect($socialRotate->uri())->toBe('api/webhooks/social/{uuid}/secret/rotate'); + expect($socialRotate->methods())->toContain('POST'); + + expect($socialStatus)->not->toBeNull(); + expect($socialStatus->uri())->toBe('api/webhooks/social/{uuid}/secret'); + expect($socialStatus->methods())->toContain('GET'); + + expect($contentRotate)->not->toBeNull(); + expect($contentRotate->uri())->toBe('api/webhooks/content/{uuid}/secret/rotate'); + expect($contentRotate->methods())->toContain('POST'); + + expect($contentStatus)->not->toBeNull(); + expect($contentStatus->uri())->toBe('api/webhooks/content/{uuid}/secret'); + expect($contentStatus->methods())->toContain('GET'); +});