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  }