internal/service/webhook/types.go: Added Source type (gitea/github/gitlab/unknown), DetectWebhookSource(), DetectEventType(), and PushEvent normalized type. Moved GiteaPushPayload to payloads.go.
internal/service/webhook/webhook.go: HandleWebhook now accepts a Source parameter and uses ParsePushPayload() for unified parsing instead of directly unmarshaling Gitea payloads.
internal/handlers/webhook.go: Calls DetectWebhookSource() and DetectEventType() to auto-detect the platform before delegating to the webhook service.
internal/service/webhook/webhook_test.go: Comprehensive tests for source detection, event type extraction, payload parsing (all 3 platforms), commit URL fallback paths, and integration tests through HandleWebhook for GitHub and GitLab sources.
README.md: Updated description, features, non-goals, and architecture to reflect multi-platform webhook support.
Commit URL extraction fallback paths for GitHub and GitLab
Direct struct deserialization for all three payload types
Full HandleWebhook integration tests with GitHub and GitLab sources
## Summary
Adds GitHub and GitLab push webhook support alongside the existing Gitea support.
closes #68
## What Changed
### Auto-detection of webhook source
The webhook handler now auto-detects which platform sent the webhook by examining HTTP headers:
- **Gitea**: `X-Gitea-Event`
- **GitHub**: `X-GitHub-Event`
- **GitLab**: `X-Gitlab-Event`
Existing Gitea webhooks continue to work unchanged. Unknown sources fall back to Gitea format for backward compatibility.
### Normalized push event
All three payload formats are parsed into a unified `PushEvent` struct containing:
- Source platform, ref, branch, commit SHA
- Repository name, clone URL, HTML URL
- Commit URL (with per-platform fallback logic)
- Pusher username/name
### New files
- **`internal/service/webhook/payloads.go`**: Source-specific payload structs (`GiteaPushPayload`, `GitHubPushPayload`, `GitLabPushPayload`), `ParsePushPayload()` dispatcher, per-platform parsers, branch extraction, and commit URL extraction functions.
### Modified files
- **`internal/service/webhook/types.go`**: Added `Source` type (gitea/github/gitlab/unknown), `DetectWebhookSource()`, `DetectEventType()`, and `PushEvent` normalized type. Moved `GiteaPushPayload` to payloads.go.
- **`internal/service/webhook/webhook.go`**: `HandleWebhook` now accepts a `Source` parameter and uses `ParsePushPayload()` for unified parsing instead of directly unmarshaling Gitea payloads.
- **`internal/handlers/webhook.go`**: Calls `DetectWebhookSource()` and `DetectEventType()` to auto-detect the platform before delegating to the webhook service.
- **`internal/service/webhook/webhook_test.go`**: Comprehensive tests for source detection, event type extraction, payload parsing (all 3 platforms), commit URL fallback paths, and integration tests through `HandleWebhook` for GitHub and GitLab sources.
- **`README.md`**: Updated description, features, non-goals, and architecture to reflect multi-platform webhook support.
## Test coverage
Webhook package: **96.9%** statement coverage. Tests cover:
- `DetectWebhookSource` with all header combinations and precedence
- `DetectEventType` for each platform
- `ParsePushPayload` for Gitea, GitHub, GitLab, unknown source, invalid JSON, empty payloads
- Commit URL extraction fallback paths for GitHub and GitLab
- Direct struct deserialization for all three payload types
- Full `HandleWebhook` integration tests with GitHub and GitLab sources
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.
Code Review: PR #170 — GitHub and GitLab webhook support
Policy Compliance Check
Policy
Status
Evidence
External references pinned by hash
✅ PASS
No new dependencies — go.mod/go.sum unchanged. All Docker base images in Dockerfile remain pinned by SHA256.
No linter/CI/test config modifications
✅ PASS
.golangci.yml, Makefile, Dockerfile, .gitea/workflows/ — zero changes.
No new migration files (pre-1.0.0)
✅ PASS
No migration changes.
nolint comments justified
✅ PASS
//nolint:tagliatelle on payload structs (JSON field names match external API snake_case — standard justification, consistent with existing GiteaPushPayload pattern). //nolint:funlen on table-driven tests — consistent with pre-existing pattern.
README consistency
✅ PASS
Description, features, non-goals, and architecture all updated for multi-platform support. Non-goals correctly changed from "Support for non-Gitea webhooks" to "Support for non-push webhook events".
TestHandleWebhookGitHubSource, TestHandleWebhookGitLabSource — full end-to-end through service layer
Claimed 96.9% statement coverage for the webhook package.
Code Quality Assessment
Architecture: Clean separation — types in types.go, payload parsing in payloads.go, service logic in webhook.go. Handler in handlers/webhook.go does detection, delegates to service.
Error handling: Parse failures produce an empty PushEvent{Source: source}, allowing webhook events to still be recorded (graceful degradation, consistent with existing Gitea behavior).
Backward compatibility: SourceUnknown falls back to Gitea format — existing integrations sending webhooks without recognized headers continue working.
Scope: Tight — only push webhook support as specified. No scope creep.
Build Result
$ docker build .
✅ make fmt-check — PASS
✅ make lint — PASS
✅ make test — PASS
✅ make build — PASS
✅ Image built successfully
Verdict: ✅ PASS
Clean, well-structured implementation that meets all requirements from issue #68, follows all repo policies, and has comprehensive test coverage. No policy violations found.
## Code Review: PR #170 — GitHub and GitLab webhook support
### Policy Compliance Check
| Policy | Status | Evidence |
|--------|--------|----------|
| External references pinned by hash | ✅ PASS | No new dependencies — `go.mod`/`go.sum` unchanged. All Docker base images in Dockerfile remain pinned by SHA256. |
| No linter/CI/test config modifications | ✅ PASS | `.golangci.yml`, `Makefile`, `Dockerfile`, `.gitea/workflows/` — zero changes. |
| No new migration files (pre-1.0.0) | ✅ PASS | No migration changes. |
| `nolint` comments justified | ✅ PASS | `//nolint:tagliatelle` on payload structs (JSON field names match external API snake_case — standard justification, consistent with existing `GiteaPushPayload` pattern). `//nolint:funlen` on table-driven tests — consistent with pre-existing pattern. |
| README consistency | ✅ PASS | Description, features, non-goals, and architecture all updated for multi-platform support. Non-goals correctly changed from "Support for non-Gitea webhooks" to "Support for non-push webhook events". |
| No secrets/PII committed | ✅ PASS | Only test data with `example.com` domains. |
### Requirements Checklist ([Issue #68](https://git.eeqj.de/sneak/upaas/issues/68))
| Requirement | Status | Implementation |
|-------------|--------|----------------|
| Parse GitHub push webhook payloads | ✅ | `GitHubPushPayload` struct + `parseGitHubPush()` in [`payloads.go`](https://git.eeqj.de/sneak/upaas/src/branch/feature/github-gitlab-webhook-support/internal/service/webhook/payloads.go) |
| Parse GitLab push webhook payloads | ✅ | `GitLabPushPayload` struct + `parseGitLabPush()` in [`payloads.go`](https://git.eeqj.de/sneak/upaas/src/branch/feature/github-gitlab-webhook-support/internal/service/webhook/payloads.go) |
| Auto-detect webhook source by headers | ✅ | `DetectWebhookSource()` in [`types.go`](https://git.eeqj.de/sneak/upaas/src/branch/feature/github-gitlab-webhook-support/internal/service/webhook/types.go) — checks `X-Gitea-Event`, `X-GitHub-Event`, `X-Gitlab-Event` with defined precedence |
| Branch extraction for each format | ✅ | Shared `extractBranch()` in `payloads.go`, applied uniformly across all three parsers |
### Test Coverage Check
All new exported types and functions have tests:
| Export | Test |
|--------|------|
| `Source` type + constants | `TestWebhookSourceString` |
| `DetectWebhookSource()` | `TestDetectWebhookSource` (7 cases: each platform, unknown, empty, precedence) |
| `DetectEventType()` | `TestDetectEventType` (5 cases: each platform, unknown, missing header) |
| `ParsePushPayload()` | `TestParsePushPayloadGitea`, `TestParsePushPayloadGitHub`, `TestParsePushPayloadGitLab`, `TestParsePushPayloadUnknownFallsBackToGitea`, `TestParsePushPayloadInvalidJSON`, `TestParsePushPayloadEmptyPayload` |
| `PushEvent` struct | `TestPushEventConstruction` |
| `GiteaPushPayload` | `TestGiteaPushPayloadParsing` |
| `GitHubPushPayload` | `TestGitHubPushPayloadParsing` |
| `GitLabPushPayload` | `TestGitLabPushPayloadParsing` |
| Commit URL fallbacks | `TestGitHubCommitURLFallback` (3 paths), `TestGitLabCommitURLFallback` (2 paths) |
| Integration (HandleWebhook) | `TestHandleWebhookGitHubSource`, `TestHandleWebhookGitLabSource` — full end-to-end through service layer |
Claimed 96.9% statement coverage for the webhook package.
### Code Quality Assessment
- **Architecture**: Clean separation — types in `types.go`, payload parsing in `payloads.go`, service logic in `webhook.go`. Handler in `handlers/webhook.go` does detection, delegates to service.
- **Error handling**: Parse failures produce an empty `PushEvent{Source: source}`, allowing webhook events to still be recorded (graceful degradation, consistent with existing Gitea behavior).
- **Backward compatibility**: `SourceUnknown` falls back to Gitea format — existing integrations sending webhooks without recognized headers continue working.
- **Edge cases covered**: Empty payloads, invalid JSON, missing commits, missing head_commit, commit URL fallback chains.
- **Scope**: Tight — only push webhook support as specified. No scope creep.
### Build Result
```
$ docker build .
✅ make fmt-check — PASS
✅ make lint — PASS
✅ make test — PASS
✅ make build — PASS
✅ Image built successfully
```
### Verdict: ✅ PASS
Clean, well-structured implementation that meets all requirements from [issue #68](https://git.eeqj.de/sneak/upaas/issues/68), follows all repo policies, and has comprehensive test coverage. No policy violations found.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds GitHub and GitLab push webhook support alongside the existing Gitea support.
closes #68
What Changed
Auto-detection of webhook source
The webhook handler now auto-detects which platform sent the webhook by examining HTTP headers:
X-Gitea-EventX-GitHub-EventX-Gitlab-EventExisting Gitea webhooks continue to work unchanged. Unknown sources fall back to Gitea format for backward compatibility.
Normalized push event
All three payload formats are parsed into a unified
PushEventstruct containing:New files
internal/service/webhook/payloads.go: Source-specific payload structs (GiteaPushPayload,GitHubPushPayload,GitLabPushPayload),ParsePushPayload()dispatcher, per-platform parsers, branch extraction, and commit URL extraction functions.Modified files
internal/service/webhook/types.go: AddedSourcetype (gitea/github/gitlab/unknown),DetectWebhookSource(),DetectEventType(), andPushEventnormalized type. MovedGiteaPushPayloadto payloads.go.internal/service/webhook/webhook.go:HandleWebhooknow accepts aSourceparameter and usesParsePushPayload()for unified parsing instead of directly unmarshaling Gitea payloads.internal/handlers/webhook.go: CallsDetectWebhookSource()andDetectEventType()to auto-detect the platform before delegating to the webhook service.internal/service/webhook/webhook_test.go: Comprehensive tests for source detection, event type extraction, payload parsing (all 3 platforms), commit URL fallback paths, and integration tests throughHandleWebhookfor GitHub and GitLab sources.README.md: Updated description, features, non-goals, and architecture to reflect multi-platform webhook support.Test coverage
Webhook package: 96.9% statement coverage. Tests cover:
DetectWebhookSourcewith all header combinations and precedenceDetectEventTypefor each platformParsePushPayloadfor Gitea, GitHub, GitLab, unknown source, invalid JSON, empty payloadsHandleWebhookintegration tests with GitHub and GitLab sourcesCode Review: PR #170 — GitHub and GitLab webhook support
Policy Compliance Check
go.mod/go.sumunchanged. All Docker base images in Dockerfile remain pinned by SHA256..golangci.yml,Makefile,Dockerfile,.gitea/workflows/— zero changes.nolintcomments justified//nolint:tagliatelleon payload structs (JSON field names match external API snake_case — standard justification, consistent with existingGiteaPushPayloadpattern).//nolint:funlenon table-driven tests — consistent with pre-existing pattern.example.comdomains.Requirements Checklist (Issue #68)
GitHubPushPayloadstruct +parseGitHubPush()inpayloads.goGitLabPushPayloadstruct +parseGitLabPush()inpayloads.goDetectWebhookSource()intypes.go— checksX-Gitea-Event,X-GitHub-Event,X-Gitlab-Eventwith defined precedenceextractBranch()inpayloads.go, applied uniformly across all three parsersTest Coverage Check
All new exported types and functions have tests:
Sourcetype + constantsTestWebhookSourceStringDetectWebhookSource()TestDetectWebhookSource(7 cases: each platform, unknown, empty, precedence)DetectEventType()TestDetectEventType(5 cases: each platform, unknown, missing header)ParsePushPayload()TestParsePushPayloadGitea,TestParsePushPayloadGitHub,TestParsePushPayloadGitLab,TestParsePushPayloadUnknownFallsBackToGitea,TestParsePushPayloadInvalidJSON,TestParsePushPayloadEmptyPayloadPushEventstructTestPushEventConstructionGiteaPushPayloadTestGiteaPushPayloadParsingGitHubPushPayloadTestGitHubPushPayloadParsingGitLabPushPayloadTestGitLabPushPayloadParsingTestGitHubCommitURLFallback(3 paths),TestGitLabCommitURLFallback(2 paths)TestHandleWebhookGitHubSource,TestHandleWebhookGitLabSource— full end-to-end through service layerClaimed 96.9% statement coverage for the webhook package.
Code Quality Assessment
types.go, payload parsing inpayloads.go, service logic inwebhook.go. Handler inhandlers/webhook.godoes detection, delegates to service.PushEvent{Source: source}, allowing webhook events to still be recorded (graceful degradation, consistent with existing Gitea behavior).SourceUnknownfalls back to Gitea format — existing integrations sending webhooks without recognized headers continue working.Build Result
Verdict: ✅ PASS
Clean, well-structured implementation that meets all requirements from issue #68, follows all repo policies, and has comprehensive test coverage. No policy violations found.