Skip to content

Commit a9610ef

Browse files
authored
Merge pull request #31 from carlsimpson-magento/feature/webhook-sync
Add webhook endpoint for GitHub docs sync
2 parents 31d7928 + ffbef8b commit a9610ef

4 files changed

Lines changed: 108 additions & 1 deletion

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Artisan;
7+
use Illuminate\Support\Facades\Log;
8+
use Symfony\Component\Process\Process;
9+
10+
class WebhookController extends Controller
11+
{
12+
/**
13+
* Handle GitHub webhook to sync documentation.
14+
*/
15+
public function syncDocs(Request $request)
16+
{
17+
// Verify webhook secret
18+
$secret = config('services.github.webhook_secret');
19+
20+
if (!$secret) {
21+
Log::error('Webhook secret not configured');
22+
return response()->json(['error' => 'Webhook not configured'], 500);
23+
}
24+
25+
// Verify GitHub signature
26+
$signature = $request->header('X-Hub-Signature-256');
27+
if (!$signature) {
28+
Log::warning('Webhook request missing signature');
29+
return response()->json(['error' => 'Missing signature'], 401);
30+
}
31+
32+
$payload = $request->getContent();
33+
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
34+
35+
if (!hash_equals($expectedSignature, $signature)) {
36+
Log::warning('Webhook signature mismatch');
37+
return response()->json(['error' => 'Invalid signature'], 401);
38+
}
39+
40+
// Only process push events to main branch
41+
$event = $request->header('X-GitHub-Event');
42+
if ($event !== 'push') {
43+
return response()->json(['message' => 'Event ignored', 'event' => $event]);
44+
}
45+
46+
$payload = json_decode($request->getContent(), true);
47+
$ref = $payload['ref'] ?? '';
48+
49+
if ($ref !== 'refs/heads/main') {
50+
return response()->json(['message' => 'Branch ignored', 'ref' => $ref]);
51+
}
52+
53+
Log::info('Starting docs sync from webhook');
54+
55+
try {
56+
// Pull latest docs
57+
$docsPath = resource_path('docs/main');
58+
59+
$fetchProcess = new Process(['git', 'fetch', 'origin', 'main'], $docsPath);
60+
$fetchProcess->run();
61+
62+
if (!$fetchProcess->isSuccessful()) {
63+
throw new \RuntimeException('Git fetch failed: ' . $fetchProcess->getErrorOutput());
64+
}
65+
66+
$resetProcess = new Process(['git', 'reset', '--hard', 'origin/main'], $docsPath);
67+
$resetProcess->run();
68+
69+
if (!$resetProcess->isSuccessful()) {
70+
throw new \RuntimeException('Git reset failed: ' . $resetProcess->getErrorOutput());
71+
}
72+
73+
// Clear Laravel caches
74+
Artisan::call('cache:clear');
75+
Artisan::call('view:clear');
76+
Artisan::call('config:clear');
77+
Artisan::call('route:clear');
78+
79+
// Rebuild caches
80+
Artisan::call('config:cache');
81+
Artisan::call('route:cache');
82+
Artisan::call('view:cache');
83+
84+
Log::info('Docs sync completed successfully');
85+
86+
return response()->json([
87+
'success' => true,
88+
'message' => 'Documentation synced successfully',
89+
]);
90+
91+
} catch (\Exception $e) {
92+
Log::error('Docs sync failed: ' . $e->getMessage());
93+
94+
return response()->json([
95+
'error' => 'Sync failed',
96+
'message' => $e->getMessage(),
97+
], 500);
98+
}
99+
}
100+
}

app/Http/Middleware/VerifyCsrfToken.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
1212
* @var array
1313
*/
1414
protected $except = [
15-
//
15+
'webhook/*',
1616
];
1717
}

config/services.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'token' => env('GITHUB_TOKEN'),
3535
'owner' => env('GITHUB_REPO_OWNER', 'magentoopensource'),
3636
'repo' => env('GITHUB_REPO_NAME', 'docs'),
37+
'webhook_secret' => env('GITHUB_WEBHOOK_SECRET'),
3738
],
3839

3940
];

routes/web.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use Illuminate\Support\Facades\Route;
44
use App\Http\Controllers\DocsController;
55
use App\Http\Controllers\TeamController;
6+
use App\Http\Controllers\WebhookController;
67

78
// Apply rate limiting to all routes to prevent abuse
89
// 120 requests per minute per IP (reasonable for a documentation site)
@@ -21,3 +22,8 @@
2122

2223
Route::get("team", [TeamController::class, "index"])->name("team");
2324
});
25+
26+
// GitHub webhook for docs sync (excluded from throttling)
27+
Route::post('/webhook/sync-docs', [WebhookController::class, 'syncDocs'])
28+
->middleware('throttle:10,1')
29+
->name('webhook.sync-docs');

0 commit comments

Comments
 (0)