github.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package feed
  2. import (
  3. "fmt"
  4. "net/http"
  5. "sync"
  6. "time"
  7. )
  8. type githubReleaseLatestResponseJson struct {
  9. TagName string `json:"tag_name"`
  10. PublishedAt string `json:"published_at"`
  11. HtmlUrl string `json:"html_url"`
  12. Reactions struct {
  13. Downvotes int `json:"-1"`
  14. } `json:"reactions"`
  15. }
  16. func fetchLatestGithubRelease(request *ReleaseRequest) (*AppRelease, error) {
  17. httpRequest, err := http.NewRequest(
  18. "GET",
  19. fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.Repository),
  20. nil,
  21. )
  22. if err != nil {
  23. return nil, err
  24. }
  25. if request.Token != nil {
  26. httpRequest.Header.Add("Authorization", "Bearer "+(*request.Token))
  27. }
  28. response, err := decodeJsonFromRequest[githubReleaseLatestResponseJson](defaultClient, httpRequest)
  29. if err != nil {
  30. return nil, err
  31. }
  32. version := response.TagName
  33. if len(version) > 0 && version[0] != 'v' {
  34. version = "v" + version
  35. }
  36. return &AppRelease{
  37. Source: ReleaseSourceGithub,
  38. Name: request.Repository,
  39. Version: version,
  40. NotesUrl: response.HtmlUrl,
  41. TimeReleased: parseRFC3339Time(response.PublishedAt),
  42. Downvotes: response.Reactions.Downvotes,
  43. }, nil
  44. }
  45. type GithubTicket struct {
  46. Number int
  47. CreatedAt time.Time
  48. Title string
  49. }
  50. type RepositoryDetails struct {
  51. Name string
  52. Stars int
  53. Forks int
  54. OpenPullRequests int
  55. PullRequests []GithubTicket
  56. OpenIssues int
  57. Issues []GithubTicket
  58. }
  59. type githubRepositoryDetailsResponseJson struct {
  60. Name string `json:"full_name"`
  61. Stars int `json:"stargazers_count"`
  62. Forks int `json:"forks_count"`
  63. }
  64. type githubTicketResponseJson struct {
  65. Count int `json:"total_count"`
  66. Tickets []struct {
  67. Number int `json:"number"`
  68. CreatedAt string `json:"created_at"`
  69. Title string `json:"title"`
  70. } `json:"items"`
  71. }
  72. func FetchRepositoryDetailsFromGithub(repository string, token string, maxPRs int, maxIssues int) (RepositoryDetails, error) {
  73. repositoryRequest, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s", repository), nil)
  74. if err != nil {
  75. return RepositoryDetails{}, fmt.Errorf("%w: could not create request with repository: %v", ErrNoContent, err)
  76. }
  77. PRsRequest, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/search/issues?q=is:pr+is:open+repo:%s&per_page=%d", repository, maxPRs), nil)
  78. issuesRequest, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/search/issues?q=is:issue+is:open+repo:%s&per_page=%d", repository, maxIssues), nil)
  79. if token != "" {
  80. token = fmt.Sprintf("Bearer %s", token)
  81. repositoryRequest.Header.Add("Authorization", token)
  82. PRsRequest.Header.Add("Authorization", token)
  83. issuesRequest.Header.Add("Authorization", token)
  84. }
  85. var detailsResponse githubRepositoryDetailsResponseJson
  86. var detailsErr error
  87. var PRsResponse githubTicketResponseJson
  88. var PRsErr error
  89. var issuesResponse githubTicketResponseJson
  90. var issuesErr error
  91. var wg sync.WaitGroup
  92. wg.Add(1)
  93. go (func() {
  94. defer wg.Done()
  95. detailsResponse, detailsErr = decodeJsonFromRequest[githubRepositoryDetailsResponseJson](defaultClient, repositoryRequest)
  96. })()
  97. if maxPRs > 0 {
  98. wg.Add(1)
  99. go (func() {
  100. defer wg.Done()
  101. PRsResponse, PRsErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, PRsRequest)
  102. })()
  103. }
  104. if maxIssues > 0 {
  105. wg.Add(1)
  106. go (func() {
  107. defer wg.Done()
  108. issuesResponse, issuesErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, issuesRequest)
  109. })()
  110. }
  111. wg.Wait()
  112. if detailsErr != nil {
  113. return RepositoryDetails{}, fmt.Errorf("%w: could not get repository details: %s", ErrNoContent, detailsErr)
  114. }
  115. details := RepositoryDetails{
  116. Name: detailsResponse.Name,
  117. Stars: detailsResponse.Stars,
  118. Forks: detailsResponse.Forks,
  119. PullRequests: make([]GithubTicket, 0, len(PRsResponse.Tickets)),
  120. Issues: make([]GithubTicket, 0, len(issuesResponse.Tickets)),
  121. }
  122. err = nil
  123. if maxPRs > 0 {
  124. if PRsErr != nil {
  125. err = fmt.Errorf("%w: could not get PRs: %s", ErrPartialContent, PRsErr)
  126. } else {
  127. details.OpenPullRequests = PRsResponse.Count
  128. for i := range PRsResponse.Tickets {
  129. details.PullRequests = append(details.PullRequests, GithubTicket{
  130. Number: PRsResponse.Tickets[i].Number,
  131. CreatedAt: parseRFC3339Time(PRsResponse.Tickets[i].CreatedAt),
  132. Title: PRsResponse.Tickets[i].Title,
  133. })
  134. }
  135. }
  136. }
  137. if maxIssues > 0 {
  138. if issuesErr != nil {
  139. // TODO: fix, overwriting the previous error
  140. err = fmt.Errorf("%w: could not get issues: %s", ErrPartialContent, issuesErr)
  141. } else {
  142. details.OpenIssues = issuesResponse.Count
  143. for i := range issuesResponse.Tickets {
  144. details.Issues = append(details.Issues, GithubTicket{
  145. Number: issuesResponse.Tickets[i].Number,
  146. CreatedAt: parseRFC3339Time(issuesResponse.Tickets[i].CreatedAt),
  147. Title: issuesResponse.Tickets[i].Title,
  148. })
  149. }
  150. }
  151. }
  152. return details, err
  153. }