package github import ( "context" "encoding/json" "fmt" ) const maxPageCount = 100 type ( qlData struct { Organization *qlOrganization `json:"organization"` } qlMembersWithRoleConnection struct { Nodes []qlUser `json:"nodes"` PageInfo qlPageInfo `json:"pageInfo"` } qlOrganization struct { MembersWithRole *qlMembersWithRoleConnection `json:"membersWithRole"` Team *qlTeam `json:"team"` Teams *qlTeamConnection `json:"teams"` } qlPageInfo struct { EndCursor string `json:"endCursor"` HasNextPage bool `json:"hasNextPage"` } qlResult struct { Data *qlData `json:"data"` } qlTeam struct { ID string `json:"id"` Name string `json:"name"` Slug string `json:"slug"` Members *qlTeamMemberConnection `json:"members"` } qlTeamConnection struct { Edges []qlTeamEdge `json:"edges"` PageInfo qlPageInfo `json:"pageInfo"` } qlTeamEdge struct { Node qlTeam `json:"node"` } qlTeamMemberConnection struct { Edges []qlTeamMemberEdge `json:"edges"` PageInfo qlPageInfo `json:"pageInfo"` } qlTeamMemberEdge struct { Node qlUser `json:"node"` } qlUser struct { ID string `json:"id"` Login string `json:"login"` Name string `json:"name"` Email string `json:"email"` } ) func (p *Provider) listOrganizationMembers(ctx context.Context, orgSlug string) ([]qlUser, error) { var results []qlUser var cursor *string for { var res qlResult q := fmt.Sprintf(`query { organization(login:%s) { membersWithRole(first:%d, after:%s) { pageInfo { endCursor hasNextPage } nodes { id login name email } } } }`, encode(orgSlug), maxPageCount, encode(cursor)) _, err := p.graphql(ctx, q, &res) if err != nil { return nil, err } results = append(results, res.Data.Organization.MembersWithRole.Nodes...) if !res.Data.Organization.MembersWithRole.PageInfo.HasNextPage { break } cursor = &res.Data.Organization.MembersWithRole.PageInfo.EndCursor } return results, nil } func (p *Provider) listOrganizationTeamsWithMemberIDs(ctx context.Context, orgSlug string) ([]teamWithMemberIDs, error) { var results []teamWithMemberIDs var pageInfos []qlPageInfo // first query all the teams with their members var cursor *string for { var res qlResult q := fmt.Sprintf(`query { organization(login:%s) { teams(first:%d, after:%s) { pageInfo { endCursor hasNextPage } edges { node { id name slug members(first:%d) { pageInfo { endCursor hasNextPage } edges { node { id } } } } } } } }`, encode(orgSlug), maxPageCount, encode(cursor), maxPageCount) _, err := p.graphql(ctx, q, &res) if err != nil { return nil, err } for _, teamEdge := range res.Data.Organization.Teams.Edges { var memberIDs []string for _, memberEdge := range teamEdge.Node.Members.Edges { memberIDs = append(memberIDs, memberEdge.Node.ID) } results = append(results, teamWithMemberIDs{ ID: teamEdge.Node.ID, Slug: teamEdge.Node.Slug, Name: teamEdge.Node.Name, MemberIDs: memberIDs, }) pageInfos = append(pageInfos, teamEdge.Node.Members.PageInfo) } if !res.Data.Organization.Teams.PageInfo.HasNextPage { break } cursor = &res.Data.Organization.Teams.PageInfo.EndCursor } // it's possible we didn't get all the members if the initial query, so go through each team and // check the member pageInfo. If there are still remaining members, query those. for i, pageInfo := range pageInfos { if !pageInfo.HasNextPage { continue } cursor = &pageInfo.EndCursor for { var res qlResult q := fmt.Sprintf(`query { organization(login:%s) { team(slug:%s) { members(first:%d, after:%s) { pageInfo { endCursor hasNextPage } edges { node { id } } } } } }`, encode(orgSlug), encode(results[i].Slug), maxPageCount, encode(cursor)) _, err := p.graphql(ctx, q, &res) if err != nil { return nil, err } for _, memberEdge := range res.Data.Organization.Team.Members.Edges { results[i].MemberIDs = append(results[i].MemberIDs, memberEdge.Node.ID) } if !res.Data.Organization.Team.Members.PageInfo.HasNextPage { break } cursor = &res.Data.Organization.Team.Members.PageInfo.EndCursor } } return results, nil } func (p *Provider) listUserOrganizationTeams(ctx context.Context, userSlug string, orgSlug string) ([]string, error) { // GitHub's Rest API doesn't have an easy way of querying this data, so we use the GraphQL API. var teamSlugs []string var cursor *string for { var res qlResult q := fmt.Sprintf(`query { organization(login:%s) { teams(first:%d, userLogins:[%s], after:%s) { pageInfo { endCursor hasNextPage } edges { node { id slug } } } } }`, encode(orgSlug), maxPageCount, encode(userSlug), encode(cursor)) _, err := p.graphql(ctx, q, &res) if err != nil { return nil, err } for _, edge := range res.Data.Organization.Teams.Edges { teamSlugs = append(teamSlugs, edge.Node.Slug) } if !res.Data.Organization.Teams.PageInfo.HasNextPage { break } cursor = &res.Data.Organization.Teams.PageInfo.EndCursor } return teamSlugs, nil } func encode(obj interface{}) string { bs, _ := json.Marshal(obj) return string(bs) }