Files
upaas/internal/handlers/webhook.go
user 0c5c727e01
All checks were successful
Check / check (pull_request) Successful in 1m50s
feat: add GitHub and GitLab webhook support
Add auto-detection of webhook source (Gitea, GitHub, GitLab) by
examining HTTP headers (X-Gitea-Event, X-GitHub-Event, X-Gitlab-Event).

Parse push webhook payloads from all three platforms into a normalized
PushEvent type for unified processing. Each platform's payload format
is handled by dedicated parser functions with correct field mapping
and commit URL extraction.

The webhook handler now detects the source automatically — existing
Gitea webhooks continue to work unchanged, while GitHub and GitLab
webhooks are parsed with their respective payload formats.

Includes comprehensive tests for source detection, event type
extraction, payload parsing for all three platforms, commit URL
fallback logic, and integration tests via HandleWebhook.
2026-03-17 02:37:29 -07:00

79 lines
1.9 KiB
Go

package handlers
import (
"io"
"net/http"
"github.com/go-chi/chi/v5"
"sneak.berlin/go/upaas/internal/models"
"sneak.berlin/go/upaas/internal/service/webhook"
)
// maxWebhookBodySize is the maximum allowed size of a webhook request body (1MB).
const maxWebhookBodySize = 1 << 20
// HandleWebhook handles incoming webhooks from Gitea, GitHub, or GitLab.
// The webhook source is auto-detected from HTTP headers.
func (h *Handlers) HandleWebhook() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
secret := chi.URLParam(request, "secret")
if secret == "" {
http.NotFound(writer, request)
return
}
// Find app by webhook secret
application, findErr := models.FindAppByWebhookSecret(
request.Context(),
h.db,
secret,
)
if findErr != nil {
h.log.Error("failed to find app by webhook secret", "error", findErr)
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
return
}
if application == nil {
http.NotFound(writer, request)
return
}
// Read request body with size limit to prevent memory exhaustion
body, readErr := io.ReadAll(io.LimitReader(request.Body, maxWebhookBodySize))
if readErr != nil {
h.log.Error("failed to read webhook body", "error", readErr)
http.Error(writer, "Bad Request", http.StatusBadRequest)
return
}
// Auto-detect webhook source from headers
source := webhook.DetectWebhookSource(request.Header)
// Extract event type based on detected source
eventType := webhook.DetectEventType(request.Header, source)
// Process webhook
webhookErr := h.webhook.HandleWebhook(
request.Context(),
application,
source,
eventType,
body,
)
if webhookErr != nil {
h.log.Error("failed to process webhook", "error", webhookErr)
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
return
}
writer.WriteHeader(http.StatusOK)
}
}