diff --git a/github/activity_star.go b/github/activity_star.go index d405d85a240..2c395ff2b4b 100644 --- a/github/activity_star.go +++ b/github/activity_star.go @@ -51,6 +51,65 @@ func (s *ActivityService) ListStargazers(ctx context.Context, owner, repo string return stargazers, resp, nil } +// StargazerCount represents the number of users who have starred a repository. +type StargazerCount struct { + Count int `json:"count"` +} + +// GetStargazerCount gets the number of people who have starred the specified repo. +// +// GitHub API docs: https://docs.github.com/rest/activity/starring?apiVersion=2022-11-28#get-stargazer-count +// +//meta:operation GET /repos/{owner}/{repo}/stargazers/count +func (s *ActivityService) GetStargazerCount(ctx context.Context, owner, repo string) (*StargazerCount, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stargazers/count", owner, repo) + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var count *StargazerCount + resp, err := s.client.Do(req, &count) + if err != nil { + return nil, resp, err + } + + return count, resp, nil +} + +// StargazerHistory represents the stars a repository received during one week. +type StargazerHistory struct { + Days []int `json:"days"` + Total int `json:"total"` + Week Timestamp `json:"week"` +} + +// ListStargazerHistory lists the stars of the specified repo grouped by week. +// +// GitHub API docs: https://docs.github.com/rest/activity/starring?apiVersion=2022-11-28#get-repository-star-history +// +//meta:operation GET /repos/{owner}/{repo}/stargazers/history +func (s *ActivityService) ListStargazerHistory(ctx context.Context, owner, repo string, opts *ListOptions) ([]*StargazerHistory, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stargazers/history", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + var history []*StargazerHistory + resp, err := s.client.Do(req, &history) + if err != nil { + return nil, resp, err + } + + return history, resp, nil +} + // ActivityListStarredOptions specifies the optional parameters to the // ActivityService.ListStarred method. type ActivityListStarredOptions struct { diff --git a/github/activity_star_test.go b/github/activity_star_test.go index e3ddab7aeb6..9e5e5e8c841 100644 --- a/github/activity_star_test.go +++ b/github/activity_star_test.go @@ -54,6 +54,86 @@ func TestActivityService_ListStargazers(t *testing.T) { }) } +func TestActivityService_GetStargazerCount(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/stargazers/count", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"count":42}`) + }) + + ctx := t.Context() + count, _, err := client.Activity.GetStargazerCount(ctx, "o", "r") + if err != nil { + t.Errorf("Activity.GetStargazerCount returned error: %v", err) + } + + want := &StargazerCount{Count: 42} + if !cmp.Equal(count, want) { + t.Errorf("Activity.GetStargazerCount returned %+v, want %+v", count, want) + } + + const methodName = "GetStargazerCount" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Activity.GetStargazerCount(ctx, "\n", "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Activity.GetStargazerCount(ctx, "o", "r") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestActivityService_ListStargazerHistory(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/stargazers/history", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + "per_page": "30", + }) + + fmt.Fprint(w, `[{"week":`+referenceUnixTimeStr+`,"total":19,"days":[0,12,7,0,0,0,0]}]`) + }) + + ctx := t.Context() + opts := &ListOptions{Page: 2, PerPage: 30} + history, _, err := client.Activity.ListStargazerHistory(ctx, "o", "r", opts) + if err != nil { + t.Errorf("Activity.ListStargazerHistory returned error: %v", err) + } + + want := []*StargazerHistory{{ + Days: []int{0, 12, 7, 0, 0, 0, 0}, + Total: 19, + Week: referenceTimestamp, + }} + if !cmp.Equal(history, want) { + t.Errorf("Activity.ListStargazerHistory returned %+v, want %+v", history, want) + } + + const methodName = "ListStargazerHistory" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Activity.ListStargazerHistory(ctx, "\n", "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Activity.ListStargazerHistory(ctx, "o", "r", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestActivityService_ListStarred_authenticatedUser(t *testing.T) { t.Parallel() client, mux, _ := setup(t) diff --git a/github/github-accessors.go b/github/github-accessors.go index e192e57d774..a34f04dad1c 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -43966,6 +43966,38 @@ func (s *Stargazer) GetUser() *User { return s.User } +// GetCount returns the Count field. +func (s *StargazerCount) GetCount() int { + if s == nil { + return 0 + } + return s.Count +} + +// GetDays returns the Days slice if it's non-nil, nil otherwise. +func (s *StargazerHistory) GetDays() []int { + if s == nil || s.Days == nil { + return nil + } + return s.Days +} + +// GetTotal returns the Total field. +func (s *StargazerHistory) GetTotal() int { + if s == nil { + return 0 + } + return s.Total +} + +// GetWeek returns the Week field. +func (s *StargazerHistory) GetWeek() Timestamp { + if s == nil { + return Timestamp{} + } + return s.Week +} + // GetRepository returns the Repository field. func (s *StarredRepository) GetRepository() *Repository { if s == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index e86fbcce1fc..e0e72c61ad6 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -54743,6 +54743,41 @@ func TestStargazer_GetUser(tt *testing.T) { s.GetUser() } +func TestStargazerCount_GetCount(tt *testing.T) { + tt.Parallel() + s := &StargazerCount{} + s.GetCount() + s = nil + s.GetCount() +} + +func TestStargazerHistory_GetDays(tt *testing.T) { + tt.Parallel() + zeroValue := []int{} + s := &StargazerHistory{Days: zeroValue} + s.GetDays() + s = &StargazerHistory{} + s.GetDays() + s = nil + s.GetDays() +} + +func TestStargazerHistory_GetTotal(tt *testing.T) { + tt.Parallel() + s := &StargazerHistory{} + s.GetTotal() + s = nil + s.GetTotal() +} + +func TestStargazerHistory_GetWeek(tt *testing.T) { + tt.Parallel() + s := &StargazerHistory{} + s.GetWeek() + s = nil + s.GetWeek() +} + func TestStarredRepository_GetRepository(tt *testing.T) { tt.Parallel() s := &StarredRepository{} diff --git a/github/github-iterators.go b/github/github-iterators.go index d66fd9baded..3413ef22f63 100644 --- a/github/github-iterators.go +++ b/github/github-iterators.go @@ -1413,6 +1413,37 @@ func (s *ActivityService) ListRepositoryNotificationsIter(ctx context.Context, o } } +// ListStargazerHistoryIter returns an iterator that paginates through all results of ListStargazerHistory. +func (s *ActivityService) ListStargazerHistoryIter(ctx context.Context, owner string, repo string, opts *ListOptions) iter.Seq2[*StargazerHistory, error] { + return func(yield func(*StargazerHistory, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListOptions{} + } else { + opts = new(*opts) + } + + for { + results, resp, err := s.ListStargazerHistory(ctx, owner, repo, opts) + if err != nil { + yield(nil, err) + return + } + + for _, item := range results { + if !yield(item, nil) { + return + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + } +} + // ListStargazersIter returns an iterator that paginates through all results of ListStargazers. func (s *ActivityService) ListStargazersIter(ctx context.Context, owner string, repo string, opts *ListOptions) iter.Seq2[*Stargazer, error] { return func(yield func(*Stargazer, error) bool) { diff --git a/github/github-iterators_test.go b/github/github-iterators_test.go index d689ef2959c..ebbf921315d 100644 --- a/github/github-iterators_test.go +++ b/github/github-iterators_test.go @@ -2967,6 +2967,78 @@ func TestActivityService_ListRepositoryNotificationsIter(t *testing.T) { } } +func TestActivityService_ListStargazerHistoryIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `[{},{},{}]`) + case 2: + fmt.Fprint(w, `[{},{},{},{}]`) + case 3: + fmt.Fprint(w, `[{},{}]`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `[{},{}]`) + } + }) + + iter := client.Activity.ListStargazerHistoryIter(t.Context(), "", "", nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Activity.ListStargazerHistoryIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListOptions{} + iter = client.Activity.ListStargazerHistoryIter(t.Context(), "", "", opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Activity.ListStargazerHistoryIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Activity.ListStargazerHistoryIter(t.Context(), "", "", nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Activity.ListStargazerHistoryIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Activity.ListStargazerHistoryIter(t.Context(), "", "", nil) + gotItems = 0 + iter(func(item *StargazerHistory, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Activity.ListStargazerHistoryIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + func TestActivityService_ListStargazersIter(t *testing.T) { t.Parallel() client, mux, _ := setup(t)