github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/doghouse/appengine/github_webhook.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 9 "github.com/google/go-github/v39/github" 10 11 "github.com/mistwind/reviewdog/doghouse/server/storage" 12 ) 13 14 type githubWebhookHandler struct { 15 secret []byte 16 ghInstStore storage.GitHubInstallationStore 17 } 18 19 func (g *githubWebhookHandler) handleWebhook(w http.ResponseWriter, r *http.Request) { 20 if r.Method != http.MethodPost { 21 w.WriteHeader(http.StatusMethodNotAllowed) 22 return 23 } 24 ctx := r.Context() 25 payload, err := g.validatePayload(r) 26 if err != nil { 27 w.WriteHeader(http.StatusUnauthorized) 28 return 29 } 30 var handleFunc func(ctx context.Context, payload []byte) (status int, err error) 31 switch github.WebHookType(r) { 32 case "installation": 33 handleFunc = g.handleInstallationEvent 34 case "check_suite": 35 handleFunc = g.handleCheckSuiteEvent 36 } 37 if handleFunc != nil { 38 status, err := handleFunc(ctx, payload) 39 if err != nil { 40 w.WriteHeader(http.StatusInternalServerError) 41 fmt.Fprintf(w, "failed to handle %s event: %v", github.WebHookType(r), err) 42 return 43 } 44 w.WriteHeader(status) 45 fmt.Fprintf(w, "resource created. event: %s", github.WebHookType(r)) 46 return 47 } 48 w.WriteHeader(http.StatusAccepted) 49 } 50 51 func (g *githubWebhookHandler) validatePayload(r *http.Request) (payload []byte, err error) { 52 return github.ValidatePayload(r, g.secret) 53 } 54 55 func (g *githubWebhookHandler) handleCheckSuiteEvent(ctx context.Context, payload []byte) (status int, err error) { 56 var c CheckSuiteEvent 57 if err := json.Unmarshal(payload, &c); err != nil { 58 return 0, err 59 } 60 switch c.Action { 61 case "requested": 62 // Update InstallationID on check_suite event in case the users re-install 63 // the app. 64 return http.StatusCreated, g.ghInstStore.Put(ctx, &storage.GitHubInstallation{ 65 InstallationID: c.Installation.ID, 66 AccountName: c.Repository.Owner.Login, 67 AccountID: c.Repository.Owner.ID, 68 }) 69 } 70 return http.StatusAccepted, nil 71 } 72 73 func (g *githubWebhookHandler) handleInstallationEvent(ctx context.Context, payload []byte) (status int, err error) { 74 var e InstallationEvent 75 if err := json.Unmarshal(payload, &e); err != nil { 76 return 0, err 77 } 78 switch e.Action { 79 case "created": 80 return http.StatusCreated, g.ghInstStore.Put(ctx, &storage.GitHubInstallation{ 81 InstallationID: e.Installation.ID, 82 AccountName: e.Installation.Account.Login, 83 AccountID: e.Installation.Account.ID, 84 }) 85 } 86 return http.StatusAccepted, nil 87 } 88 89 // Example: https://gist.github.com/haya14busa/7a9a87da5159d6853fed865ca5ad5ec7 90 type InstallationEvent struct { 91 Action string `json:"action,omitempty"` 92 Installation struct { 93 ID int64 `json:"id,omitempty"` 94 Account struct { 95 Login string `json:"login"` 96 ID int64 `json:"id"` 97 } `json:"account"` 98 } `json:"installation,omitempty"` 99 } 100 101 // Example: https://gist.github.com/haya14busa/2aaffaa89a224ee2ffcbd3d414d6d009 102 type CheckSuiteEvent struct { 103 Action string `json:"action,omitempty"` 104 Repository struct { 105 ID int64 `json:"id,omitempty"` 106 FullName string `json:"full_name,omitempty"` 107 Owner struct { 108 Login string `json:"login"` 109 ID int64 `json:"id"` 110 } `json:"owner"` 111 } `json:"repository,omitempty"` 112 Installation struct { 113 ID int64 `json:"id,omitempty"` 114 } `json:"installation,omitempty"` 115 }