Skip to content

Support graphql #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 121 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ HubProxy is a proxy for GitHub webhooks, built for people building with GitHub a
- View event type statistics over time
- Replay single events or event ranges
- Filter and query capabilities for all operations
- **GraphQL API**:
- Alternative to REST API with the same functionality
- Flexible querying with precise field selection
- Interactive GraphiQL and Playground interfaces for testing
- Support for complex queries and mutations in a single request
- **Monitoring**:
- Provides metrics and logging for webhook delivery status and performance
- Track event patterns and volume through API statistics
Expand Down Expand Up @@ -296,7 +301,11 @@ sqlite3 .cache/hubproxy.db

## API Reference

HubProxy provides a REST API for querying and replaying webhook events. All API endpoints return JSON responses.
HubProxy provides both REST and GraphQL APIs for querying and replaying webhook events.

### REST API

All REST API endpoints return JSON responses.

### List Events

Expand Down Expand Up @@ -469,15 +478,119 @@ Replays all webhook events within a specified time range.
}
```

**Notes:**
- Each replayed event uses GitHub's original delivery ID to ensure proper tracing
- The event is marked with a "replayed" status
- The original event remains unchanged in the database
- The webhook payload is preserved exactly as it was in the original event
- Range replay has a default limit of 100 events (can be overridden with `limit` parameter)
### GraphQL API

### Prometheus Metrics
HubProxy also provides a GraphQL API that mirrors the functionality of the REST API with more flexibility in querying.

```http
POST /graphql
```

The GraphQL endpoint also serves an interactive GraphiQL interface when accessed from a browser, allowing you to explore and test queries.

#### Queries

##### List Events

```graphql
query {
events(
type: "push",
repository: "owner/repo",
sender: "username",
status: "received",
since: "2024-02-01T00:00:00Z",
until: "2024-02-02T00:00:00Z",
limit: 10,
offset: 0
) {
events {
id
type
payload
createdAt
status
repository
sender
replayedFrom
originalTime
}
total
}
}
```

##### Get Single Event

```graphql
query {
event(id: "d2a1f85a-delivery-id-123") {
id
type
payload
createdAt
status
repository
sender
}
}
```

##### Get Event Statistics

```graphql
query {
stats(since: "2024-02-01T00:00:00Z") {
type
count
}
}
```

#### Mutations

##### Replay Single Event

```graphql
mutation {
replayEvent(id: "d2a1f85a-delivery-id-123") {
replayedCount
events {
id
type
status
replayedFrom
originalTime
}
}
}
```

##### Replay Events by Time Range

```graphql
mutation {
replayRange(
since: "2024-02-01T00:00:00Z",
until: "2024-02-02T00:00:00Z",
type: "push",
repository: "owner/repo",
sender: "username",
limit: 10
) {
replayedCount
events {
id
type
status
replayedFrom
originalTime
}
}
}
```

### Prometheus Metrics
```
GET /metrics
```
Expand Down
10 changes: 10 additions & 0 deletions cmd/hubproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"hubproxy/internal/api"
"hubproxy/internal/graphql"
"hubproxy/internal/metrics"
"hubproxy/internal/security"
"hubproxy/internal/storage"
Expand Down Expand Up @@ -249,6 +250,12 @@ func run() error {
apiHandler := api.NewHandler(store, logger)
apiRouter := chi.NewRouter()

// Create GraphQL handler
graphqlHandler, err := graphql.NewHandler(store, logger)
if err != nil {
return fmt.Errorf("failed to create GraphQL handler: %w", err)
}

apiRouter.Use(metrics.Middleware)
apiRouter.Use(middleware.RequestID)
if viper.GetBool("trusted-proxy") {
Expand All @@ -264,6 +271,9 @@ func run() error {
apiRouter.Get("/api/replay", apiHandler.ReplayRange)
apiRouter.Handle("/metrics", promhttp.Handler())

// Add GraphQL endpoint
apiRouter.Handle("/graphql", graphqlHandler)

apiSrv := &http.Server{
Handler: apiRouter,
ReadTimeout: 10 * time.Second,
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ require (
github.com/go-chi/chi/v5 v5.2.1
github.com/go-sql-driver/mysql v1.8.1
github.com/google/uuid v1.6.0
github.com/graphql-go/graphql v0.8.1
github.com/graphql-go/handler v0.2.4
github.com/jackc/pgx/v5 v5.7.2
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.24
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/graphql-go/handler v0.2.4 h1:gz9q11TUHPNUpqzV8LMa+rkqM5NUuH/nkE3oF2LS3rI=
github.com/graphql-go/handler v0.2.4/go.mod h1:gsQlb4gDvURR0bgN8vWQEh+s5vJALM2lYL3n3cf6OxQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
Expand Down
130 changes: 130 additions & 0 deletions internal/graphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# GraphQL Interface for HubProxy

This package implements a GraphQL interface for HubProxy that mirrors the functionality of the REST API.

## Endpoints

The GraphQL interface is available at `/graphql` on the API server.

## Queries

### `events` - List webhook events

```graphql
query {
events(
type: String
repository: String
sender: String
status: String
since: DateTime
until: DateTime
limit: Int
offset: Int
) {
events {
id
type
payload
createdAt
status
error
repository
sender
replayedFrom
originalTime
}
total
}
}
```

### `event` - Get a single webhook event by ID

```graphql
query {
event(id: "event-id") {
id
type
payload
createdAt
status
error
repository
sender
replayedFrom
originalTime
}
}
```

### `stats` - Get webhook event statistics

```graphql
query {
stats(since: "2023-01-01T00:00:00Z") {
type
count
}
}
```

## Mutations

### `replayEvent` - Replay a single webhook event

```graphql
mutation {
replayEvent(id: "event-id") {
replayedCount
events {
id
type
payload
createdAt
status
repository
sender
replayedFrom
originalTime
}
}
}
```

### `replayRange` - Replay multiple webhook events within a time range

```graphql
mutation {
replayRange(
since: "2023-01-01T00:00:00Z"
until: "2023-01-02T00:00:00Z"
type: String
repository: String
sender: String
limit: Int
) {
replayedCount
events {
id
type
payload
createdAt
status
repository
sender
replayedFrom
originalTime
}
}
}
```

## Interactive Tools

The GraphQL endpoint includes:

- **GraphiQL**: An interactive in-browser GraphQL IDE
- **Playground**: An alternative GraphQL IDE

These tools are available directly at the `/graphql` endpoint when accessed from a browser.
Loading
Loading