github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/repo/list/http.go (about)

     1  package list
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"reflect"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/abdfnx/gh-api/internal/ghinstance"
    11  	"github.com/abdfnx/gh-api/pkg/githubsearch"
    12  	"github.com/shurcooL/githubv4"
    13  	"github.com/shurcooL/graphql"
    14  )
    15  
    16  type Repository struct {
    17  	NameWithOwner string
    18  	Description   string
    19  	IsFork        bool
    20  	IsPrivate     bool
    21  	IsArchived    bool
    22  	PushedAt      time.Time
    23  }
    24  
    25  func (r Repository) Info() string {
    26  	var tags []string
    27  
    28  	if r.IsPrivate {
    29  		tags = append(tags, "private")
    30  	} else {
    31  		tags = append(tags, "public")
    32  	}
    33  	if r.IsFork {
    34  		tags = append(tags, "fork")
    35  	}
    36  	if r.IsArchived {
    37  		tags = append(tags, "archived")
    38  	}
    39  
    40  	return strings.Join(tags, ", ")
    41  }
    42  
    43  type RepositoryList struct {
    44  	Owner        string
    45  	Repositories []Repository
    46  	TotalCount   int
    47  	FromSearch   bool
    48  }
    49  
    50  type FilterOptions struct {
    51  	Visibility  string // private, public
    52  	Fork        bool
    53  	Source      bool
    54  	Language    string
    55  	Archived    bool
    56  	NonArchived bool
    57  }
    58  
    59  func listRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
    60  	if filter.Language != "" || filter.Archived || filter.NonArchived {
    61  		return searchRepos(client, hostname, limit, owner, filter)
    62  	}
    63  
    64  	perPage := limit
    65  	if perPage > 100 {
    66  		perPage = 100
    67  	}
    68  
    69  	variables := map[string]interface{}{
    70  		"perPage":   githubv4.Int(perPage),
    71  		"endCursor": (*githubv4.String)(nil),
    72  	}
    73  
    74  	if filter.Visibility != "" {
    75  		variables["privacy"] = githubv4.RepositoryPrivacy(strings.ToUpper(filter.Visibility))
    76  	} else {
    77  		variables["privacy"] = (*githubv4.RepositoryPrivacy)(nil)
    78  	}
    79  
    80  	if filter.Fork {
    81  		variables["fork"] = githubv4.Boolean(true)
    82  	} else if filter.Source {
    83  		variables["fork"] = githubv4.Boolean(false)
    84  	} else {
    85  		variables["fork"] = (*githubv4.Boolean)(nil)
    86  	}
    87  
    88  	var ownerConnection string
    89  	if owner == "" {
    90  		ownerConnection = `graphql:"repositoryOwner: viewer"`
    91  	} else {
    92  		ownerConnection = `graphql:"repositoryOwner(login: $owner)"`
    93  		variables["owner"] = githubv4.String(owner)
    94  	}
    95  
    96  	type repositoryOwner struct {
    97  		Login        string
    98  		Repositories struct {
    99  			Nodes      []Repository
   100  			TotalCount int
   101  			PageInfo   struct {
   102  				HasNextPage bool
   103  				EndCursor   string
   104  			}
   105  		} `graphql:"repositories(first: $perPage, after: $endCursor, privacy: $privacy, isFork: $fork, ownerAffiliations: OWNER, orderBy: { field: PUSHED_AT, direction: DESC })"`
   106  	}
   107  	query := reflect.StructOf([]reflect.StructField{
   108  		{
   109  			Name: "RepositoryOwner",
   110  			Type: reflect.TypeOf(repositoryOwner{}),
   111  			Tag:  reflect.StructTag(ownerConnection),
   112  		},
   113  	})
   114  
   115  	gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
   116  	listResult := RepositoryList{}
   117  pagination:
   118  	for {
   119  		result := reflect.New(query)
   120  		err := gql.QueryNamed(context.Background(), "RepositoryList", result.Interface(), variables)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		owner := result.Elem().FieldByName("RepositoryOwner").Interface().(repositoryOwner)
   126  		listResult.TotalCount = owner.Repositories.TotalCount
   127  		listResult.Owner = owner.Login
   128  
   129  		for _, repo := range owner.Repositories.Nodes {
   130  			listResult.Repositories = append(listResult.Repositories, repo)
   131  			if len(listResult.Repositories) >= limit {
   132  				break pagination
   133  			}
   134  		}
   135  
   136  		if !owner.Repositories.PageInfo.HasNextPage {
   137  			break
   138  		}
   139  		variables["endCursor"] = githubv4.String(owner.Repositories.PageInfo.EndCursor)
   140  	}
   141  
   142  	return &listResult, nil
   143  }
   144  
   145  func searchRepos(client *http.Client, hostname string, limit int, owner string, filter FilterOptions) (*RepositoryList, error) {
   146  	type query struct {
   147  		Search struct {
   148  			RepositoryCount int
   149  			Nodes           []struct {
   150  				Repository Repository `graphql:"...on Repository"`
   151  			}
   152  			PageInfo struct {
   153  				HasNextPage bool
   154  				EndCursor   string
   155  			}
   156  		} `graphql:"search(type: REPOSITORY, query: $query, first: $perPage, after: $endCursor)"`
   157  	}
   158  
   159  	perPage := limit
   160  	if perPage > 100 {
   161  		perPage = 100
   162  	}
   163  
   164  	variables := map[string]interface{}{
   165  		"query":     githubv4.String(searchQuery(owner, filter)),
   166  		"perPage":   githubv4.Int(perPage),
   167  		"endCursor": (*githubv4.String)(nil),
   168  	}
   169  
   170  	gql := graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), client)
   171  	listResult := RepositoryList{FromSearch: true}
   172  pagination:
   173  	for {
   174  		var result query
   175  		err := gql.QueryNamed(context.Background(), "RepositoryListSearch", &result, variables)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		listResult.TotalCount = result.Search.RepositoryCount
   181  		for _, node := range result.Search.Nodes {
   182  			if listResult.Owner == "" {
   183  				idx := strings.IndexRune(node.Repository.NameWithOwner, '/')
   184  				listResult.Owner = node.Repository.NameWithOwner[:idx]
   185  			}
   186  			listResult.Repositories = append(listResult.Repositories, node.Repository)
   187  			if len(listResult.Repositories) >= limit {
   188  				break pagination
   189  			}
   190  		}
   191  
   192  		if !result.Search.PageInfo.HasNextPage {
   193  			break
   194  		}
   195  		variables["endCursor"] = githubv4.String(result.Search.PageInfo.EndCursor)
   196  	}
   197  
   198  	return &listResult, nil
   199  }
   200  
   201  func searchQuery(owner string, filter FilterOptions) string {
   202  	q := githubsearch.NewQuery()
   203  	q.SortBy(githubsearch.UpdatedAt, githubsearch.Desc)
   204  
   205  	if owner == "" {
   206  		q.OwnedBy("@me")
   207  	} else {
   208  		q.OwnedBy(owner)
   209  	}
   210  
   211  	if filter.Fork {
   212  		q.OnlyForks()
   213  	} else {
   214  		q.IncludeForks(!filter.Source)
   215  	}
   216  
   217  	if filter.Language != "" {
   218  		q.SetLanguage(filter.Language)
   219  	}
   220  
   221  	switch filter.Visibility {
   222  	case "public":
   223  		q.SetVisibility(githubsearch.Public)
   224  	case "private":
   225  		q.SetVisibility(githubsearch.Private)
   226  	}
   227  
   228  	if filter.Archived {
   229  		q.SetArchived(true)
   230  	} else if filter.NonArchived {
   231  		q.SetArchived(false)
   232  	}
   233  
   234  	return q.String()
   235  }