Files
Matheus Sampaio Queiroga 59c8a863d6 Improve GitHub rate limit handling
Check the `retry-after` header first for rate limit waits. Fall back
to `x-ratelimit-remaining` and `x-ratelimit-reset` if necessary,
correctly interpreting the reset value as a Unix timestamp.

Also simplifies the `ReRequest` helper to use `http.DefaultClient` and
refines panic recovery in Gitea asset uploads.
2025-04-22 21:13:50 -03:00

166 lines
5.5 KiB
Go

package gitea
import (
"fmt"
"io"
"iter"
"mime/multipart"
"net/http"
"net/url"
"path"
"strconv"
gitea_api "sirherobrine23.com.br/go-bds/request/gitea/structs"
"sirherobrine23.com.br/go-bds/request/v2"
)
// Get release by tag name
func (client Gitea) ReleaseTag(tagName string) (*gitea_api.Release, error) {
reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes}
client.authHeader(&reqOptions.Header)
res, _, err := request.JSON[*gitea_api.Release](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases/tags", url.PathEscape(tagName))}).String(), reqOptions)
return res, err
}
// Get release by ID
func (client Gitea) Release(id int) (*gitea_api.Release, error) {
reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes}
client.authHeader(&reqOptions.Header)
res, _, err := request.JSON[*gitea_api.Release](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases", strconv.Itoa(id))}).String(), reqOptions)
return res, err
}
// List all release from gitea APIs
func (client Gitea) ReleasesSeq() iter.Seq2[*gitea_api.Release, error] {
releases, res, err := []*gitea_api.Release{}, (*http.Response)(nil), error(nil)
reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes}
client.authHeader(&reqOptions.Header)
return func(yield func(*gitea_api.Release, error) bool) {
requestUrl := client.Host.ResolveReference(&url.URL{RawQuery: "limit=100", Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases")}).String()
for requestUrl != "" {
if len(releases) == 0 {
if releases, res, err = request.JSON[[]*gitea_api.Release](requestUrl, reqOptions); res != nil {
requestUrl = parsePaginator(res.Header).NextPageToken
} else {
requestUrl = ""
}
}
if err != nil {
yield(nil, err)
return
}
for len(releases) > 0 {
if !yield(releases[0], nil) {
return
}
releases = releases[1:]
}
}
}
}
// List all release Assets in [iter.Seq2]
func (client Gitea) AssetsSeq(releaseID int) iter.Seq2[*gitea_api.Attachment, error] {
asserts, res, err := []*gitea_api.Attachment{}, (*http.Response)(nil), error(nil)
reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes}
client.authHeader(&reqOptions.Header)
return func(yield func(*gitea_api.Attachment, error) bool) {
requestUrl := client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases", strconv.Itoa(releaseID), "assets")}).String()
for requestUrl != "" {
if len(asserts) == 0 {
if asserts, res, err = request.JSON[[]*gitea_api.Attachment](requestUrl, reqOptions); res != nil {
requestUrl = parsePaginator(res.Header).NextPageToken
} else {
requestUrl = ""
}
}
if err != nil {
yield(nil, err)
return
}
for len(asserts) > 0 {
if !yield(asserts[0], nil) {
return
}
asserts = asserts[1:]
}
}
}
}
// Get all releases from Gitea APIs and return slice with all versions
func (client Gitea) Releases() ([]*gitea_api.Release, error) {
releases := []*gitea_api.Release{}
for release, err := range client.ReleasesSeq() {
if err != nil {
return nil, err
}
releases = append(releases, release)
}
return releases, nil
}
// List all release Assets in to slice
func (client Gitea) AllAssets(releaseID int) ([]*gitea_api.Attachment, error) {
asserts := []*gitea_api.Attachment{}
for assert, err := range client.AssetsSeq(releaseID) {
if err != nil {
return nil, err
}
asserts = append(asserts, assert)
}
return asserts, nil
}
// Delete assert file from Release
func (client Gitea) DeleteAsset(releaseID, assetID int) error {
reqOptions := &request.Options{Method: "DELETE", Header: request.Header{}, CodeProcess: processCodes}
client.authHeader(&reqOptions.Header)
res, err := request.MakeRequestWithStatus(client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases", strconv.Itoa(releaseID), "assets", strconv.Itoa(assetID))}), reqOptions)
res.Body.Close()
return err
}
// Upload file to Release
func (client Gitea) UploadAsset(releaseID int, name string, file io.Reader) (fileInfo *gitea_api.Attachment, err error) {
defer func(err *error) {
if err2 := recover(); err2 != nil {
switch v := err2.(type) {
case error:
*err = v
default:
*err = fmt.Errorf("%s", err2)
}
}
}(&err)
readForm, writeForm := io.Pipe()
mw := multipart.NewWriter(writeForm)
go func(mw *multipart.Writer) {
part, err := mw.CreateFormFile("attachment", name)
if err != nil {
panic(err)
} else if _, err = io.Copy(part, file); err != nil {
panic(err)
} else if err = mw.Close(); err != nil {
panic(err)
}
}(mw)
reqOptions := &request.Options{
Method: "POST",
Body: readForm,
Header: request.Header{
"Content-Type": mw.FormDataContentType(),
},
CodeProcess: processCodes.Extends(request.MapCode{
201: func(res *http.Response) (*http.Response, error) { return res, nil },
422: func(res *http.Response) (*http.Response, error) {
return nil, fmt.Errorf("same filename exists in release: %s", name)
},
}),
}
client.authHeader(&reqOptions.Header)
_, err = request.DoJSON(client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/repos", client.repoPath(), "releases", strconv.Itoa(releaseID), "assets")}).String(), fileInfo, reqOptions)
return
}