github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/ui/versionmanager/github.go (about)

     1  /*
     2   * Copyright (C) 2021 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package versionmanager
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"time"
    25  
    26  	"github.com/mysteriumnetwork/node/requests"
    27  )
    28  
    29  const (
    30  	nodeUIAssetName        = "dist.tar.gz"
    31  	compatibilityAssetName = "compatibility.json"
    32  	apiURI                 = "https://api.github.com"
    33  	nodeUIPath             = "repos/mysteriumnetwork/dvpn-web"
    34  )
    35  
    36  type httpClient interface {
    37  	Do(req *http.Request) (*http.Response, error)
    38  	DoRequestAndParseResponse(req *http.Request, resp interface{}) error
    39  }
    40  
    41  type github struct {
    42  	http httpClient
    43  }
    44  
    45  func newGithub(httpClient httpClient) *github {
    46  	return &github{http: httpClient}
    47  }
    48  
    49  func (g *github) nodeUIReleases(perPage int, page int) ([]GitHubRelease, error) {
    50  	req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases", nodeUIPath), nil)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("failed to create NodeUI releases fetch request: %w", err)
    53  	}
    54  
    55  	q := req.URL.Query()
    56  	if perPage != 0 {
    57  		q.Add("per_page", fmt.Sprint(perPage))
    58  	}
    59  
    60  	if page != 0 {
    61  		q.Add("page", fmt.Sprint(page))
    62  	}
    63  	req.URL.RawQuery = q.Encode()
    64  
    65  	res, err := g.http.Do(req)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("failed to fetch NodeUI releases: %w", err)
    68  	}
    69  
    70  	if err := requests.ParseResponseError(res); err != nil {
    71  		return nil, fmt.Errorf("response error: %w", err)
    72  	}
    73  
    74  	var releases []GitHubRelease
    75  	err = requests.ParseResponseJSON(res, &releases)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to parse response: %w", err)
    78  	}
    79  
    80  	return releases, nil
    81  }
    82  
    83  func (g *github) nodeUIReleaseByVersion(name string) (GitHubRelease, error) {
    84  	req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases/tags/%s", nodeUIPath, name), nil)
    85  	if err != nil {
    86  		return GitHubRelease{}, fmt.Errorf("could not create request: %w", err)
    87  	}
    88  
    89  	var release GitHubRelease
    90  	err = g.http.DoRequestAndParseResponse(req, &release)
    91  	if err != nil {
    92  		return GitHubRelease{}, fmt.Errorf("could not fetch version tagged %q: %w", name, err)
    93  	}
    94  
    95  	return release, nil
    96  }
    97  
    98  func (g *github) nodeUIDownloadURL(versionName string) (*url.URL, error) {
    99  	r, err := g.nodeUIReleaseByVersion(versionName)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases/%d/assets", nodeUIPath, r.Id), nil)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("failed to create NodeUI assets ID %d, versionName %q: %w", r.Id, r.Name, err)
   107  	}
   108  
   109  	var assets []GithubAsset
   110  	err = g.http.DoRequestAndParseResponse(req, &assets)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("failed to fetch NodeUI releases: %w", err)
   113  	}
   114  
   115  	uiDist, ok := findNodeUIDist(assets)
   116  	if !ok {
   117  		return nil, fmt.Errorf("could not find nodeUI dist asset for release ID %d version %s", r.Id, versionName)
   118  	}
   119  	return url.Parse(uiDist.BrowserDownloadUrl)
   120  }
   121  
   122  func findNodeUIDist(assets []GithubAsset) (GithubAsset, bool) {
   123  	for _, ass := range assets {
   124  		if ass.Name == nodeUIAssetName {
   125  			return ass, true
   126  		}
   127  	}
   128  	return GithubAsset{}, false
   129  }
   130  
   131  // GitHubRelease github release resource
   132  type GitHubRelease struct {
   133  	Url       string `json:"url"`
   134  	AssetsUrl string `json:"assets_url"`
   135  	UploadUrl string `json:"upload_url"`
   136  	HtmlUrl   string `json:"html_url"`
   137  	Id        int    `json:"id"`
   138  	Author    struct {
   139  		Login             string `json:"login"`
   140  		Id                int    `json:"id"`
   141  		NodeId            string `json:"node_id"`
   142  		AvatarUrl         string `json:"avatar_url"`
   143  		GravatarId        string `json:"gravatar_id"`
   144  		Url               string `json:"url"`
   145  		HtmlUrl           string `json:"html_url"`
   146  		FollowersUrl      string `json:"followers_url"`
   147  		FollowingUrl      string `json:"following_url"`
   148  		GistsUrl          string `json:"gists_url"`
   149  		StarredUrl        string `json:"starred_url"`
   150  		SubscriptionsUrl  string `json:"subscriptions_url"`
   151  		OrganizationsUrl  string `json:"organizations_url"`
   152  		ReposUrl          string `json:"repos_url"`
   153  		EventsUrl         string `json:"events_url"`
   154  		ReceivedEventsUrl string `json:"received_events_url"`
   155  		Type              string `json:"type"`
   156  		SiteAdmin         bool   `json:"site_admin"`
   157  	} `json:"author"`
   158  	NodeId          string    `json:"node_id"`
   159  	TagName         string    `json:"tag_name"`
   160  	TargetCommitish string    `json:"target_commitish"`
   161  	Name            string    `json:"name"`
   162  	Draft           bool      `json:"draft"`
   163  	Prerelease      bool      `json:"prerelease"`
   164  	CreatedAt       time.Time `json:"created_at"`
   165  	PublishedAt     time.Time `json:"published_at"`
   166  	Assets          []struct {
   167  		Url      string `json:"url"`
   168  		Id       int    `json:"id"`
   169  		NodeId   string `json:"node_id"`
   170  		Name     string `json:"name"`
   171  		Label    string `json:"label"`
   172  		Uploader struct {
   173  			Login             string `json:"login"`
   174  			Id                int    `json:"id"`
   175  			NodeId            string `json:"node_id"`
   176  			AvatarUrl         string `json:"avatar_url"`
   177  			GravatarId        string `json:"gravatar_id"`
   178  			Url               string `json:"url"`
   179  			HtmlUrl           string `json:"html_url"`
   180  			FollowersUrl      string `json:"followers_url"`
   181  			FollowingUrl      string `json:"following_url"`
   182  			GistsUrl          string `json:"gists_url"`
   183  			StarredUrl        string `json:"starred_url"`
   184  			SubscriptionsUrl  string `json:"subscriptions_url"`
   185  			OrganizationsUrl  string `json:"organizations_url"`
   186  			ReposUrl          string `json:"repos_url"`
   187  			EventsUrl         string `json:"events_url"`
   188  			ReceivedEventsUrl string `json:"received_events_url"`
   189  			Type              string `json:"type"`
   190  			SiteAdmin         bool   `json:"site_admin"`
   191  		} `json:"uploader"`
   192  		ContentType        string    `json:"content_type"`
   193  		State              string    `json:"state"`
   194  		Size               int       `json:"size"`
   195  		DownloadCount      int       `json:"download_count"`
   196  		CreatedAt          time.Time `json:"created_at"`
   197  		UpdatedAt          time.Time `json:"updated_at"`
   198  		BrowserDownloadUrl string    `json:"browser_download_url"`
   199  	} `json:"assets"`
   200  	TarballUrl string `json:"tarball_url"`
   201  	ZipballUrl string `json:"zipball_url"`
   202  	Body       string `json:"body"`
   203  }
   204  
   205  // GithubAsset github asset resource
   206  type GithubAsset struct {
   207  	Url      string `json:"url"`
   208  	Id       int    `json:"id"`
   209  	NodeId   string `json:"node_id"`
   210  	Name     string `json:"name"`
   211  	Label    string `json:"label"`
   212  	Uploader struct {
   213  		Login             string `json:"login"`
   214  		Id                int    `json:"id"`
   215  		NodeId            string `json:"node_id"`
   216  		AvatarUrl         string `json:"avatar_url"`
   217  		GravatarId        string `json:"gravatar_id"`
   218  		Url               string `json:"url"`
   219  		HtmlUrl           string `json:"html_url"`
   220  		FollowersUrl      string `json:"followers_url"`
   221  		FollowingUrl      string `json:"following_url"`
   222  		GistsUrl          string `json:"gists_url"`
   223  		StarredUrl        string `json:"starred_url"`
   224  		SubscriptionsUrl  string `json:"subscriptions_url"`
   225  		OrganizationsUrl  string `json:"organizations_url"`
   226  		ReposUrl          string `json:"repos_url"`
   227  		EventsUrl         string `json:"events_url"`
   228  		ReceivedEventsUrl string `json:"received_events_url"`
   229  		Type              string `json:"type"`
   230  		SiteAdmin         bool   `json:"site_admin"`
   231  	} `json:"uploader"`
   232  	ContentType        string    `json:"content_type"`
   233  	State              string    `json:"state"`
   234  	Size               int       `json:"size"`
   235  	DownloadCount      int       `json:"download_count"`
   236  	CreatedAt          time.Time `json:"created_at"`
   237  	UpdatedAt          time.Time `json:"updated_at"`
   238  	BrowserDownloadUrl string    `json:"browser_download_url"`
   239  }