|
@@ -1,9 +1,11 @@
|
|
|
package feed
|
|
|
|
|
|
import (
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"log/slog"
|
|
|
"net/http"
|
|
|
+ "net/url"
|
|
|
"sync"
|
|
|
"time"
|
|
|
)
|
|
@@ -17,6 +19,19 @@ type githubReleaseLatestResponseJson struct {
|
|
|
} `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)
|
|
|
|
|
@@ -27,7 +42,107 @@ func parseGithubTime(t string) time.Time {
|
|
|
return parsedTime
|
|
|
}
|
|
|
|
|
|
-func FetchLatestReleasesFromGithub(repositories []string, token string) (AppReleases, error) {
|
|
|
+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 {
|