github.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package feed
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "sync"
  7. "time"
  8. )
  9. type githubReleaseLatestResponseJson struct {
  10. TagName string `json:"tag_name"`
  11. PublishedAt string `json:"published_at"`
  12. HtmlUrl string `json:"html_url"`
  13. Reactions struct {
  14. Downvotes int `json:"-1"`
  15. } `json:"reactions"`
  16. }
  17. func fetchLatestGithubRelease(request *ReleaseRequest) (*AppRelease, error) {
  18. httpRequest, err := http.NewRequest(
  19. "GET",
  20. fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.Repository),
  21. nil,
  22. )
  23. if err != nil {
  24. return nil, err
  25. }
  26. if request.Token != nil {
  27. httpRequest.Header.Add("Authorization", "Bearer "+(*request.Token))
  28. }
  29. response, err := decodeJsonFromRequest[githubReleaseLatestResponseJson](defaultClient, httpRequest)
  30. if err != nil {
  31. return nil, err
  32. }
  33. version := response.TagName
  34. if len(version) > 0 && version[0] != 'v' {
  35. version = "v" + version
  36. }
  37. return &AppRelease{
  38. Source: ReleaseSourceGithub,
  39. Name: request.Repository,
  40. Version: version,
  41. NotesUrl: response.HtmlUrl,
  42. TimeReleased: parseRFC3339Time(response.PublishedAt),
  43. Downvotes: response.Reactions.Downvotes,
  44. }, nil
  45. }
  46. type GithubTicket struct {
  47. Number int
  48. CreatedAt time.Time
  49. Title string
  50. }
  51. type RepositoryDetails struct {
  52. Name string
  53. Stars int
  54. Forks int
  55. OpenPullRequests int
  56. PullRequests []GithubTicket
  57. OpenIssues int
  58. Issues []GithubTicket
  59. LastCommits int
  60. Commits []CommitsDetails
  61. }
  62. type githubRepositoryDetailsResponseJson struct {
  63. Name string `json:"full_name"`
  64. Stars int `json:"stargazers_count"`
  65. Forks int `json:"forks_count"`
  66. }
  67. type githubTicketResponseJson struct {
  68. Count int `json:"total_count"`
  69. Tickets []struct {
  70. Number int `json:"number"`
  71. CreatedAt string `json:"created_at"`
  72. Title string `json:"title"`
  73. } `json:"items"`
  74. }
  75. type CommitsDetails struct {
  76. Sha string
  77. Author string
  78. Email string
  79. Date time.Time
  80. Message string
  81. }
  82. type Author struct {
  83. Name string `json:"name"`
  84. Email string `json:"email"`
  85. Date string `json:"date"`
  86. }
  87. type Commit struct {
  88. Sha string `json:"sha"`
  89. Commit struct {
  90. Author Author `json:"author"`
  91. Message string `json:"message"`
  92. } `json:"commit"`
  93. }
  94. type githubCommitsResponseJson []Commit
  95. func FetchRepositoryDetailsFromGithub(repository string, token string, maxPRs int, maxIssues int, maxCommits int) (RepositoryDetails, error) {
  96. repositoryRequest, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s", repository), nil)
  97. if err != nil {
  98. return RepositoryDetails{}, fmt.Errorf("%w: could not create request with repository: %v", ErrNoContent, err)
  99. }
  100. 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)
  101. 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)
  102. CommitsRequest, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s/commits?per_page=%d", repository, maxCommits), nil)
  103. if token != "" {
  104. token = fmt.Sprintf("Bearer %s", token)
  105. repositoryRequest.Header.Add("Authorization", token)
  106. PRsRequest.Header.Add("Authorization", token)
  107. issuesRequest.Header.Add("Authorization", token)
  108. CommitsRequest.Header.Add("Authorization", token)
  109. }
  110. var detailsResponse githubRepositoryDetailsResponseJson
  111. var detailsErr error
  112. var PRsResponse githubTicketResponseJson
  113. var PRsErr error
  114. var issuesResponse githubTicketResponseJson
  115. var issuesErr error
  116. var CommitsResponse githubCommitsResponseJson
  117. var CommitsErr error
  118. var wg sync.WaitGroup
  119. wg.Add(1)
  120. go (func() {
  121. defer wg.Done()
  122. detailsResponse, detailsErr = decodeJsonFromRequest[githubRepositoryDetailsResponseJson](defaultClient, repositoryRequest)
  123. })()
  124. if maxPRs > 0 {
  125. wg.Add(1)
  126. go (func() {
  127. defer wg.Done()
  128. PRsResponse, PRsErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, PRsRequest)
  129. })()
  130. }
  131. if maxIssues > 0 {
  132. wg.Add(1)
  133. go (func() {
  134. defer wg.Done()
  135. issuesResponse, issuesErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, issuesRequest)
  136. })()
  137. }
  138. if maxCommits > 0 {
  139. wg.Add(1)
  140. go (func() {
  141. defer wg.Done()
  142. CommitsResponse, CommitsErr = decodeJsonFromRequest[githubCommitsResponseJson](defaultClient, CommitsRequest)
  143. })()
  144. }
  145. wg.Wait()
  146. if detailsErr != nil {
  147. return RepositoryDetails{}, fmt.Errorf("%w: could not get repository details: %s", ErrNoContent, detailsErr)
  148. }
  149. details := RepositoryDetails{
  150. Name: detailsResponse.Name,
  151. Stars: detailsResponse.Stars,
  152. Forks: detailsResponse.Forks,
  153. PullRequests: make([]GithubTicket, 0, len(PRsResponse.Tickets)),
  154. Issues: make([]GithubTicket, 0, len(issuesResponse.Tickets)),
  155. Commits: make([]CommitsDetails, 0, len(CommitsResponse)),
  156. }
  157. err = nil
  158. if maxPRs > 0 {
  159. if PRsErr != nil {
  160. err = fmt.Errorf("%w: could not get PRs: %s", ErrPartialContent, PRsErr)
  161. } else {
  162. details.OpenPullRequests = PRsResponse.Count
  163. for i := range PRsResponse.Tickets {
  164. details.PullRequests = append(details.PullRequests, GithubTicket{
  165. Number: PRsResponse.Tickets[i].Number,
  166. CreatedAt: parseRFC3339Time(PRsResponse.Tickets[i].CreatedAt),
  167. Title: PRsResponse.Tickets[i].Title,
  168. })
  169. }
  170. }
  171. }
  172. if maxIssues > 0 {
  173. if issuesErr != nil {
  174. // TODO: fix, overwriting the previous error
  175. err = fmt.Errorf("%w: could not get issues: %s", ErrPartialContent, issuesErr)
  176. } else {
  177. details.OpenIssues = issuesResponse.Count
  178. for i := range issuesResponse.Tickets {
  179. details.Issues = append(details.Issues, GithubTicket{
  180. Number: issuesResponse.Tickets[i].Number,
  181. CreatedAt: parseRFC3339Time(issuesResponse.Tickets[i].CreatedAt),
  182. Title: issuesResponse.Tickets[i].Title,
  183. })
  184. }
  185. }
  186. }
  187. if maxCommits > 0 {
  188. if CommitsErr != nil {
  189. err = fmt.Errorf("%w: could not get issues: %s", ErrPartialContent, CommitsErr)
  190. } else {
  191. for _, commit := range CommitsResponse {
  192. details.LastCommits++
  193. details.Commits = append(details.Commits, CommitsDetails{
  194. Sha: commit.Sha,
  195. Author: commit.Commit.Author.Name,
  196. Email: commit.Commit.Author.Email,
  197. Date: parseGithubTime(commit.Commit.Author.Date),
  198. Message: strings.SplitN(commit.Commit.Message, "\n\n", 2)[0],
  199. })
  200. }
  201. }
  202. }
  203. return details, err
  204. }