diff --git a/file.go b/file.go index 4ac0acb..2cea314 100644 --- a/file.go +++ b/file.go @@ -61,7 +61,7 @@ func (f *FileBackup) MigrateRepo(owner, repoOwner string, isOwnerOrg, isRepoOwne DefaultBranch string `json:"default_branch"` UpdateAt string `json:"updated_at"` } - err := GithubRequestJson("GET", url, githubToken, &info) + err := GithubRequestJson("GET", url, githubToken, nil, &info) if err != nil { return "", err } @@ -93,7 +93,7 @@ func (f *FileBackup) MigrateRepo(owner, repoOwner string, isOwnerOrg, isRepoOwne return "", wErr } } else { - dErr := GithubRequest("GET", url, githubToken, func(resp *http.Response) error { + dErr := GithubRequest("GET", url, githubToken, nil, func(resp *http.Response) error { tarFile, wErr := os.Create(filePath) if wErr != nil { return wErr diff --git a/github.go b/github.go index 2d526cd..685eb51 100644 --- a/github.go +++ b/github.go @@ -1,18 +1,19 @@ package main import ( + "bytes" "encoding/json" "fmt" + "io" "net/http" - "strings" ) type Repo struct { Name string `json:"name"` Description string `json:"description"` - Private bool `json:"private"` - Fork bool `json:"fork"` - Archived bool `json:"archived"` + Private bool `json:"isPrivate"` + Fork bool `json:"isFork"` + Archived bool `json:"isArchived"` Owner struct { Login string `json:"login"` } @@ -26,99 +27,90 @@ func NewGithub(token string) *Github { return &Github{Token: token} } -func filterRepos(owner string, res []Repo) map[string]Repo { - matchedRepos := make(map[string]Repo) - ownerLower := strings.ToLower(owner) - for _, repo := range res { - if strings.ToLower(repo.Owner.Login) != ownerLower { - continue - } - matchedRepos[repo.Name] = repo - } - return matchedRepos +func (g *Github) LoadAllRepos(owner string, isOrg bool) ([]Repo, error) { + tmpl := ` +query { + %s { + repositories( + first: 100, + after: %s + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + name + description + isPrivate + isFork + isArchived + owner { + login + } + } + } + } } - -func (g *Github) loadReposPageBySearch(owner string, perPage, page int, isOrg bool) (map[string]Repo, error) { - url := "search/repositories" +` + next := "null" + queryType := "" + dataKey := "" + var repos []Repo if isOrg { - url = fmt.Sprintf("%s?q=org%s&page=%d&per_page=%d", url, "%3A"+owner, page, perPage) + dataKey = "organization" + queryType = fmt.Sprintf("organization(login: \"%s\")", owner) } else { - url = fmt.Sprintf("%s?q=user%s&page=%d&per_page=%d", url, "%3A"+owner, page, perPage) - } - var res struct { - Items []Repo `json:"items"` - } - err := GithubRequestJson("GET", url, g.Token, &res) - if err != nil { - return nil, err - } - return filterRepos(owner, res.Items), nil - -} - -func (g *Github) loadReposPage(owner string, perPage, page int, isOrg bool) (map[string]Repo, error) { - url := fmt.Sprintf("users/%s/repos", owner) - if isOrg { - url = fmt.Sprintf("orgs/%s/repos", owner) - } - url = fmt.Sprintf("%s?per_page=%d&page=%d&type=all", url, perPage, page) - var res []Repo - err := GithubRequestJson("GET", url, g.Token, &res) - if err != nil { - return nil, err + dataKey = "repositoryOwner" + queryType = fmt.Sprintf("repositoryOwner(login: \"%s\")", owner) } - return filterRepos(owner, res), nil -} - -func (g *Github) LoadAllRepos(owner string, isOrg bool) ([]Repo, error) { - page := 1 - perPage := 100 - res := make(map[string]Repo) for { - repos, err := g.loadReposPageBySearch(owner, perPage, page, isOrg) + var data struct { + Data map[string]struct { + Repositories struct { + PageInfo struct { + HasNextPage bool `json:"hasNextPage"` + EndCursor string `json:"endCursor"` + } `json:"pageInfo"` + Nodes []Repo `json:"nodes"` + } `json:"repositories"` + } `json:"data"` + } + query := fmt.Sprintf(tmpl, queryType, next) + rawBody, err := json.Marshal(map[string]any{"query": query}) if err != nil { return nil, err } - if len(repos) == 0 { - break - } - for k, v := range repos { - res[k] = v - } - page++ - } - page = 1 - for { - repos, err := g.loadReposPage(owner, perPage, page, isOrg) + err = GithubRequestJson("POST", "graphql", g.Token, bytes.NewReader(rawBody), &data) if err != nil { return nil, err } - if len(repos) == 0 { - break + info, ok := data.Data[dataKey] + if !ok { + return nil, fmt.Errorf("no data key %s", dataKey) } - for k, v := range repos { - res[k] = v + for _, repo := range info.Repositories.Nodes { + if repo.Owner.Login == owner { + repos = append(repos, repo) + } } - page++ - } - var resSlice []Repo - for _, v := range res { - resSlice = append(resSlice, v) + if !info.Repositories.PageInfo.HasNextPage { + break + } + next = fmt.Sprintf(`"%s"`, info.Repositories.PageInfo.EndCursor) } - return resSlice, nil + return repos, nil } -func GithubRequest(method, path, token string, handler func(*http.Response) error) error { +func GithubRequest(method, path, token string, body io.Reader, handler func(*http.Response) error) error { url := fmt.Sprintf("https://api.github.com/%s", path) - req, err := http.NewRequest(method, url, nil) + req, err := http.NewRequest(method, url, body) if err != nil { return err } if token != "" { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) } - req.Header.Add("Accept", "application/vnd.github.v3+json") - req.Header.Add("X-GitHub-Api-Version", "2022-11-28") resp, err := http.DefaultClient.Do(req) if err != nil { return err @@ -130,8 +122,8 @@ func GithubRequest(method, path, token string, handler func(*http.Response) erro return handler(resp) } -func GithubRequestJson[T any](method, path, token string, res *T) error { - return GithubRequest(method, path, token, func(resp *http.Response) error { +func GithubRequestJson[T any](method, path, token string, body io.Reader, res *T) error { + return GithubRequest(method, path, token, body, func(resp *http.Response) error { return json.NewDecoder(resp.Body).Decode(res) }) } diff --git a/github_test.go b/github_test.go new file mode 100644 index 0000000..e84d542 --- /dev/null +++ b/github_test.go @@ -0,0 +1,27 @@ +package main + +import ( + "os" + "testing" +) + +func TestGithub_LoadAllRepos(t *testing.T) { + github := NewGithub(os.Getenv("GITHUB_TOKEN")) + repos, err := github.LoadAllRepos("TBXark", false) + if err != nil { + t.Fatal(err) + } + t.Logf("repos count: %d", len(repos)) + for _, repo := range repos { + t.Log(repo.Name) + } + + repos, err = github.LoadAllRepos("tbxark-arc", true) + if err != nil { + t.Fatal(err) + } + t.Logf("repos count: %d", len(repos)) + for _, repo := range repos { + t.Log(repo.Name) + } +}