Skip to content

Commit 0933293

Browse files
authored
feat: github sponsors webhook ingress (#1061)
* feat(github): add github sponsors webhook metadata Signed-off-by: Xe Iaso <me@xeiaso.net> * chore(pb): format generated code Signed-off-by: Xe Iaso <me@xeiaso.net> * chore: go fmt ./... Signed-off-by: Xe Iaso <me@xeiaso.net> * docs(AGENTS): tell AI agents to format code before committing it Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(xesite): expose github sponsors webhook Signed-off-by: Xe Iaso <me@xeiaso.net> * chore: add xesitectl binary to gitignore * deps: add github.com/google/subcommands dependency * feat: add xesitectl command for testing GitHub Sponsors webhooks - Create xesitectl command using github.com/google/subcommands framework - Implement test-webhook subcommand with comprehensive options - Support all GitHub Sponsors event types (created, edited, cancelled, etc.) - Load configuration from environment variables with godotenv/autoload - Generate realistic mock sponsorship events with proper HMAC signatures - Support custom webhook URLs, secrets, and sponsorship parameters - Add comprehensive help documentation and examples - Enable verbose logging for debugging webhook requests Usage: xesitectl test-webhook -action created -sponsor testsponsor -tier "Pro Tier" xesitectl test-webhook -action cancelled -sponsor oldsponsor -url https://myapp.com/hook * docs: add .env.example for xesitectl configuration Example environment variables for xesitectl test-webhook command: - GITHUB_SPONSORS_SECRET: Webhook secret for HMAC signatures - WEBHOOK_URL: Custom webhook endpoint URL - TEST_SPONSOR: Default sponsor login name - TEST_TIER: Default sponsorship tier name - TEST_PRICE: Default monthly price in dollars * chore: move github sponsors webhook to its own service Signed-off-by: Xe Iaso <me@xeiaso.net> * feat: add GORM models Signed-off-by: Xe Iaso <me@xeiaso.net> * chore(manifest): introduce github sponsor webhook manifests Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(sponsor-webhook): setup database connection Signed-off-by: Xe Iaso <me@xeiaso.net> * feat(github-webhook): kinda terrible automatic writing to the database on ingress Signed-off-by: Xe Iaso <me@xeiaso.net> --------- Signed-off-by: Xe Iaso <me@xeiaso.net>
1 parent 3cdac09 commit 0933293

35 files changed

Lines changed: 1908 additions & 129 deletions

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Test environment variables for xesitectl
2+
GITHUB_SPONSORS_SECRET=test-secret-from-env
3+
WEBHOOK_URL=http://localhost:3000/.within/hook/github-sponsors
4+
TEST_SPONSOR=envtestsponsor
5+
TEST_TIER=Env Tier
6+
TEST_PRICE=25

.github/workflows/earthly.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: Docker
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
# Publish semver tags as releases.
7-
tags: [ 'v*.*.*' ]
7+
tags: ["v*.*.*"]
88
pull_request:
9-
branches: [ "main" ]
9+
branches: ["main"]
1010

1111
jobs:
1212
build:
@@ -21,7 +21,7 @@ jobs:
2121
- name: Set up Docker Buildx
2222
uses: docker/setup-buildx-action@v3
2323

24-
- name: Log into registry
24+
- name: Log into registry
2525
if: github.event_name != 'pull_request'
2626
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
2727
with:
@@ -37,6 +37,7 @@ jobs:
3737
source: .
3838
push: true
3939
set: |
40+
github-sponsor-webhook.tags=ghcr.io/xe/site/github-sponsor-webhook:latest
4041
patreon-saasproxy.tags=ghcr.io/xe/site/patreon-saasproxy:latest
4142
xesite.tags=ghcr.io/xe/site/bin:latest
4243
@@ -47,7 +48,6 @@ jobs:
4748
source: .
4849
push: false
4950
set: |
51+
github-sponsor-webhook.tags=ghcr.io/xe/site/github-sponsor-webhook:latest
5052
patreon-saasproxy.tags=ghcr.io/xe/site/patreon-saasproxy:latest
5153
xesite.tags=ghcr.io/xe/site/bin:latest
52-
53-

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ cw.tar
99
.patreon.json
1010
.direnv
1111
node_modules
12+
/xesite
13+
/xesitectl
14+
/github-sponsor-webhook

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
## Coding Style & Naming Conventions
2020
- Go code follows `gofmt`; run `go fmt ./...` before committing.
21+
- Use `goimports` to organize imports: `go get -tool golang.org/x/tools/cmd/goimports@latest` then `find . -name "*.go" -exec goimports -w {} \;`.
2122
- Use `camelCase` for variables/functions, `PascalCase` for exported types.
2223
- Indentation: tabs (default `go fmt`).
2324
- Dhall files use kebab‑case filenames (e.g., `my-config.dhall`).

cmd/github-sponsor-webhook/main.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"log"
7+
"log/slog"
8+
"net"
9+
"net/http"
10+
"os"
11+
12+
"github.com/donatj/hmacsig"
13+
"github.com/facebookgo/flagenv"
14+
_ "github.com/joho/godotenv/autoload"
15+
"github.com/prometheus/client_golang/prometheus/promhttp"
16+
"gorm.io/driver/postgres"
17+
"gorm.io/gorm"
18+
"xeiaso.net/v4/internal"
19+
"xeiaso.net/v4/internal/models"
20+
)
21+
22+
var (
23+
bind = flag.String("bind", ":4823", "Port to listen on")
24+
databaseURL = flag.String("database-url", "", "Database URL")
25+
githubSponsorsSecret = flag.String("github-sponsors-secret", "", "GitHub Sponsors secret to use for webhooks")
26+
)
27+
28+
func main() {
29+
flagenv.Parse()
30+
flag.Parse()
31+
internal.Slog()
32+
33+
_ = context.Background()
34+
35+
ln, err := net.Listen("tcp", *bind)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
40+
if *databaseURL == "" {
41+
slog.Error("database-url is required")
42+
os.Exit(1)
43+
}
44+
45+
if *githubSponsorsSecret == "" {
46+
slog.Error("github-sponsors-secret is required")
47+
os.Exit(1)
48+
}
49+
50+
slog.Info("starting GitHub Sponsors webhook service",
51+
"bind", *bind,
52+
)
53+
54+
db, err := gorm.Open(postgres.Open(*databaseURL), &gorm.Config{})
55+
if err != nil {
56+
slog.Error("can't connect to database", "err", err)
57+
os.Exit(1)
58+
}
59+
60+
if err := db.Exec("SELECT 1 + 1").Error; err != nil {
61+
slog.Error("can't ping database", "err", err)
62+
os.Exit(1)
63+
}
64+
65+
if err := models.SetupDatabase(db); err != nil {
66+
slog.Error("database setup error", "err", err)
67+
os.Exit(1)
68+
}
69+
70+
gsh := &GitHubSponsorsWebhook{DB: db}
71+
s := hmacsig.Handler256(gsh, *githubSponsorsSecret)
72+
73+
mux := http.NewServeMux()
74+
mux.Handle("/.within/hook/github-sponsors", s)
75+
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
76+
w.Header().Set("Content-Type", "application/json")
77+
w.WriteHeader(http.StatusOK)
78+
w.Write([]byte(`{"status":"ok","service":"github-sponsor-webhook"}`))
79+
})
80+
81+
// Expose Prometheus metrics at /metrics for observability
82+
mux.Handle("/metrics", promhttp.Handler())
83+
84+
var h http.Handler = mux
85+
h = internal.CacheHeader(h)
86+
h = internal.AcceptEncodingMiddleware(h)
87+
h = internal.RefererMiddleware(h)
88+
89+
slog.Info("GitHub Sponsors webhook service ready", "bind", *bind)
90+
log.Fatal(http.Serve(ln, h))
91+
}

0 commit comments

Comments
 (0)