Files
forgebot/internal/apiserver/handlers/webhook.go
T
unkinben 49d514c050 Initial scaffold: API service, K8s operator, and CRDs
Forgebot is a K8s operator + API service for dispatching AI agent
jobs from git forge commands. Includes:

- CRDs: AgentPool, AgentTask, ProviderQueue, RepositoryBinding
- API server with webhook handler, task queue, and comment proxy
- Operator controllers for task scheduling and job management
- Gitea provider with webhook parsing and signature verification
- PostgreSQL database with auto-migration
- Woodpecker CI pipelines and multi-stage Dockerfiles
2026-06-08 22:49:18 +10:00

92 lines
2.1 KiB
Go

package handlers
import (
"io"
"log/slog"
"net/http"
"strings"
"git.unkin.net/unkin/forgebot/internal/database"
"git.unkin.net/unkin/forgebot/internal/provider/gitea"
"git.unkin.net/unkin/forgebot/pkg/models"
)
type WebhookHandler struct {
db *database.DB
provider *gitea.Client
webhookSecret string
}
func NewWebhookHandler(db *database.DB, provider *gitea.Client, secret string) *WebhookHandler {
return &WebhookHandler{
db: db,
provider: provider,
webhookSecret: secret,
}
}
func (h *WebhookHandler) HandleGitea(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
signature := r.Header.Get("X-Gitea-Signature")
if !gitea.VerifySignature(body, h.webhookSecret, signature) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
event, err := h.provider.ParseWebhook(body, h.webhookSecret)
if err != nil {
slog.Error("failed to parse webhook", "error", err)
http.Error(w, "failed to parse webhook", http.StatusBadRequest)
return
}
if event == nil {
w.WriteHeader(http.StatusOK)
return
}
commands := models.ParseCommands(event.Body)
if len(commands) == 0 {
w.WriteHeader(http.StatusOK)
return
}
parts := strings.SplitN(event.Repository, "/", 2)
if len(parts) != 2 {
http.Error(w, "invalid repository format", http.StatusBadRequest)
return
}
for _, cmd := range commands {
task, err := h.db.CreateTask(r.Context(), models.CreateTaskRequest{
Command: cmd.Name,
Repository: event.Repository,
Ref: event.Ref,
IssueNumber: event.IssueNum,
PRNumber: event.PRNum,
CommentID: event.CommentID,
Body: cmd.Args,
Author: event.Author,
})
if err != nil {
slog.Error("failed to create task", "error", err, "command", cmd.Name)
continue
}
slog.Info("task created from webhook",
"id", task.ID,
"command", cmd.Name,
"repository", event.Repository,
"author", event.Author,
)
h.provider.AddReaction(parts[0], parts[1], event.CommentID, "eyes")
}
w.WriteHeader(http.StatusAccepted)
}