Skip to content

Instantly share code, notes, and snippets.

@UNC1739
Created October 14, 2024 17:42
Show Gist options
  • Select an option

  • Save UNC1739/f0803fe65d3db81cae92a3a6e7ed8c61 to your computer and use it in GitHub Desktop.

Select an option

Save UNC1739/f0803fe65d3db81cae92a3a6e7ed8c61 to your computer and use it in GitHub Desktop.
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