import { json } from "@remix-run/node"; import type { ActionFunctionArgs } from "@remix-run/node"; import { githubApp } from "~/lib/github-app.server"; import { hugexService } from "~/lib/hugex-service.server"; import type { HugExJobCreationParams } from "~/lib/hugex-service.server"; export async function action({ request }: ActionFunctionArgs) { if (request.method !== "POST") { return json({ error: "Method not allowed" }, { status: 405 }); } let payload: string; let eventData: any; try { payload = await request.text(); const signature = request.headers.get("x-hub-signature-256") || ""; const event = request.headers.get("x-github-event") || ""; const delivery = request.headers.get("x-github-delivery") || ""; console.log(`📥 Received GitHub webhook: ${event} (${delivery})`); // // Verify webhook signature // if (!githubApp.verifyWebhookSignature(payload, signature)) { // console.error("Invalid webhook signature"); // return json({ error: "Invalid signature" }, { status: 401 }); // } try { eventData = JSON.parse(payload); } catch (parseError) { console.error("Failed to parse webhook payload:", parseError); return json({ error: "Invalid JSON payload" }, { status: 400 }); } // Only log event data for specific events we care about if (["installation", "installation_repositories"].includes(event)) { console.log("Event data:", JSON.stringify(eventData, null, 2)); } // Handle different webhook events switch (event) { case "installation": await handleInstallationEvent(eventData); break; case "installation_repositories": await handleInstallationRepositoriesEvent(eventData); break; case "push": await handlePushEvent(eventData); break; case "pull_request": await handlePullRequestEvent(eventData); break; case "issues": await handleIssuesEvent(eventData); break; default: console.log(`Unhandled event type: ${event}`); } return json({ success: true, event, delivery }); } catch (error) { console.error("Webhook error:", error); return json({ error: "Internal server error" }, { status: 500 }); } } async function handleInstallationEvent(data: any) { const { action, installation, repositories } = data; console.log(`🔧 Installation ${action}:`, { installationId: installation.id, account: installation.account.login, repositoryCount: repositories?.length || 0, }); if (action === "created") { // App was installed - you can store installation info here console.log(`✅ App installed by ${installation.account.login}`); } else if (action === "deleted") { // App was uninstalled console.log(`❌ App uninstalled by ${installation.account.login}`); } } async function handleInstallationRepositoriesEvent(data: any) { const { action, installation, repositories_added, repositories_removed } = data; console.log(`📁 Installation repositories ${action}:`, { installationId: installation.id, added: repositories_added?.length || 0, removed: repositories_removed?.length || 0, }); } async function handlePushEvent(data: any) { const { repository, pusher, commits } = data; console.log(`🚀 Push to ${repository.full_name}:`, { pusher: pusher.name, commits: commits.length, branch: data.ref.replace("refs/heads/", ""), }); // Example: Use the pusher's authenticated session try { const userAuth = githubApp.getUserAuth(pusher.name); if (userAuth) { console.log(`🔑 Found authenticated user: ${pusher.name}`); const environment = { LLM_MODEL: "gpt-4", LLM_PROVIDER: "openai", }; const secrets = { OPENAI_API_KEY: "sk-test", //userAuth.token, // Use the user's token for authentication }; const params = { title: `Push by ${pusher.name} to ${repository.full_name}`, description: `New push to ${repository.full_name} by ${pusher.name}`, repository: { url: repository.html_url, }, branch: data.ref.replace("refs/heads/", ""), environment, secrets, }; console.log("Creating HugEx job with params:", params); // Create HugEx job const jobId = await hugexService.createJob(pusher.name, params); if (jobId) { console.log(`✅ Created HugEx job for push event: ${jobId}`); } else { console.warn( `⚠️ Failed to create HugEx job for push event by ${pusher.name}` ); } } else { console.log(`⚠️ No authentication found for user: ${pusher.name}`); } } catch (error) { console.error("Error handling push event:", error); } } async function handlePullRequestEvent(data: any) { const { action, pull_request, repository } = data; console.log(`🔀 Pull request ${action} in ${repository.full_name}:`, { number: pull_request.number, title: pull_request.title, author: pull_request.user.login, }); // Example: Use the PR author's authenticated session try { const userAuth = githubApp.getUserAuth(pull_request.user.login); if (userAuth) { console.log(`🔑 Found authenticated user: ${pull_request.user.login}`); // You can now use the user's authentication to perform actions } else { console.log( `⚠️ No authentication found for user: ${pull_request.user.login}` ); } } catch (error) { console.error("Error handling pull request event:", error); } } async function handleIssuesEvent(data: any) { const { action, issue, repository } = data; console.log(`🐛 Issue ${action} in ${repository.full_name}:`, { number: issue.number, title: issue.title, author: issue.user.login, }); // Process issue for HugEx if action is 'opened' or 'edited' if (["opened", "edited"].includes(action)) { try { // Check for @hugex mention and create job if found const jobCreated = await hugexService.processGitHubIssue( issue, repository ); if (jobCreated) { console.log(`✅ Created HugEx job for issue #${issue.number}`); } } catch (error) { console.error("Error processing issue for HugEx:", error); } } // Example: Use the issue author's authenticated session try { const userAuth = githubApp.getUserAuth(issue.user.login); if (userAuth) { console.log(`🔑 Found authenticated user: ${issue.user.login}`); // You can now use the user's authentication to perform actions } else { console.log(`⚠️ No authentication found for user: ${issue.user.login}`); } } catch (error) { console.error("Error handling issues event:", error); } } // Prevent GET requests export async function loader() { return json( { error: "This endpoint only accepts POST requests from GitHub webhooks" }, { status: 405 } ); }