|
@@ -1,344 +0,0 @@
|
|
|
-package feed
|
|
|
-
|
|
|
-import (
|
|
|
- "errors"
|
|
|
- "fmt"
|
|
|
- "log/slog"
|
|
|
- "net/http"
|
|
|
- "net/url"
|
|
|
- "sync"
|
|
|
- "time"
|
|
|
-)
|
|
|
-
|
|
|
-type githubReleaseLatestResponseJson struct {
|
|
|
- TagName string `json:"tag_name"`
|
|
|
- PublishedAt string `json:"published_at"`
|
|
|
- HtmlUrl string `json:"html_url"`
|
|
|
- Reactions struct {
|
|
|
- Downvotes int `json:"-1"`
|
|
|
- } `json:"reactions"`
|
|
|
-}
|
|
|
-
|
|
|
-type gitlabReleaseResponseJson struct {
|
|
|
- TagName string `json:"tag_name"`
|
|
|
- PublishedAt string `json:"created_at"`
|
|
|
- Links struct {
|
|
|
- Self string `json:"self"`
|
|
|
- } `json:"_links"`
|
|
|
- Draft bool `json:"draft"`
|
|
|
- PreRelease bool `json:"prerelease"`
|
|
|
- Reactions struct {
|
|
|
- Downvotes int `json:"-1"`
|
|
|
- } `json:"reactions"`
|
|
|
-}
|
|
|
-
|
|
|
-func parseGithubTime(t string) time.Time {
|
|
|
- parsedTime, err := time.Parse("2006-01-02T15:04:05Z", t)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return time.Now()
|
|
|
- }
|
|
|
-
|
|
|
- return parsedTime
|
|
|
-}
|
|
|
-
|
|
|
-func FetchLatestReleasesFromGitForge(repositories []string, token string, source string) (AppReleases, error) {
|
|
|
- switch source {
|
|
|
- case "github":
|
|
|
- return fetchLatestReleasesFromGithub(repositories, token)
|
|
|
- case "gitlab":
|
|
|
- return fetchLatestReleasesFromGitlab(repositories, token)
|
|
|
- default:
|
|
|
- return nil, errors.New(fmt.Sprintf("Release source %s is invalid", source))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func fetchLatestReleasesFromGitlab(repositories []string, token string) (AppReleases, error) {
|
|
|
- appReleases := make(AppReleases, 0, len(repositories))
|
|
|
-
|
|
|
- if len(repositories) == 0 {
|
|
|
- return appReleases, nil
|
|
|
- }
|
|
|
-
|
|
|
- requests := make([]*http.Request, len(repositories))
|
|
|
-
|
|
|
- for i, repository := range repositories {
|
|
|
- request, _ := http.NewRequest("GET", fmt.Sprintf("https://gitlab.com/api/v4/projects/%s/releases/", url.QueryEscape(repository)), nil)
|
|
|
-
|
|
|
- if token != "" {
|
|
|
- request.Header.Add("PRIVATE-TOKEN", token)
|
|
|
- }
|
|
|
-
|
|
|
- requests[i] = request
|
|
|
- }
|
|
|
-
|
|
|
- task := decodeJsonFromRequestTask[[]gitlabReleaseResponseJson](defaultClient)
|
|
|
- job := newJob(task, requests).withWorkers(15)
|
|
|
- responses, errs, err := workerPoolDo(job)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- var failed int
|
|
|
-
|
|
|
- for i := range responses {
|
|
|
- if errs[i] != nil {
|
|
|
- failed++
|
|
|
- slog.Error("Failed to fetch or parse gitlab release", "error", errs[i], "url", requests[i].URL)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- releases := responses[i]
|
|
|
-
|
|
|
- if len(releases) < 1 {
|
|
|
- failed++
|
|
|
- slog.Error("No releases found", "repository", repositories[i], "url", requests[i].URL)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- var liveRelease *gitlabReleaseResponseJson
|
|
|
-
|
|
|
- for i := range releases {
|
|
|
- release := &releases[i]
|
|
|
-
|
|
|
- if !release.Draft && !release.PreRelease {
|
|
|
- liveRelease = release
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if liveRelease == nil {
|
|
|
- slog.Error("No live release found", "repository", repositories[i], "url", requests[i].URL)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- version := liveRelease.TagName
|
|
|
-
|
|
|
- if version[0] != 'v' {
|
|
|
- version = "v" + version
|
|
|
- }
|
|
|
-
|
|
|
- appReleases = append(appReleases, AppRelease{
|
|
|
- Name: repositories[i],
|
|
|
- Version: version,
|
|
|
- NotesUrl: liveRelease.Links.Self,
|
|
|
- TimeReleased: parseGithubTime(liveRelease.PublishedAt),
|
|
|
- Downvotes: liveRelease.Reactions.Downvotes,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if len(appReleases) == 0 {
|
|
|
- return nil, ErrNoContent
|
|
|
- }
|
|
|
-
|
|
|
- appReleases.SortByNewest()
|
|
|
-
|
|
|
- if failed > 0 {
|
|
|
- return appReleases, fmt.Errorf("%w: could not get %d releases", ErrPartialContent, failed)
|
|
|
- }
|
|
|
-
|
|
|
- return appReleases, nil
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-func fetchLatestReleasesFromGithub(repositories []string, token string) (AppReleases, error) {
|
|
|
- appReleases := make(AppReleases, 0, len(repositories))
|
|
|
-
|
|
|
- if len(repositories) == 0 {
|
|
|
- return appReleases, nil
|
|
|
- }
|
|
|
-
|
|
|
- requests := make([]*http.Request, len(repositories))
|
|
|
-
|
|
|
- for i, repository := range repositories {
|
|
|
- request, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repository), nil)
|
|
|
-
|
|
|
- if token != "" {
|
|
|
- request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
- }
|
|
|
-
|
|
|
- requests[i] = request
|
|
|
- }
|
|
|
-
|
|
|
- task := decodeJsonFromRequestTask[githubReleaseLatestResponseJson](defaultClient)
|
|
|
- job := newJob(task, requests).withWorkers(15)
|
|
|
- responses, errs, err := workerPoolDo(job)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- var failed int
|
|
|
-
|
|
|
- for i := range responses {
|
|
|
- if errs[i] != nil {
|
|
|
- failed++
|
|
|
- slog.Error("Failed to fetch or parse github release", "error", errs[i], "url", requests[i].URL)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- liveRelease := &responses[i]
|
|
|
-
|
|
|
- if liveRelease == nil {
|
|
|
- slog.Error("No live release found", "repository", repositories[i], "url", requests[i].URL)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- version := liveRelease.TagName
|
|
|
-
|
|
|
- if version[0] != 'v' {
|
|
|
- version = "v" + version
|
|
|
- }
|
|
|
-
|
|
|
- appReleases = append(appReleases, AppRelease{
|
|
|
- Name: repositories[i],
|
|
|
- Version: version,
|
|
|
- NotesUrl: liveRelease.HtmlUrl,
|
|
|
- TimeReleased: parseGithubTime(liveRelease.PublishedAt),
|
|
|
- Downvotes: liveRelease.Reactions.Downvotes,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if len(appReleases) == 0 {
|
|
|
- return nil, ErrNoContent
|
|
|
- }
|
|
|
-
|
|
|
- appReleases.SortByNewest()
|
|
|
-
|
|
|
- if failed > 0 {
|
|
|
- return appReleases, fmt.Errorf("%w: could not get %d releases", ErrPartialContent, failed)
|
|
|
- }
|
|
|
-
|
|
|
- return appReleases, nil
|
|
|
-}
|
|
|
-
|
|
|
-type GithubTicket struct {
|
|
|
- Number int
|
|
|
- CreatedAt time.Time
|
|
|
- Title string
|
|
|
-}
|
|
|
-
|
|
|
-type RepositoryDetails struct {
|
|
|
- Name string
|
|
|
- Stars int
|
|
|
- Forks int
|
|
|
- OpenPullRequests int
|
|
|
- PullRequests []GithubTicket
|
|
|
- OpenIssues int
|
|
|
- Issues []GithubTicket
|
|
|
-}
|
|
|
-
|
|
|
-type githubRepositoryDetailsResponseJson struct {
|
|
|
- Name string `json:"full_name"`
|
|
|
- Stars int `json:"stargazers_count"`
|
|
|
- Forks int `json:"forks_count"`
|
|
|
-}
|
|
|
-
|
|
|
-type githubTicketResponseJson struct {
|
|
|
- Count int `json:"total_count"`
|
|
|
- Tickets []struct {
|
|
|
- Number int `json:"number"`
|
|
|
- CreatedAt string `json:"created_at"`
|
|
|
- Title string `json:"title"`
|
|
|
- } `json:"items"`
|
|
|
-}
|
|
|
-
|
|
|
-func FetchRepositoryDetailsFromGithub(repository string, token string, maxPRs int, maxIssues int) (RepositoryDetails, error) {
|
|
|
- repositoryRequest, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s", repository), nil)
|
|
|
-
|
|
|
- if err != nil {
|
|
|
- return RepositoryDetails{}, fmt.Errorf("%w: could not create request with repository: %v", ErrNoContent, err)
|
|
|
- }
|
|
|
-
|
|
|
- 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)
|
|
|
- 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)
|
|
|
-
|
|
|
- if token != "" {
|
|
|
- token = fmt.Sprintf("Bearer %s", token)
|
|
|
- repositoryRequest.Header.Add("Authorization", token)
|
|
|
- PRsRequest.Header.Add("Authorization", token)
|
|
|
- issuesRequest.Header.Add("Authorization", token)
|
|
|
- }
|
|
|
-
|
|
|
- var detailsResponse githubRepositoryDetailsResponseJson
|
|
|
- var detailsErr error
|
|
|
- var PRsResponse githubTicketResponseJson
|
|
|
- var PRsErr error
|
|
|
- var issuesResponse githubTicketResponseJson
|
|
|
- var issuesErr error
|
|
|
- var wg sync.WaitGroup
|
|
|
-
|
|
|
- wg.Add(1)
|
|
|
- go (func() {
|
|
|
- defer wg.Done()
|
|
|
- detailsResponse, detailsErr = decodeJsonFromRequest[githubRepositoryDetailsResponseJson](defaultClient, repositoryRequest)
|
|
|
- })()
|
|
|
-
|
|
|
- if maxPRs > 0 {
|
|
|
- wg.Add(1)
|
|
|
- go (func() {
|
|
|
- defer wg.Done()
|
|
|
- PRsResponse, PRsErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, PRsRequest)
|
|
|
- })()
|
|
|
- }
|
|
|
-
|
|
|
- if maxIssues > 0 {
|
|
|
- wg.Add(1)
|
|
|
- go (func() {
|
|
|
- defer wg.Done()
|
|
|
- issuesResponse, issuesErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, issuesRequest)
|
|
|
- })()
|
|
|
- }
|
|
|
-
|
|
|
- wg.Wait()
|
|
|
-
|
|
|
- if detailsErr != nil {
|
|
|
- return RepositoryDetails{}, fmt.Errorf("%w: could not get repository details: %s", ErrNoContent, detailsErr)
|
|
|
- }
|
|
|
-
|
|
|
- details := RepositoryDetails{
|
|
|
- Name: detailsResponse.Name,
|
|
|
- Stars: detailsResponse.Stars,
|
|
|
- Forks: detailsResponse.Forks,
|
|
|
- PullRequests: make([]GithubTicket, 0, len(PRsResponse.Tickets)),
|
|
|
- Issues: make([]GithubTicket, 0, len(issuesResponse.Tickets)),
|
|
|
- }
|
|
|
-
|
|
|
- err = nil
|
|
|
-
|
|
|
- if maxPRs > 0 {
|
|
|
- if PRsErr != nil {
|
|
|
- err = fmt.Errorf("%w: could not get PRs: %s", ErrPartialContent, PRsErr)
|
|
|
- } else {
|
|
|
- details.OpenPullRequests = PRsResponse.Count
|
|
|
-
|
|
|
- for i := range PRsResponse.Tickets {
|
|
|
- details.PullRequests = append(details.PullRequests, GithubTicket{
|
|
|
- Number: PRsResponse.Tickets[i].Number,
|
|
|
- CreatedAt: parseGithubTime(PRsResponse.Tickets[i].CreatedAt),
|
|
|
- Title: PRsResponse.Tickets[i].Title,
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if maxIssues > 0 {
|
|
|
- if issuesErr != nil {
|
|
|
- // TODO: fix, overwriting the previous error
|
|
|
- err = fmt.Errorf("%w: could not get issues: %s", ErrPartialContent, issuesErr)
|
|
|
- } else {
|
|
|
- details.OpenIssues = issuesResponse.Count
|
|
|
-
|
|
|
- for i := range issuesResponse.Tickets {
|
|
|
- details.Issues = append(details.Issues, GithubTicket{
|
|
|
- Number: issuesResponse.Tickets[i].Number,
|
|
|
- CreatedAt: parseGithubTime(issuesResponse.Tickets[i].CreatedAt),
|
|
|
- Title: issuesResponse.Tickets[i].Title,
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return details, err
|
|
|
-}
|