Skip to content

Commit 987361c

Browse files
committed
fix github api calls
1 parent ccca1eb commit 987361c

2 files changed

Lines changed: 103 additions & 31 deletions

File tree

internal/libs/github/client.go

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10-
"net/url"
10+
"slices"
11+
"strings"
1112
"time"
1213
)
1314

@@ -17,9 +18,10 @@ const (
1718
apiBaseURL = "https://api.github.com"
1819
apiVersionHeader = "2022-11-28"
1920
acceptHeader = "application/vnd.github+json"
21+
fetchPageSize = 100
22+
maxFetchPages = 10
2023
)
2124

22-
// repo is the JSON shape returned by both the list and search endpoints.
2325
type repo struct {
2426
Name string `json:"name"`
2527
FullName string `json:"full_name"`
@@ -59,11 +61,11 @@ func New() *Client {
5961
// ListReposPage returns one page of repositories accessible by the token.
6062
// Pass page=1 to start; use Page.NextPage for subsequent calls (0 means done).
6163
//
62-
// When search is non-empty the GitHub Search API is used so that filtering
63-
// happens server-side. When search is empty the standard list endpoint is used.
64+
// When search is non-empty all accessible repos are fetched from /user/repos
65+
// and filtered client-side so that org repos are included in results.
6466
func (c *Client) ListReposPage(ctx context.Context, token string, page, perPage int, search string) (Page, error) {
6567
if search != "" {
66-
return c.searchReposPage(ctx, token, page, perPage, search)
68+
return c.searchReposPage(ctx, token, perPage, search)
6769
}
6870
return c.listReposPage(ctx, token, page, perPage)
6971
}
@@ -84,31 +86,66 @@ func (c *Client) listReposPage(ctx context.Context, token string, page, perPage
8486
return Page{Repos: out, NextPage: nextPage}, nil
8587
}
8688

87-
func (c *Client) searchReposPage(ctx context.Context, token string, page, perPage int, search string) (Page, error) {
88-
type searchResponse struct {
89-
TotalCount int `json:"total_count"`
90-
Items []repo `json:"items"`
91-
}
89+
// searchReposPage fetches repos from /user/repos in batches and filters
90+
// client-side by name/description. This avoids the search API which returns
91+
// all public repos on GitHub.
92+
func (c *Client) searchReposPage(ctx context.Context, token string, perPage int, search string) (Page, error) {
93+
lower := strings.ToLower(search)
94+
var matched []Repo
9295

93-
q := url.QueryEscape(search + " user:@me")
94-
u := fmt.Sprintf("%s/search/repositories?q=%s&per_page=%d&page=%d", apiBaseURL, q, perPage, page)
96+
for p := 1; p <= maxFetchPages; p++ {
97+
u := fmt.Sprintf("%s/user/repos?per_page=%d&page=%d", apiBaseURL, fetchPageSize, p)
9598

96-
var result searchResponse
97-
if err := c.doJSON(ctx, token, u, &result); err != nil {
98-
return Page{}, err
99+
var rows []repo
100+
if err := c.doJSON(ctx, token, u, &rows); err != nil {
101+
return Page{}, err
102+
}
103+
104+
for _, r := range rows {
105+
nameLower := strings.ToLower(r.Name)
106+
descLower := strings.ToLower(r.Description)
107+
if strings.Contains(nameLower, lower) || strings.Contains(descLower, lower) {
108+
matched = append(matched, convertRepo(r))
109+
}
110+
}
111+
112+
if len(rows) < fetchPageSize {
113+
break
114+
}
99115
}
100116

101-
out := convertRepos(result.Items)
102-
nextPage := 0
103-
if len(result.Items) == perPage {
104-
nextPage = page + 1
117+
sortByNameRelevance(matched, lower)
118+
119+
if len(matched) > perPage {
120+
matched = matched[:perPage]
105121
}
106-
return Page{Repos: out, NextPage: nextPage}, nil
122+
return Page{Repos: matched}, nil
123+
}
124+
125+
func sortByNameRelevance(repos []Repo, lower string) {
126+
slices.SortStableFunc(repos, func(a, b Repo) int {
127+
aName := strings.ToLower(a.Name)
128+
bName := strings.ToLower(b.Name)
129+
aExact := aName == lower
130+
bExact := bName == lower
131+
if aExact != bExact {
132+
if aExact {
133+
return -1
134+
}
135+
return 1
136+
}
137+
aHas := strings.Contains(aName, lower)
138+
bHas := strings.Contains(bName, lower)
139+
if aHas != bHas {
140+
if aHas {
141+
return -1
142+
}
143+
return 1
144+
}
145+
return 0
146+
})
107147
}
108148

109-
// doJSON performs a GET request with standard GitHub headers and decodes the
110-
// JSON response into dst. It enforces a response body size limit to prevent
111-
// unbounded reads from a misbehaving upstream.
112149
func (c *Client) doJSON(ctx context.Context, token, rawURL string, dst any) error {
113150
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
114151
if err != nil {
@@ -136,17 +173,21 @@ func (c *Client) doJSON(ctx context.Context, token, rawURL string, dst any) erro
136173
return nil
137174
}
138175

176+
func convertRepo(r repo) Repo {
177+
return Repo{
178+
Name: r.Name,
179+
FullName: r.FullName,
180+
HTMLURL: r.HTMLURL,
181+
Private: r.Private,
182+
DefaultBranch: r.DefaultBranch,
183+
Description: r.Description,
184+
}
185+
}
186+
139187
func convertRepos(rows []repo) []Repo {
140188
out := make([]Repo, len(rows))
141189
for i, r := range rows {
142-
out[i] = Repo{
143-
Name: r.Name,
144-
FullName: r.FullName,
145-
HTMLURL: r.HTMLURL,
146-
Private: r.Private,
147-
DefaultBranch: r.DefaultBranch,
148-
Description: r.Description,
149-
}
190+
out[i] = convertRepo(r)
150191
}
151192
return out
152193
}

internal/libs/gitlab/client.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"io"
99
"net/http"
1010
"net/url"
11+
"slices"
12+
"strings"
1113
"time"
1214
)
1315

@@ -99,9 +101,38 @@ func (c *Client) ListProjectsPage(ctx context.Context, token string, page, perPa
99101
}
100102
}
101103

104+
if search != "" {
105+
sortProjectsByNameRelevance(out, search)
106+
}
107+
102108
nextPage := 0
103109
if len(rows) == perPage {
104110
nextPage = page + 1
105111
}
106112
return Page{Projects: out, NextPage: nextPage}, nil
107113
}
114+
115+
func sortProjectsByNameRelevance(projects []Project, search string) {
116+
lower := strings.ToLower(search)
117+
slices.SortStableFunc(projects, func(a, b Project) int {
118+
aName := strings.ToLower(a.Name)
119+
bName := strings.ToLower(b.Name)
120+
aExact := aName == lower
121+
bExact := bName == lower
122+
if aExact != bExact {
123+
if aExact {
124+
return -1
125+
}
126+
return 1
127+
}
128+
aHas := strings.Contains(aName, lower)
129+
bHas := strings.Contains(bName, lower)
130+
if aHas != bHas {
131+
if aHas {
132+
return -1
133+
}
134+
return 1
135+
}
136+
return 0
137+
})
138+
}

0 commit comments

Comments
 (0)