Files

138 lines
3.7 KiB
Go

package webhook
import (
"encoding/json"
"net/http"
"github.com/rs/zerolog"
)
// EventHandler processes webhook events
type EventHandler interface {
HandlePullRequest(event *PullRequestEvent)
HandlePullRequestReview(event *PullRequestReviewEvent)
HandlePullRequestComment(event *PullRequestCommentEvent)
HandleIssue(event *IssueEvent)
HandleIssueComment(event *IssueCommentEvent)
}
// Handler handles incoming Gitea webhooks
type Handler struct {
validator *Validator
eventHandler EventHandler
logger zerolog.Logger
}
// NewHandler creates a new webhook handler
func NewHandler(secret string, eventHandler EventHandler, logger zerolog.Logger) *Handler {
return &Handler{
validator: NewValidator(secret),
eventHandler: eventHandler,
logger: logger.With().Str("component", "webhook").Logger(),
}
}
// ServeHTTP handles the webhook HTTP request
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Only accept POST requests
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Get event metadata
eventType := GetEventType(r)
deliveryID := GetDeliveryID(r)
logger := h.logger.With().
Str("delivery_id", deliveryID).
Str("event_type", string(eventType)).
Logger()
// Validate signature and read body
body, err := h.validator.ValidateRequest(r)
if err != nil {
logger.Warn().Err(err).Msg("webhook validation failed")
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// Respond immediately with 200 OK
// Process the event asynchronously
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"accepted"}`))
// Process the event in a goroutine
go h.processEvent(logger, eventType, body)
}
// processEvent parses and routes the event to the appropriate handler
func (h *Handler) processEvent(logger zerolog.Logger, eventType GiteaEventType, body []byte) {
var err error
switch eventType {
case EventPullRequest:
var event PullRequestEvent
if err = json.Unmarshal(body, &event); err == nil {
logger.Info().
Str("action", event.Action).
Int64("pr_number", event.Number).
Str("repo", event.Repository.FullName).
Msg("processing pull request event")
h.eventHandler.HandlePullRequest(&event)
}
case EventPullRequestReview:
var event PullRequestReviewEvent
if err = json.Unmarshal(body, &event); err == nil {
logger.Info().
Str("action", event.Action).
Str("review_state", event.Review.State).
Str("repo", event.Repository.FullName).
Msg("processing pull request review event")
h.eventHandler.HandlePullRequestReview(&event)
}
case EventPullRequestComment:
var event PullRequestCommentEvent
if err = json.Unmarshal(body, &event); err == nil {
logger.Info().
Str("action", event.Action).
Int64("pr_number", event.PullRequest.Number).
Str("repo", event.Repository.FullName).
Msg("processing pull request comment event")
h.eventHandler.HandlePullRequestComment(&event)
}
case EventIssues:
var event IssueEvent
if err = json.Unmarshal(body, &event); err == nil {
logger.Info().
Str("action", event.Action).
Int64("issue_number", event.Issue.Number).
Str("repo", event.Repository.FullName).
Msg("processing issue event")
h.eventHandler.HandleIssue(&event)
}
case EventIssueComment:
var event IssueCommentEvent
if err = json.Unmarshal(body, &event); err == nil {
logger.Info().
Str("action", event.Action).
Int64("issue_number", event.Issue.Number).
Str("repo", event.Repository.FullName).
Msg("processing issue comment event")
h.eventHandler.HandleIssueComment(&event)
}
default:
logger.Debug().Msg("ignoring unknown event type")
return
}
if err != nil {
logger.Error().Err(err).Msg("failed to parse event payload")
}
}