Created
October 14, 2024 17:42
-
-
Save UNC1739/f0803fe65d3db81cae92a3a6e7ed8c61 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net/http" | |
| "os" | |
| "strings" | |
| "golang.org/x/oauth2" | |
| "golang.org/x/oauth2/github" | |
| ) | |
| var ( | |
| githubOauthConfig = &oauth2.Config{ | |
| ClientID: os.Getenv("GITHUB_CLIENT_ID"), | |
| ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"), | |
| Endpoint: github.Endpoint, | |
| RedirectURL: "http://localhost:8080/callback", | |
| Scopes: []string{"read:user", "user:email", "read:org", "repo"}, | |
| } | |
| // In production, generate a random state string per request | |
| oauthStateString = "pseudo-random" | |
| ) | |
| type Organization struct { | |
| Login string `json:"login"` | |
| } | |
| type Repository struct { | |
| Name string `json:"name"` | |
| FullName string `json:"full_name"` | |
| HTMLURL string `json:"html_url"` | |
| Description string `json:"description"` | |
| } | |
| func main() { | |
| http.HandleFunc("/", handleHome) | |
| http.HandleFunc("/login", handleLogin) | |
| http.HandleFunc("/callback", handleCallback) | |
| fmt.Println("Server started at http://localhost:8080") | |
| log.Fatal(http.ListenAndServe(":8080", nil)) | |
| } | |
| func handleHome(w http.ResponseWriter, r *http.Request) { | |
| html := `<html><body><a href="/login">Log in with GitHub</a></body></html>` | |
| fmt.Fprint(w, html) | |
| } | |
| func handleLogin(w http.ResponseWriter, r *http.Request) { | |
| url := githubOauthConfig.AuthCodeURL(oauthStateString) | |
| http.Redirect(w, r, url, http.StatusTemporaryRedirect) | |
| } | |
| func handleCallback(w http.ResponseWriter, r *http.Request) { | |
| // Validate state | |
| state := r.FormValue("state") | |
| if state != oauthStateString { | |
| log.Println("Invalid OAuth state") | |
| http.Redirect(w, r, "/", http.StatusTemporaryRedirect) | |
| return | |
| } | |
| // Exchange code for access token | |
| code := r.FormValue("code") | |
| token, err := githubOauthConfig.Exchange(context.Background(), code) | |
| if err != nil { | |
| log.Printf("Token exchange failed: %s", err.Error()) | |
| http.Redirect(w, r, "/", http.StatusTemporaryRedirect) | |
| return | |
| } | |
| // Create a client with the access token | |
| client := githubOauthConfig.Client(context.Background(), token) | |
| // Fetch user organizations | |
| orgs, err := getUserOrganizations(client) | |
| if err != nil { | |
| log.Printf("Failed to get user organizations: %s", err.Error()) | |
| http.Error(w, "Failed to get organizations", http.StatusInternalServerError) | |
| return | |
| } | |
| // Prepare HTML output | |
| fmt.Fprintf(w, "<html><body>") | |
| fmt.Fprintf(w, "<h1>Organizations and Repositories</h1>") | |
| for _, org := range orgs { | |
| fmt.Fprintf(w, "<h2>Organization: %s</h2>", org.Login) | |
| // Fetch repositories for the organization | |
| repos, err := getOrganizationRepositories(client, org.Login) | |
| if err != nil { | |
| log.Printf("Failed to get repositories for org %s: %s", org.Login, err.Error()) | |
| fmt.Fprintf(w, "<p>Failed to get repositories for organization %s</p>", org.Login) | |
| continue | |
| } | |
| // List repositories | |
| if len(repos) > 0 { | |
| fmt.Fprintf(w, "<ul>") | |
| for _, repo := range repos { | |
| fmt.Fprintf(w, `<li><a href="%s">%s</a>: %s</li>`, repo.HTMLURL, repo.FullName, repo.Description) | |
| } | |
| fmt.Fprintf(w, "</ul>") | |
| } else { | |
| fmt.Fprintf(w, "<p>No repositories found for this organization.</p>") | |
| } | |
| } | |
| // Fetch user-owned repositories | |
| userRepos, err := getUserRepositories(client) | |
| if err != nil { | |
| log.Printf("Failed to get user repositories: %s", err.Error()) | |
| http.Error(w, "Failed to get user repositories", http.StatusInternalServerError) | |
| return | |
| } | |
| fmt.Fprintf(w, "<h2>Your Repositories</h2>") | |
| if len(userRepos) > 0 { | |
| fmt.Fprintf(w, "<ul>") | |
| for _, repo := range userRepos { | |
| fmt.Fprintf(w, `<li><a href="%s">%s</a>: %s</li>`, repo.HTMLURL, repo.FullName, repo.Description) | |
| } | |
| fmt.Fprintf(w, "</ul>") | |
| } else { | |
| fmt.Fprintf(w, "<p>No repositories found for your account.</p>") | |
| } | |
| fmt.Fprintf(w, "</body></html>") | |
| } | |
| func getUserOrganizations(client *http.Client) ([]Organization, error) { | |
| var allOrgs []Organization | |
| url := "https://api.github.com/user/orgs" | |
| for url != "" { | |
| resp, err := client.Get(url) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| body, _ := io.ReadAll(resp.Body) | |
| return nil, fmt.Errorf("failed to get orgs: status %d, body: %s", resp.StatusCode, body) | |
| } | |
| var orgs []Organization | |
| if err := json.NewDecoder(resp.Body).Decode(&orgs); err != nil { | |
| return nil, err | |
| } | |
| allOrgs = append(allOrgs, orgs...) | |
| // Get next page URL from Link header | |
| url = getNextPageURL(resp.Header.Get("Link")) | |
| } | |
| return allOrgs, nil | |
| } | |
| func getOrganizationRepositories(client *http.Client, org string) ([]Repository, error) { | |
| var allRepos []Repository | |
| url := fmt.Sprintf("https://api.github.com/orgs/%s/repos", org) | |
| for url != "" { | |
| resp, err := client.Get(url) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| body, _ := io.ReadAll(resp.Body) | |
| return nil, fmt.Errorf("failed to get repos for org %s: status %d, body: %s", org, resp.StatusCode, body) | |
| } | |
| var repos []Repository | |
| if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { | |
| return nil, err | |
| } | |
| allRepos = append(allRepos, repos...) | |
| // Get next page URL from Link header | |
| url = getNextPageURL(resp.Header.Get("Link")) | |
| } | |
| return allRepos, nil | |
| } | |
| func getUserRepositories(client *http.Client) ([]Repository, error) { | |
| var allRepos []Repository | |
| url := "https://api.github.com/user/repos" | |
| for url != "" { | |
| resp, err := client.Get(url) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| body, _ := io.ReadAll(resp.Body) | |
| return nil, fmt.Errorf("failed to get user repos: status %d, body: %s", resp.StatusCode, body) | |
| } | |
| var repos []Repository | |
| if err := json.NewDecoder(resp.Body).Decode(&repos); err != nil { | |
| return nil, err | |
| } | |
| allRepos = append(allRepos, repos...) | |
| // Get next page URL from Link header | |
| url = getNextPageURL(resp.Header.Get("Link")) | |
| } | |
| return allRepos, nil | |
| } | |
| func getNextPageURL(linkHeader string) string { | |
| links := strings.Split(linkHeader, ",") | |
| for _, link := range links { | |
| parts := strings.Split(strings.TrimSpace(link), ";") | |
| if len(parts) < 2 { | |
| continue | |
| } | |
| if strings.Contains(parts[1], `rel="next"`) { | |
| return strings.Trim(parts[0], "<>") | |
| } | |
| } | |
| return "" | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment