github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/repo/list/http.go (about)

     1  package list
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/shurcooL/githubv4"
     9  
    10  	"github.com/ungtb10d/cli/v2/api"
    11  	"github.com/ungtb10d/cli/v2/pkg/search"
    12  )
    13  
    14  type RepositoryList struct {
    15  	Owner        string
    16  	Repositories []api.Repository
    17  	TotalCount   int
    18  	FromSearch   bool
    19  }
    20  
    21  type FilterOptions struct {
    22  	Visibility  string // private, public, internal
    23  	Fork        bool
    24  	Source      bool
    25  	Language    string
    26  	Topic       []string
    27  	Archived    bool
    28  	NonArchived bool
    29  	Fields      []string
    30  }
    31  
    32  func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
    33  	if filter.Language != "" || filter.Archived || filter.NonArchived || len(filter.Topic) > 0 || filter.Visibility == "internal" {
    34  		return searchRepos(client, hostname, limit, owner, filter)
    35  	}
    36  
    37  	perPage := limit
    38  	if perPage > 100 {
    39  		perPage = 100
    40  	}
    41  
    42  	variables := map[string]interface{}{
    43  		"perPage": githubv4.Int(perPage),
    44  	}
    45  
    46  	if filter.Visibility != "" {
    47  		variables["privacy"] = githubv4.RepositoryPrivacy(strings.ToUpper(filter.Visibility))
    48  	}
    49  
    50  	if filter.Fork {
    51  		variables["fork"] = githubv4.Boolean(true)
    52  	} else if filter.Source {
    53  		variables["fork"] = githubv4.Boolean(false)
    54  	}
    55  
    56  	inputs := []string{"$perPage:Int!", "$endCursor:String", "$privacy:RepositoryPrivacy", "$fork:Boolean"}
    57  	var ownerConnection string
    58  	if owner == "" {
    59  		ownerConnection = "repositoryOwner: viewer"
    60  	} else {
    61  		ownerConnection = "repositoryOwner(login: $owner)"
    62  		variables["owner"] = githubv4.String(owner)
    63  		inputs = append(inputs, "$owner:String!")
    64  	}
    65  
    66  	type result struct {
    67  		RepositoryOwner struct {
    68  			Login        string
    69  			Repositories struct {
    70  				Nodes      []api.Repository
    71  				TotalCount int
    72  				PageInfo   struct {
    73  					HasNextPage bool
    74  					EndCursor   string
    75  				}
    76  			}
    77  		}
    78  	}
    79  
    80  	query := fmt.Sprintf(`query RepositoryList(%s) {
    81  		%s {
    82  			login
    83  			repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, affiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC }) {
    84  				nodes{%s}
    85  				totalCount
    86  				pageInfo{hasNextPage,endCursor}
    87  			}
    88  		}
    89  	}`, strings.Join(inputs, ","), ownerConnection, api.RepositoryGraphQL(filter.Fields))
    90  
    91  	apiClient := api.NewClientFromHTTP(client)
    92  	listResult := RepositoryList{}
    93  pagination:
    94  	for {
    95  		var res result
    96  		err := apiClient.GraphQL(hostname, query, variables, &res)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  
   101  		owner := res.RepositoryOwner
   102  		listResult.TotalCount = owner.Repositories.TotalCount
   103  		listResult.Owner = owner.Login
   104  
   105  		for _, repo := range owner.Repositories.Nodes {
   106  			listResult.Repositories = append(listResult.Repositories, repo)
   107  			if len(listResult.Repositories) >= limit {
   108  				break pagination
   109  			}
   110  		}
   111  
   112  		if !owner.Repositories.PageInfo.HasNextPage {
   113  			break
   114  		}
   115  		variables["endCursor"] = githubv4.String(owner.Repositories.PageInfo.EndCursor)
   116  	}
   117  
   118  	return &listResult, nil
   119  }
   120  
   121  func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
   122  	type result struct {
   123  		Search struct {
   124  			RepositoryCount int
   125  			Nodes           []api.Repository
   126  			PageInfo        struct {
   127  				HasNextPage bool
   128  				EndCursor   string
   129  			}
   130  		}
   131  	}
   132  
   133  	query := fmt.Sprintf(`query RepositoryListSearch($query:String!,$perPage:Int!,$endCursor:String) {
   134  		search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor) {
   135  			repositoryCount
   136  			nodes{...on Repository{%s}}
   137  			pageInfo{hasNextPage,endCursor}
   138  		}
   139  	}`, api.RepositoryGraphQL(filter.Fields))
   140  
   141  	perPage := limit
   142  	if perPage > 100 {
   143  		perPage = 100
   144  	}
   145  
   146  	variables := map[string]interface{}{
   147  		"query":   githubv4.String(searchQuery(owner, filter)),
   148  		"perPage": githubv4.Int(perPage),
   149  	}
   150  
   151  	apiClient := api.NewClientFromHTTP(client)
   152  	listResult := RepositoryList{FromSearch: true}
   153  pagination:
   154  	for {
   155  		var result result
   156  		err := apiClient.GraphQL(hostname, query, variables, &result)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		listResult.TotalCount = result.Search.RepositoryCount
   162  		for _, repo := range result.Search.Nodes {
   163  			if listResult.Owner == "" && repo.NameWithOwner != "" {
   164  				idx := strings.IndexRune(repo.NameWithOwner, '/')
   165  				listResult.Owner = repo.NameWithOwner[:idx]
   166  			}
   167  			listResult.Repositories = append(listResult.Repositories, repo)
   168  			if len(listResult.Repositories) >= limit {
   169  				break pagination
   170  			}
   171  		}
   172  
   173  		if !result.Search.PageInfo.HasNextPage {
   174  			break
   175  		}
   176  		variables["endCursor"] = githubv4.String(result.Search.PageInfo.EndCursor)
   177  	}
   178  
   179  	return &listResult, nil
   180  }
   181  
   182  func searchQuery(owner string, filter FilterOptions) string {
   183  	if owner == "" {
   184  		owner = "@me"
   185  	}
   186  
   187  	fork := "true"
   188  	if filter.Fork {
   189  		fork = "only"
   190  	} else if filter.Source {
   191  		fork = "false"
   192  	}
   193  
   194  	var archived *bool
   195  	if filter.Archived {
   196  		trueBool := true
   197  		archived = &trueBool
   198  	}
   199  	if filter.NonArchived {
   200  		falseBool := false
   201  		archived = &falseBool
   202  	}
   203  
   204  	q := search.Query{
   205  		Keywords: []string{"sort:updated-desc"},
   206  		Qualifiers: search.Qualifiers{
   207  			Archived: archived,
   208  			Fork:     fork,
   209  			Is:       []string{filter.Visibility},
   210  			Language: filter.Language,
   211  			Topic:    filter.Topic,
   212  			User:     owner,
   213  		},
   214  	}
   215  
   216  	return q.String()
   217  }