From 520d37c408647466841bd221aad87ed6906862d6 Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Tue, 1 Apr 2025 23:46:29 -0300 Subject: [PATCH] Add Gitea and Github basic clients --- gitea/apipaginator.go | 69 ++++ gitea/gitea.go | 126 ++++++++ gitea/releases.go | 160 ++++++++++ gitea/structs/activity.go | 25 ++ gitea/structs/activitypub.go | 9 + gitea/structs/admin_user.go | 50 +++ gitea/structs/attachment.go | 23 ++ gitea/structs/commit_status.go | 73 +++++ gitea/structs/commit_status_test.go | 174 ++++++++++ gitea/structs/cron.go | 15 + gitea/structs/doc.go | 4 + gitea/structs/fork.go | 12 + gitea/structs/git_blob.go | 13 + gitea/structs/git_hook.go | 19 ++ gitea/structs/hook.go | 480 ++++++++++++++++++++++++++++ gitea/structs/issue.go | 254 +++++++++++++++ gitea/structs/issue_comment.go | 78 +++++ gitea/structs/issue_label.go | 62 ++++ gitea/structs/issue_milestone.go | 39 +++ gitea/structs/issue_reaction.go | 20 ++ gitea/structs/issue_stopwatch.go | 22 ++ gitea/structs/issue_test.go | 105 ++++++ gitea/structs/issue_tracked_time.go | 35 ++ gitea/structs/lfs_lock.go | 64 ++++ gitea/structs/mirror.go | 27 ++ gitea/structs/miscellaneous.go | 103 ++++++ gitea/structs/nodeinfo.go | 43 +++ gitea/structs/notifications.go | 49 +++ gitea/structs/org.go | 68 ++++ gitea/structs/org_member.go | 9 + gitea/structs/org_team.go | 54 ++++ gitea/structs/package.go | 32 ++ gitea/structs/pull.go | 116 +++++++ gitea/structs/pull_review.go | 104 ++++++ gitea/structs/release.go | 49 +++ gitea/structs/repo.go | 410 ++++++++++++++++++++++++ gitea/structs/repo_actions.go | 120 +++++++ gitea/structs/repo_branch.go | 141 ++++++++ gitea/structs/repo_collaborator.go | 17 + gitea/structs/repo_commit.go | 69 ++++ gitea/structs/repo_compare.go | 10 + gitea/structs/repo_file.go | 172 ++++++++++ gitea/structs/repo_key.go | 39 +++ gitea/structs/repo_note.go | 10 + gitea/structs/repo_refs.go | 18 ++ gitea/structs/repo_tag.go | 66 ++++ gitea/structs/repo_topic.go | 28 ++ gitea/structs/repo_tree.go | 24 ++ gitea/structs/repo_watch.go | 18 ++ gitea/structs/repo_wiki.go | 46 +++ gitea/structs/secret.go | 28 ++ gitea/structs/settings.go | 37 +++ gitea/structs/status.go | 40 +++ gitea/structs/task.go | 30 ++ gitea/structs/user.go | 130 ++++++++ gitea/structs/user_app.go | 51 +++ gitea/structs/user_email.go | 26 ++ gitea/structs/user_gpgkey.go | 50 +++ gitea/structs/user_key.go | 21 ++ gitea/structs/variable.go | 46 +++ gitea/structs/visible_type.go | 58 ++++ gitea/user.go | 25 ++ github/apipaginator.go | 69 ++++ github/github.go | 103 ++++++ github/releases.go | 194 +++++++++++ github/users.go | 76 +++++ go.mod | 8 + go.sum | 29 +- v2/request.go | 18 +- 69 files changed, 4805 insertions(+), 7 deletions(-) create mode 100644 gitea/apipaginator.go create mode 100644 gitea/gitea.go create mode 100644 gitea/releases.go create mode 100644 gitea/structs/activity.go create mode 100644 gitea/structs/activitypub.go create mode 100644 gitea/structs/admin_user.go create mode 100644 gitea/structs/attachment.go create mode 100644 gitea/structs/commit_status.go create mode 100644 gitea/structs/commit_status_test.go create mode 100644 gitea/structs/cron.go create mode 100644 gitea/structs/doc.go create mode 100644 gitea/structs/fork.go create mode 100644 gitea/structs/git_blob.go create mode 100644 gitea/structs/git_hook.go create mode 100644 gitea/structs/hook.go create mode 100644 gitea/structs/issue.go create mode 100644 gitea/structs/issue_comment.go create mode 100644 gitea/structs/issue_label.go create mode 100644 gitea/structs/issue_milestone.go create mode 100644 gitea/structs/issue_reaction.go create mode 100644 gitea/structs/issue_stopwatch.go create mode 100644 gitea/structs/issue_test.go create mode 100644 gitea/structs/issue_tracked_time.go create mode 100644 gitea/structs/lfs_lock.go create mode 100644 gitea/structs/mirror.go create mode 100644 gitea/structs/miscellaneous.go create mode 100644 gitea/structs/nodeinfo.go create mode 100644 gitea/structs/notifications.go create mode 100644 gitea/structs/org.go create mode 100644 gitea/structs/org_member.go create mode 100644 gitea/structs/org_team.go create mode 100644 gitea/structs/package.go create mode 100644 gitea/structs/pull.go create mode 100644 gitea/structs/pull_review.go create mode 100644 gitea/structs/release.go create mode 100644 gitea/structs/repo.go create mode 100644 gitea/structs/repo_actions.go create mode 100644 gitea/structs/repo_branch.go create mode 100644 gitea/structs/repo_collaborator.go create mode 100644 gitea/structs/repo_commit.go create mode 100644 gitea/structs/repo_compare.go create mode 100644 gitea/structs/repo_file.go create mode 100644 gitea/structs/repo_key.go create mode 100644 gitea/structs/repo_note.go create mode 100644 gitea/structs/repo_refs.go create mode 100644 gitea/structs/repo_tag.go create mode 100644 gitea/structs/repo_topic.go create mode 100644 gitea/structs/repo_tree.go create mode 100644 gitea/structs/repo_watch.go create mode 100644 gitea/structs/repo_wiki.go create mode 100644 gitea/structs/secret.go create mode 100644 gitea/structs/settings.go create mode 100644 gitea/structs/status.go create mode 100644 gitea/structs/task.go create mode 100644 gitea/structs/user.go create mode 100644 gitea/structs/user_app.go create mode 100644 gitea/structs/user_email.go create mode 100644 gitea/structs/user_gpgkey.go create mode 100644 gitea/structs/user_key.go create mode 100644 gitea/structs/variable.go create mode 100644 gitea/structs/visible_type.go create mode 100644 gitea/user.go create mode 100644 github/apipaginator.go create mode 100644 github/github.go create mode 100644 github/releases.go create mode 100644 github/users.go diff --git a/gitea/apipaginator.go b/gitea/apipaginator.go new file mode 100644 index 0000000..1b8e920 --- /dev/null +++ b/gitea/apipaginator.go @@ -0,0 +1,69 @@ +package gitea + +import ( + "net/http" + "net/url" + "strconv" + "strings" +) + +type githubPagination struct { + NextPage, PrevPage, FirstPage, LastPage int + NextPageToken, Cursor, Before, After string +} + +func parsePaginator(he http.Header) *githubPagination { + r := &githubPagination{} + if links, ok := he["Link"]; ok && len(links) > 0 { + for link := range strings.SplitSeq(links[0], ",") { + segments := strings.Split(strings.TrimSpace(link), ";") + if len(segments) < 2 { + continue + } + if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { + continue + } + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + q := url.Query() + if cursor := q.Get("cursor"); cursor != "" { + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.Cursor = cursor + } + } + continue + } + page := q.Get("page") + since := q.Get("since") + before := q.Get("before") + after := q.Get("after") + if page == "" && before == "" && after == "" && since == "" { + continue + } + if since != "" && page == "" { + page = since + } + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + if r.NextPage, err = strconv.Atoi(page); err != nil { + r.NextPageToken = page + } + r.After = after + case `rel="prev"`: + r.PrevPage, _ = strconv.Atoi(page) + r.Before = before + case `rel="first"`: + r.FirstPage, _ = strconv.Atoi(page) + case `rel="last"`: + r.LastPage, _ = strconv.Atoi(page) + } + } + } + } + return r +} diff --git a/gitea/gitea.go b/gitea/gitea.go new file mode 100644 index 0000000..d36a66e --- /dev/null +++ b/gitea/gitea.go @@ -0,0 +1,126 @@ +// Client to Gitea API +package gitea + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "time" + + "sirherobrine23.com.br/go-bds/request/v2" +) + +var ( + ErrToken error = errors.New("require Token to request") + ErrBackend error = errors.New("catch 500 error in server api") + ErrNoExist error = errors.New("repository, organization or endpoint dont exists") + + processCodes = request.MapCode{ + 500: errRes5xx("500 error"), + 501: errRes5xx("501 error"), + 401: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + return nil, ErrToken + }, + 403: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + return nil, errors.New("cannot process request, authorization or not have permission") + }, + 429: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + // x-ratelimit-reset + if window, err := strconv.Atoi(res.Header.Get("x-ratelimit-reset")); err == nil { + <-time.After(time.Duration(window)) + } else { + <-time.After(time.Second) + } + return request.ReRequest(res) + }, + } +) + +type giteaError struct { + Errors []string `json:"errors"` + TeaError string `json:"error"` + Message string `json:"message"` + Url string `json:"url"` +} + +func errRes5xx(txt string) func(res *http.Response) (*http.Response, error) { + return func(res *http.Response) (*http.Response, error) { + if strings.Contains(res.Header.Get("Content-Type"), "application/json") { + var data giteaError + defer res.Body.Close() + bodyBuff, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf(txt, ErrBackend, err) + } + + if err := json.Unmarshal(bodyBuff, &data); err != nil { + return nil, fmt.Errorf(txt, ErrBackend, ErrNoExist) + } else if len(data.Errors) > 0 { + return nil, fmt.Errorf(txt, ErrBackend, fmt.Errorf("gitea api, errors: %s, %s", data.Message, strings.Join(data.Errors, ", "))) + } else if data.Message != "" { + return nil, fmt.Errorf(txt, ErrBackend, fmt.Errorf("backed error: %s", data.Message)) + } + } + return nil, fmt.Errorf(txt, ErrBackend) + } +} + +// Make new gitea client to Make API requests, host example: "https://gitea.com/api" +func NewClient(giteaHost, Owner, Repository, Token string) (*Gitea, error) { + apiUrl, err := url.Parse(giteaHost) + if err != nil { + return nil, err + } + + client := &Gitea{ + Host: apiUrl, + Username: Owner, + Repository: Repository, + Token: Token, + } + + // Check if API is OK + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, err := request.MakeRequestWithStatus(apiUrl.ResolveReference(&url.URL{Path: path.Join(apiUrl.Path, "v1/version")}), reqOptions) + if err != nil { + return nil, fmt.Errorf("cannot get gitea version: %s", err) + } + res.Body.Close() + + return client, nil +} + +// Gitea client +type Gitea struct { + Host *url.URL // Gitea api host + Username, Repository string // Repository and owner names + Token string // Gitea token +} + +// Return escapated Owner+/+Repository +func (client Gitea) repoPath() string { + return fmt.Sprintf("%s/%s", url.PathEscape(client.Username), url.PathEscape(client.Repository)) +} + +// Set token to header +func (client Gitea) authHeader(header *request.Header) { + if client.Token != "" { + (*header)["Authorization"] = fmt.Sprintf("token %s", client.Token) + } +} diff --git a/gitea/releases.go b/gitea/releases.go new file mode 100644 index 0000000..0990dc6 --- /dev/null +++ b/gitea/releases.go @@ -0,0 +1,160 @@ +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 { + *err = fmt.Errorf("%s", err2) + } + }(&err) + r, w := io.Pipe() + mw := multipart.NewWriter(w) + 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: r, + 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 +} diff --git a/gitea/structs/activity.go b/gitea/structs/activity.go new file mode 100644 index 0000000..9271349 --- /dev/null +++ b/gitea/structs/activity.go @@ -0,0 +1,25 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +type Activity struct { + ID int `json:"id"` + UserID int `json:"user_id"` // Receiver user + // the type of action + // + // enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request + OpType string `json:"op_type"` + ActUserID int `json:"act_user_id"` + ActUser *User `json:"act_user"` + RepoID int `json:"repo_id"` + Repo *Repository `json:"repo"` + CommentID int `json:"comment_id"` + Comment *Comment `json:"comment"` + RefName string `json:"ref_name"` + IsPrivate bool `json:"is_private"` + Content string `json:"content"` + Created time.Time `json:"created"` +} diff --git a/gitea/structs/activitypub.go b/gitea/structs/activitypub.go new file mode 100644 index 0000000..117eb0b --- /dev/null +++ b/gitea/structs/activitypub.go @@ -0,0 +1,9 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// ActivityPub type +type ActivityPub struct { + Context string `json:"@context"` +} diff --git a/gitea/structs/admin_user.go b/gitea/structs/admin_user.go new file mode 100644 index 0000000..4bb8d56 --- /dev/null +++ b/gitea/structs/admin_user.go @@ -0,0 +1,50 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +// CreateUserOption create user options +type CreateUserOption struct { + SourceID int `json:"source_id"` + LoginName string `json:"login_name"` + // required: true + Username string `json:"username" binding:"Required;Username;MaxSize(40)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"Required;Email;MaxSize(254)"` + Password string `json:"password" binding:"MaxSize(255)"` + MustChangePassword *bool `json:"must_change_password"` + SendNotify bool `json:"send_notify"` + Restricted *bool `json:"restricted"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + + // For explicitly setting the user creation timestamp. Useful when users are + // migrated from other systems. When omitted, the user's creation timestamp + // will be set to "now". + Created *time.Time `json:"created_at"` +} + +// EditUserOption edit user options +type EditUserOption struct { + // required: true + SourceID int `json:"source_id"` + LoginName string `json:"login_name" binding:"Required"` + Email *string `json:"email" binding:"MaxSize(254)"` + FullName *string `json:"full_name" binding:"MaxSize(100)"` + Password string `json:"password" binding:"MaxSize(255)"` + MustChangePassword *bool `json:"must_change_password"` + Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"` + Location *string `json:"location" binding:"MaxSize(50)"` + Description *string `json:"description" binding:"MaxSize(255)"` + Active *bool `json:"active"` + Admin *bool `json:"admin"` + AllowGitHook *bool `json:"allow_git_hook"` + AllowImportLocal *bool `json:"allow_import_local"` + MaxRepoCreation *int `json:"max_repo_creation"` + ProhibitLogin *bool `json:"prohibit_login"` + AllowCreateOrganization *bool `json:"allow_create_organization"` + Restricted *bool `json:"restricted"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` +} diff --git a/gitea/structs/attachment.go b/gitea/structs/attachment.go new file mode 100644 index 0000000..2c22695 --- /dev/null +++ b/gitea/structs/attachment.go @@ -0,0 +1,23 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs // import "code.gitea.io/gitea/modules/structs" + +import ( + "time" +) + +// Attachment a generic attachment +type Attachment struct { + ID int `json:"id"` + Name string `json:"name"` + Size int `json:"size"` + DownloadCount int `json:"download_count"` + Created time.Time `json:"created_at"` + UUID string `json:"uuid"` + DownloadURL string `json:"browser_download_url"` +} + +type EditAttachmentOptions struct { + Name string `json:"name"` +} diff --git a/gitea/structs/commit_status.go b/gitea/structs/commit_status.go new file mode 100644 index 0000000..dc880ef --- /dev/null +++ b/gitea/structs/commit_status.go @@ -0,0 +1,73 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// CommitStatusState holds the state of a CommitStatus +// It can be "pending", "success", "error" and "failure" +type CommitStatusState string + +const ( + // CommitStatusPending is for when the CommitStatus is Pending + CommitStatusPending CommitStatusState = "pending" + // CommitStatusSuccess is for when the CommitStatus is Success + CommitStatusSuccess CommitStatusState = "success" + // CommitStatusError is for when the CommitStatus is Error + CommitStatusError CommitStatusState = "error" + // CommitStatusFailure is for when the CommitStatus is Failure + CommitStatusFailure CommitStatusState = "failure" + // CommitStatusWarning is for when the CommitStatus is Warning + CommitStatusWarning CommitStatusState = "warning" +) + +var commitStatusPriorities = map[CommitStatusState]int{ + CommitStatusError: 0, + CommitStatusFailure: 1, + CommitStatusWarning: 2, + CommitStatusPending: 3, + CommitStatusSuccess: 4, +} + +func (css CommitStatusState) String() string { + return string(css) +} + +// NoBetterThan returns true if this State is no better than the given State +// This function only handles the states defined in CommitStatusPriorities +func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool { + // NoBetterThan only handles the 5 states above + if _, exist := commitStatusPriorities[css]; !exist { + return false + } + + if _, exist := commitStatusPriorities[css2]; !exist { + return false + } + + return commitStatusPriorities[css] <= commitStatusPriorities[css2] +} + +// IsPending represents if commit status state is pending +func (css CommitStatusState) IsPending() bool { + return css == CommitStatusPending +} + +// IsSuccess represents if commit status state is success +func (css CommitStatusState) IsSuccess() bool { + return css == CommitStatusSuccess +} + +// IsError represents if commit status state is error +func (css CommitStatusState) IsError() bool { + return css == CommitStatusError +} + +// IsFailure represents if commit status state is failure +func (css CommitStatusState) IsFailure() bool { + return css == CommitStatusFailure +} + +// IsWarning represents if commit status state is warning +func (css CommitStatusState) IsWarning() bool { + return css == CommitStatusWarning +} diff --git a/gitea/structs/commit_status_test.go b/gitea/structs/commit_status_test.go new file mode 100644 index 0000000..88e09aa --- /dev/null +++ b/gitea/structs/commit_status_test.go @@ -0,0 +1,174 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNoBetterThan(t *testing.T) { + type args struct { + css CommitStatusState + css2 CommitStatusState + } + var unExpectedState CommitStatusState + tests := []struct { + name string + args args + want bool + }{ + { + name: "success is no better than success", + args: args{ + css: CommitStatusSuccess, + css2: CommitStatusSuccess, + }, + want: true, + }, + { + name: "success is no better than pending", + args: args{ + css: CommitStatusSuccess, + css2: CommitStatusPending, + }, + want: false, + }, + { + name: "success is no better than failure", + args: args{ + css: CommitStatusSuccess, + css2: CommitStatusFailure, + }, + want: false, + }, + { + name: "success is no better than error", + args: args{ + css: CommitStatusSuccess, + css2: CommitStatusError, + }, + want: false, + }, + { + name: "pending is no better than success", + args: args{ + css: CommitStatusPending, + css2: CommitStatusSuccess, + }, + want: true, + }, + { + name: "pending is no better than pending", + args: args{ + css: CommitStatusPending, + css2: CommitStatusPending, + }, + want: true, + }, + { + name: "pending is no better than failure", + args: args{ + css: CommitStatusPending, + css2: CommitStatusFailure, + }, + want: false, + }, + { + name: "pending is no better than error", + args: args{ + css: CommitStatusPending, + css2: CommitStatusError, + }, + want: false, + }, + { + name: "failure is no better than success", + args: args{ + css: CommitStatusFailure, + css2: CommitStatusSuccess, + }, + want: true, + }, + { + name: "failure is no better than pending", + args: args{ + css: CommitStatusFailure, + css2: CommitStatusPending, + }, + want: true, + }, + { + name: "failure is no better than failure", + args: args{ + css: CommitStatusFailure, + css2: CommitStatusFailure, + }, + want: true, + }, + { + name: "failure is no better than error", + args: args{ + css: CommitStatusFailure, + css2: CommitStatusError, + }, + want: false, + }, + { + name: "error is no better than success", + args: args{ + css: CommitStatusError, + css2: CommitStatusSuccess, + }, + want: true, + }, + { + name: "error is no better than pending", + args: args{ + css: CommitStatusError, + css2: CommitStatusPending, + }, + want: true, + }, + { + name: "error is no better than failure", + args: args{ + css: CommitStatusError, + css2: CommitStatusFailure, + }, + want: true, + }, + { + name: "error is no better than error", + args: args{ + css: CommitStatusError, + css2: CommitStatusError, + }, + want: true, + }, + { + name: "unExpectedState is no better than success", + args: args{ + css: unExpectedState, + css2: CommitStatusSuccess, + }, + want: false, + }, + { + name: "unExpectedState is no better than unExpectedState", + args: args{ + css: unExpectedState, + css2: unExpectedState, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.args.css.NoBetterThan(tt.args.css2) + assert.Equal(t, tt.want, result) + }) + } +} diff --git a/gitea/structs/cron.go b/gitea/structs/cron.go new file mode 100644 index 0000000..3c8b6cb --- /dev/null +++ b/gitea/structs/cron.go @@ -0,0 +1,15 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +// Cron represents a Cron task +type Cron struct { + Name string `json:"name"` + Schedule string `json:"schedule"` + Next time.Time `json:"next"` + Prev time.Time `json:"prev"` + ExecTimes int `json:"exec_times"` +} diff --git a/gitea/structs/doc.go b/gitea/structs/doc.go new file mode 100644 index 0000000..0db0a25 --- /dev/null +++ b/gitea/structs/doc.go @@ -0,0 +1,4 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs diff --git a/gitea/structs/fork.go b/gitea/structs/fork.go new file mode 100644 index 0000000..eb7774a --- /dev/null +++ b/gitea/structs/fork.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// CreateForkOption options for creating a fork +type CreateForkOption struct { + // organization name, if forking into an organization + Organization *string `json:"organization"` + // name of the forked repository + Name *string `json:"name"` +} diff --git a/gitea/structs/git_blob.go b/gitea/structs/git_blob.go new file mode 100644 index 0000000..3824829 --- /dev/null +++ b/gitea/structs/git_blob.go @@ -0,0 +1,13 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// GitBlobResponse represents a git blob +type GitBlobResponse struct { + Content string `json:"content"` + Encoding string `json:"encoding"` + URL string `json:"url"` + SHA string `json:"sha"` + Size int `json:"size"` +} diff --git a/gitea/structs/git_hook.go b/gitea/structs/git_hook.go new file mode 100644 index 0000000..2023025 --- /dev/null +++ b/gitea/structs/git_hook.go @@ -0,0 +1,19 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// GitHook represents a Git repository hook +type GitHook struct { + Name string `json:"name"` + IsActive bool `json:"is_active"` + Content string `json:"content,omitempty"` +} + +// GitHookList represents a list of Git hooks +type GitHookList []*GitHook + +// EditGitHookOption options when modifying one Git hook +type EditGitHookOption struct { + Content string `json:"content"` +} diff --git a/gitea/structs/hook.go b/gitea/structs/hook.go new file mode 100644 index 0000000..060cbee --- /dev/null +++ b/gitea/structs/hook.go @@ -0,0 +1,480 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "errors" + "strings" + "time" + + "code.gitea.io/gitea/modules/json" +) + +// ErrInvalidReceiveHook FIXME +var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webhook") + +// Hook a hook is a web hook when one repository changed +type Hook struct { + ID int `json:"id"` + Type string `json:"type"` + BranchFilter string `json:"branch_filter"` + URL string `json:"-"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + AuthorizationHeader string `json:"authorization_header"` + Active bool `json:"active"` + Updated time.Time `json:"updated_at"` + Created time.Time `json:"created_at"` +} + +// HookList represents a list of API hook. +type HookList []*Hook + +// CreateHookOptionConfig has all config options in it +// required are "content_type" and "url" Required +type CreateHookOptionConfig map[string]string + +// CreateHookOption options when create a hook +type CreateHookOption struct { + // required: true + // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist + Type string `json:"type" binding:"Required"` + // required: true + Config CreateHookOptionConfig `json:"config" binding:"Required"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + AuthorizationHeader string `json:"authorization_header"` + // default: false + Active bool `json:"active"` +} + +// EditHookOption options when modify one hook +type EditHookOption struct { + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + AuthorizationHeader string `json:"authorization_header"` + Active *bool `json:"active"` +} + +// Payloader payload is some part of one hook +type Payloader interface { + JSONPayload() ([]byte, error) +} + +// PayloadUser represents the author or committer of a commit +type PayloadUser struct { + // Full name of the commit author + Name string `json:"name"` + Email string `json:"email"` + UserName string `json:"username"` +} + +// FIXME: consider using same format as API when commits API are added. +// applies to PayloadCommit and PayloadCommitVerification + +// PayloadCommit represents a commit +type PayloadCommit struct { + // sha1 hash of the commit + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author *PayloadUser `json:"author"` + Committer *PayloadUser `json:"committer"` + Verification *PayloadCommitVerification `json:"verification"` + Timestamp time.Time `json:"timestamp"` + Added []string `json:"added"` + Removed []string `json:"removed"` + Modified []string `json:"modified"` +} + +// PayloadCommitVerification represents the GPG verification of a commit +type PayloadCommitVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Signer *PayloadUser `json:"signer"` + Payload string `json:"payload"` +} + +var ( + _ Payloader = &CreatePayload{} + _ Payloader = &DeletePayload{} + _ Payloader = &ForkPayload{} + _ Payloader = &PushPayload{} + _ Payloader = &IssuePayload{} + _ Payloader = &IssueCommentPayload{} + _ Payloader = &PullRequestPayload{} + _ Payloader = &RepositoryPayload{} + _ Payloader = &ReleasePayload{} + _ Payloader = &PackagePayload{} +) + +// CreatePayload represents a payload information of create event. +type CreatePayload struct { + Sha string `json:"sha"` + Ref string `json:"ref"` + RefType string `json:"ref_type"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload return payload information +func (p *CreatePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ParseCreateHook parses create event hook content. +func ParseCreateHook(raw []byte) (*CreatePayload, error) { + hook := new(CreatePayload) + if err := json.Unmarshal(raw, hook); err != nil { + return nil, err + } + + // it is possible the JSON was parsed, however, + // was not from Gogs (maybe was from Bitbucket) + // So we'll check to be sure certain key fields + // were populated + switch { + case hook.Repo == nil: + return nil, ErrInvalidReceiveHook + case len(hook.Ref) == 0: + return nil, ErrInvalidReceiveHook + } + return hook, nil +} + +// PusherType define the type to push +type PusherType string + +// describe all the PusherTypes +const ( + PusherTypeUser PusherType = "user" +) + +// DeletePayload represents delete payload +type DeletePayload struct { + Ref string `json:"ref"` + RefType string `json:"ref_type"` + PusherType PusherType `json:"pusher_type"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *DeletePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ForkPayload represents fork payload +type ForkPayload struct { + Forkee *Repository `json:"forkee"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *ForkPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// HookIssueCommentAction defines hook issue comment action +type HookIssueCommentAction string + +// all issue comment actions +const ( + HookIssueCommentCreated HookIssueCommentAction = "created" + HookIssueCommentEdited HookIssueCommentAction = "edited" + HookIssueCommentDeleted HookIssueCommentAction = "deleted" +) + +// IssueCommentPayload represents a payload information of issue comment event. +type IssueCommentPayload struct { + Action HookIssueCommentAction `json:"action"` + Issue *Issue `json:"issue"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Comment *Comment `json:"comment"` + Changes *ChangesPayload `json:"changes,omitempty"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` + IsPull bool `json:"is_pull"` +} + +// JSONPayload implements Payload +func (p *IssueCommentPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// HookReleaseAction defines hook release action type +type HookReleaseAction string + +// all release actions +const ( + HookReleasePublished HookReleaseAction = "published" + HookReleaseUpdated HookReleaseAction = "updated" + HookReleaseDeleted HookReleaseAction = "deleted" +) + +// ReleasePayload represents a payload information of release event. +type ReleasePayload struct { + Action HookReleaseAction `json:"action"` + Release *Release `json:"release"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *ReleasePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// PushPayload represents a payload information of push event. +type PushPayload struct { + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + CompareURL string `json:"compare_url"` + Commits []*PayloadCommit `json:"commits"` + TotalCommits int `json:"total_commits"` + HeadCommit *PayloadCommit `json:"head_commit"` + Repo *Repository `json:"repository"` + Pusher *User `json:"pusher"` + Sender *User `json:"sender"` +} + +// JSONPayload FIXME +func (p *PushPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ParsePushHook parses push event hook content. +func ParsePushHook(raw []byte) (*PushPayload, error) { + hook := new(PushPayload) + if err := json.Unmarshal(raw, hook); err != nil { + return nil, err + } + + switch { + case hook.Repo == nil: + return nil, ErrInvalidReceiveHook + case len(hook.Ref) == 0: + return nil, ErrInvalidReceiveHook + } + return hook, nil +} + +// Branch returns branch name from a payload +func (p *PushPayload) Branch() string { + return strings.ReplaceAll(p.Ref, "refs/heads/", "") +} + +// HookIssueAction FIXME +type HookIssueAction string + +const ( + // HookIssueOpened opened + HookIssueOpened HookIssueAction = "opened" + // HookIssueClosed closed + HookIssueClosed HookIssueAction = "closed" + // HookIssueReOpened reopened + HookIssueReOpened HookIssueAction = "reopened" + // HookIssueEdited edited + HookIssueEdited HookIssueAction = "edited" + // HookIssueAssigned assigned + HookIssueAssigned HookIssueAction = "assigned" + // HookIssueUnassigned unassigned + HookIssueUnassigned HookIssueAction = "unassigned" + // HookIssueLabelUpdated label_updated + HookIssueLabelUpdated HookIssueAction = "label_updated" + // HookIssueLabelCleared label_cleared + HookIssueLabelCleared HookIssueAction = "label_cleared" + // HookIssueSynchronized synchronized + HookIssueSynchronized HookIssueAction = "synchronized" + // HookIssueMilestoned is an issue action for when a milestone is set on an issue. + HookIssueMilestoned HookIssueAction = "milestoned" + // HookIssueDemilestoned is an issue action for when a milestone is cleared on an issue. + HookIssueDemilestoned HookIssueAction = "demilestoned" + // HookIssueReviewed is an issue action for when a pull request is reviewed + HookIssueReviewed HookIssueAction = "reviewed" + // HookIssueReviewRequested is an issue action for when a reviewer is requested for a pull request. + HookIssueReviewRequested HookIssueAction = "review_requested" + // HookIssueReviewRequestRemoved is an issue action for removing a review request to someone on a pull request. + HookIssueReviewRequestRemoved HookIssueAction = "review_request_removed" +) + +// IssuePayload represents the payload information that is sent along with an issue event. +type IssuePayload struct { + Action HookIssueAction `json:"action"` + Index int `json:"number"` + Changes *ChangesPayload `json:"changes,omitempty"` + Issue *Issue `json:"issue"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` + CommitID string `json:"commit_id"` +} + +// JSONPayload encodes the IssuePayload to JSON, with an indentation of two spaces. +func (p *IssuePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ChangesFromPayload FIXME +type ChangesFromPayload struct { + From string `json:"from"` +} + +// ChangesPayload represents the payload information of issue change +type ChangesPayload struct { + Title *ChangesFromPayload `json:"title,omitempty"` + Body *ChangesFromPayload `json:"body,omitempty"` + Ref *ChangesFromPayload `json:"ref,omitempty"` +} + +// PullRequestPayload represents a payload information of pull request event. +type PullRequestPayload struct { + Action HookIssueAction `json:"action"` + Index int `json:"number"` + Changes *ChangesPayload `json:"changes,omitempty"` + PullRequest *PullRequest `json:"pull_request"` + RequestedReviewer *User `json:"requested_reviewer"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` + CommitID string `json:"commit_id"` + Review *ReviewPayload `json:"review"` +} + +// JSONPayload FIXME +func (p *PullRequestPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ReviewPayload FIXME +type ReviewPayload struct { + Type string `json:"type"` + Content string `json:"content"` +} + +// HookWikiAction an action that happens to a wiki page +type HookWikiAction string + +const ( + // HookWikiCreated created + HookWikiCreated HookWikiAction = "created" + // HookWikiEdited edited + HookWikiEdited HookWikiAction = "edited" + // HookWikiDeleted deleted + HookWikiDeleted HookWikiAction = "deleted" +) + +// WikiPayload payload for repository webhooks +type WikiPayload struct { + Action HookWikiAction `json:"action"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` + Page string `json:"page"` + Comment string `json:"comment"` +} + +// JSONPayload JSON representation of the payload +func (p *WikiPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// HookRepoAction an action that happens to a repo +type HookRepoAction string + +const ( + // HookRepoCreated created + HookRepoCreated HookRepoAction = "created" + // HookRepoDeleted deleted + HookRepoDeleted HookRepoAction = "deleted" +) + +// RepositoryPayload payload for repository webhooks +type RepositoryPayload struct { + Action HookRepoAction `json:"action"` + Repository *Repository `json:"repository"` + Organization *User `json:"organization"` + Sender *User `json:"sender"` +} + +// JSONPayload JSON representation of the payload +func (p *RepositoryPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// HookPackageAction an action that happens to a package +type HookPackageAction string + +const ( + // HookPackageCreated created + HookPackageCreated HookPackageAction = "created" + // HookPackageDeleted deleted + HookPackageDeleted HookPackageAction = "deleted" +) + +// PackagePayload represents a package payload +type PackagePayload struct { + Action HookPackageAction `json:"action"` + Repository *Repository `json:"repository"` + Package *Package `json:"package"` + Organization *Organization `json:"organization"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *PackagePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// WorkflowDispatchPayload represents a workflow dispatch payload +type WorkflowDispatchPayload struct { + Workflow string `json:"workflow"` + Ref string `json:"ref"` + Inputs map[string]any `json:"inputs"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// CommitStatusPayload represents a payload information of commit status event. +type CommitStatusPayload struct { + // TODO: add Branches per https://docs.github.com/en/webhooks/webhook-events-and-payloads#status + Commit *PayloadCommit `json:"commit"` + Context string `json:"context"` + CreatedAt time.Time `json:"created_at"` + Description string `json:"description"` + ID int `json:"id"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` + SHA string `json:"sha"` + State string `json:"state"` + TargetURL string `json:"target_url"` + UpdatedAt *time.Time `json:"updated_at"` +} + +// JSONPayload implements Payload +func (p *CommitStatusPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// WorkflowJobPayload represents a payload information of workflow job event. +type WorkflowJobPayload struct { + Action string `json:"action"` + WorkflowJob *ActionWorkflowJob `json:"workflow_job"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// JSONPayload implements Payload +func (p *WorkflowJobPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} diff --git a/gitea/structs/issue.go b/gitea/structs/issue.go new file mode 100644 index 0000000..cfa68fc --- /dev/null +++ b/gitea/structs/issue.go @@ -0,0 +1,254 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "fmt" + "path" + "slices" + "strings" + "time" + + "gopkg.in/yaml.v3" +) + +// StateType issue state type +type StateType string + +const ( + // StateOpen pr is opend + StateOpen StateType = "open" + // StateClosed pr is closed + StateClosed StateType = "closed" + // StateAll is all + StateAll StateType = "all" +) + +// PullRequestMeta PR info if an issue is a PR +type PullRequestMeta struct { + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + IsWorkInProgress bool `json:"draft"` + HTMLURL string `json:"html_url"` +} + +// RepositoryMeta basic repository information +type RepositoryMeta struct { + ID int `json:"id"` + Name string `json:"name"` + Owner string `json:"owner"` + FullName string `json:"full_name"` +} + +// Issue represents an issue in a repository +type Issue struct { + ID int `json:"id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Index int `json:"number"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int `json:"original_author_id"` + Title string `json:"title"` + Body string `json:"body"` + Ref string `json:"ref"` + Attachments []*Attachment `json:"assets"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + // deprecated + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + // Whether the issue is open or closed + // + // type: string + // enum: open,closed + State StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_date"` + + PullRequest *PullRequestMeta `json:"pull_request"` + Repo *RepositoryMeta `json:"repository"` + + PinOrder int `json:"pin_order"` +} + +// CreateIssueOption options to create one issue +type CreateIssueOption struct { + // required:true + Title string `json:"title" binding:"Required"` + Body string `json:"body"` + Ref string `json:"ref"` + // deprecated + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Deadline *time.Time `json:"due_date"` + // milestone id + Milestone int `json:"milestone"` + // list of label ids + Labels []int `json:"labels"` + Closed bool `json:"closed"` +} + +// EditIssueOption options for editing an issue +type EditIssueOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Ref *string `json:"ref"` + // deprecated + Assignee *string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone *int `json:"milestone"` + State *string `json:"state"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` +} + +// EditDeadlineOption options for creating a deadline +type EditDeadlineOption struct { + // required:true + Deadline *time.Time `json:"due_date"` +} + +// IssueDeadline represents an issue deadline +type IssueDeadline struct { + Deadline *time.Time `json:"due_date"` +} + +// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes" +type IssueFormFieldType string + +const ( + IssueFormFieldTypeMarkdown IssueFormFieldType = "markdown" + IssueFormFieldTypeTextarea IssueFormFieldType = "textarea" + IssueFormFieldTypeInput IssueFormFieldType = "input" + IssueFormFieldTypeDropdown IssueFormFieldType = "dropdown" + IssueFormFieldTypeCheckboxes IssueFormFieldType = "checkboxes" +) + +// IssueFormField represents a form field +type IssueFormField struct { + Type IssueFormFieldType `json:"type" yaml:"type"` + ID string `json:"id" yaml:"id"` + Attributes map[string]any `json:"attributes" yaml:"attributes"` + Validations map[string]any `json:"validations" yaml:"validations"` + Visible []IssueFormFieldVisible `json:"visible,omitempty"` +} + +func (iff IssueFormField) VisibleOnForm() bool { + if len(iff.Visible) == 0 { + return true + } + return slices.Contains(iff.Visible, IssueFormFieldVisibleForm) +} + +func (iff IssueFormField) VisibleInContent() bool { + if len(iff.Visible) == 0 { + // we have our markdown exception + return iff.Type != IssueFormFieldTypeMarkdown + } + return slices.Contains(iff.Visible, IssueFormFieldVisibleContent) +} + +// IssueFormFieldVisible defines issue form field visible +type IssueFormFieldVisible string + +const ( + IssueFormFieldVisibleForm IssueFormFieldVisible = "form" + IssueFormFieldVisibleContent IssueFormFieldVisible = "content" +) + +// IssueTemplate represents an issue template for a repository +type IssueTemplate struct { + Name string `json:"name" yaml:"name"` + Title string `json:"title" yaml:"title"` + About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible + Labels IssueTemplateStringSlice `json:"labels" yaml:"labels"` + Assignees IssueTemplateStringSlice `json:"assignees" yaml:"assignees"` + Ref string `json:"ref" yaml:"ref"` + Content string `json:"content" yaml:"-"` + Fields []*IssueFormField `json:"body" yaml:"body"` + FileName string `json:"file_name" yaml:"-"` +} + +type IssueTemplateStringSlice []string + +func (l *IssueTemplateStringSlice) UnmarshalYAML(value *yaml.Node) error { + var labels []string + if value.IsZero() { + *l = labels + return nil + } + switch value.Kind { + case yaml.ScalarNode: + str := "" + err := value.Decode(&str) + if err != nil { + return err + } + for _, v := range strings.Split(str, ",") { + if v = strings.TrimSpace(v); v == "" { + continue + } + labels = append(labels, v) + } + *l = labels + return nil + case yaml.SequenceNode: + if err := value.Decode(&labels); err != nil { + return err + } + *l = labels + return nil + } + return fmt.Errorf("line %d: cannot unmarshal %s into IssueTemplateStringSlice", value.Line, value.ShortTag()) +} + +type IssueConfigContactLink struct { + Name string `json:"name" yaml:"name"` + URL string `json:"url" yaml:"url"` + About string `json:"about" yaml:"about"` +} + +type IssueConfig struct { + BlankIssuesEnabled bool `json:"blank_issues_enabled" yaml:"blank_issues_enabled"` + ContactLinks []IssueConfigContactLink `json:"contact_links" yaml:"contact_links"` +} + +type IssueConfigValidation struct { + Valid bool `json:"valid"` + Message string `json:"message"` +} + +// IssueTemplateType defines issue template type +type IssueTemplateType string + +const ( + IssueTemplateTypeMarkdown IssueTemplateType = "md" + IssueTemplateTypeYaml IssueTemplateType = "yaml" +) + +// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known +func (it IssueTemplate) Type() IssueTemplateType { + if base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" { + // ignore config.yaml which is a special configuration file + return "" + } + if ext := path.Ext(it.FileName); ext == ".md" { + return IssueTemplateTypeMarkdown + } else if ext == ".yaml" || ext == ".yml" { + return IssueTemplateTypeYaml + } + return "" +} + +// IssueMeta basic issue information +type IssueMeta struct { + Index int `json:"index"` + Owner string `json:"owner"` + Name string `json:"repo"` +} diff --git a/gitea/structs/issue_comment.go b/gitea/structs/issue_comment.go new file mode 100644 index 0000000..0ddc82a --- /dev/null +++ b/gitea/structs/issue_comment.go @@ -0,0 +1,78 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Comment represents a comment on a commit or issue +type Comment struct { + ID int `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int `json:"original_author_id"` + Body string `json:"body"` + Attachments []*Attachment `json:"assets"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateIssueCommentOption options for creating a comment on an issue +type CreateIssueCommentOption struct { + // required:true + Body string `json:"body" binding:"Required"` +} + +// EditIssueCommentOption options for editing a comment +type EditIssueCommentOption struct { + // required: true + Body string `json:"body" binding:"Required"` +} + +// TimelineComment represents a timeline comment (comment of any type) on a commit or issue +type TimelineComment struct { + ID int `json:"id"` + Type string `json:"type"` + + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + Body string `json:"body"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + + OldProjectID int `json:"old_project_id"` + ProjectID int `json:"project_id"` + OldMilestone *Milestone `json:"old_milestone"` + Milestone *Milestone `json:"milestone"` + TrackedTime *TrackedTime `json:"tracked_time"` + OldTitle string `json:"old_title"` + NewTitle string `json:"new_title"` + OldRef string `json:"old_ref"` + NewRef string `json:"new_ref"` + + RefIssue *Issue `json:"ref_issue"` + RefComment *Comment `json:"ref_comment"` + RefAction string `json:"ref_action"` + // commit SHA where issue/PR was referenced + RefCommitSHA string `json:"ref_commit_sha"` + + ReviewID int `json:"review_id"` + + Label *Label `json:"label"` + + Assignee *User `json:"assignee"` + AssigneeTeam *Team `json:"assignee_team"` + // whether the assignees were removed or added + RemovedAssignee bool `json:"removed_assignee"` + + ResolveDoer *User `json:"resolve_doer"` + + DependentIssue *Issue `json:"dependent_issue"` +} diff --git a/gitea/structs/issue_label.go b/gitea/structs/issue_label.go new file mode 100644 index 0000000..ca65dd5 --- /dev/null +++ b/gitea/structs/issue_label.go @@ -0,0 +1,62 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Label a label to an issue or a pr +type Label struct { + ID int `json:"id"` + Name string `json:"name"` + // example: false + Exclusive bool `json:"exclusive"` + // example: false + IsArchived bool `json:"is_archived"` + // example: 00aabb + Color string `json:"color"` + Description string `json:"description"` + URL string `json:"url"` +} + +// CreateLabelOption options for creating a label +type CreateLabelOption struct { + // required:true + Name string `json:"name" binding:"Required"` + // example: false + Exclusive bool `json:"exclusive"` + // required:true + // example: #00aabb + Color string `json:"color" binding:"Required"` + Description string `json:"description"` + // example: false + IsArchived bool `json:"is_archived"` +} + +// EditLabelOption options for editing a label +type EditLabelOption struct { + Name *string `json:"name"` + // example: false + Exclusive *bool `json:"exclusive"` + // example: #00aabb + Color *string `json:"color"` + Description *string `json:"description"` + // example: false + IsArchived *bool `json:"is_archived"` +} + +// IssueLabelsOption a collection of labels +type IssueLabelsOption struct { + // Labels can be a list of integers representing label IDs + // or a list of strings representing label names + Labels []any `json:"labels"` +} + +// LabelTemplate info of a Label template +type LabelTemplate struct { + Name string `json:"name"` + // example: false + Exclusive bool `json:"exclusive"` + // example: 00aabb + Color string `json:"color"` + Description string `json:"description"` +} diff --git a/gitea/structs/issue_milestone.go b/gitea/structs/issue_milestone.go new file mode 100644 index 0000000..315d094 --- /dev/null +++ b/gitea/structs/issue_milestone.go @@ -0,0 +1,39 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Milestone milestone is a collection of issues on one repository +type Milestone struct { + ID int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + OpenIssues int `json:"open_issues"` + ClosedIssues int `json:"closed_issues"` + Created time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_on"` +} + +// CreateMilestoneOption options for creating a milestone +type CreateMilestoneOption struct { + Title string `json:"title"` + Description string `json:"description"` + Deadline *time.Time `json:"due_on"` + // enum: open,closed + State string `json:"state"` +} + +// EditMilestoneOption options for editing a milestone +type EditMilestoneOption struct { + Title string `json:"title"` + Description *string `json:"description"` + State *string `json:"state"` + Deadline *time.Time `json:"due_on"` +} diff --git a/gitea/structs/issue_reaction.go b/gitea/structs/issue_reaction.go new file mode 100644 index 0000000..3064c6a --- /dev/null +++ b/gitea/structs/issue_reaction.go @@ -0,0 +1,20 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// EditReactionOption contain the reaction type +type EditReactionOption struct { + Reaction string `json:"content"` +} + +// Reaction contain one reaction +type Reaction struct { + User *User `json:"user"` + Reaction string `json:"content"` + Created time.Time `json:"created_at"` +} diff --git a/gitea/structs/issue_stopwatch.go b/gitea/structs/issue_stopwatch.go new file mode 100644 index 0000000..0aef2ba --- /dev/null +++ b/gitea/structs/issue_stopwatch.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// StopWatch represent a running stopwatch +type StopWatch struct { + Created time.Time `json:"created"` + Seconds int `json:"seconds"` + Duration string `json:"duration"` + IssueIndex int `json:"issue_index"` + IssueTitle string `json:"issue_title"` + RepoOwnerName string `json:"repo_owner_name"` + RepoName string `json:"repo_name"` +} + +// StopWatches represent a list of stopwatches +type StopWatches []StopWatch diff --git a/gitea/structs/issue_test.go b/gitea/structs/issue_test.go new file mode 100644 index 0000000..55bd01d --- /dev/null +++ b/gitea/structs/issue_test.go @@ -0,0 +1,105 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +func TestIssueTemplate_Type(t *testing.T) { + tests := []struct { + fileName string + want IssueTemplateType + }{ + { + fileName: ".gitea/ISSUE_TEMPLATE/bug_report.yaml", + want: IssueTemplateTypeYaml, + }, + { + fileName: ".gitea/ISSUE_TEMPLATE/bug_report.md", + want: IssueTemplateTypeMarkdown, + }, + { + fileName: ".gitea/ISSUE_TEMPLATE/bug_report.txt", + want: "", + }, + { + fileName: ".gitea/ISSUE_TEMPLATE/config.yaml", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.fileName, func(t *testing.T) { + it := IssueTemplate{ + FileName: tt.fileName, + } + assert.Equal(t, tt.want, it.Type()) + }) + } +} + +func TestIssueTemplateStringSlice_UnmarshalYAML(t *testing.T) { + tests := []struct { + name string + content string + tmpl *IssueTemplate + want *IssueTemplate + wantErr string + }{ + { + name: "array", + content: `labels: ["a", "b", "c"]`, + tmpl: &IssueTemplate{ + Labels: []string{"should_be_overwrote"}, + }, + want: &IssueTemplate{ + Labels: []string{"a", "b", "c"}, + }, + }, + { + name: "string", + content: `labels: "a,b,c"`, + tmpl: &IssueTemplate{ + Labels: []string{"should_be_overwrote"}, + }, + want: &IssueTemplate{ + Labels: []string{"a", "b", "c"}, + }, + }, + { + name: "empty", + content: `labels:`, + tmpl: &IssueTemplate{ + Labels: []string{"should_be_overwrote"}, + }, + want: &IssueTemplate{ + Labels: nil, + }, + }, + { + name: "error", + content: ` +labels: + a: aa + b: bb +`, + tmpl: &IssueTemplate{}, + wantErr: "line 3: cannot unmarshal !!map into IssueTemplateStringSlice", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := yaml.Unmarshal([]byte(tt.content), tt.tmpl) + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, tt.tmpl) + } + }) + } +} diff --git a/gitea/structs/issue_tracked_time.go b/gitea/structs/issue_tracked_time.go new file mode 100644 index 0000000..af71b05 --- /dev/null +++ b/gitea/structs/issue_tracked_time.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// AddTimeOption options for adding time to an issue +type AddTimeOption struct { + // time in seconds + // required: true + Time int `json:"time" binding:"Required"` + Created time.Time `json:"created"` + // User who spent the time (optional) + User string `json:"user_name"` +} + +// TrackedTime worked time for an issue / pr +type TrackedTime struct { + ID int `json:"id"` + Created time.Time `json:"created"` + // Time in seconds + Time int `json:"time"` + // deprecated (only for backwards compatibility) + UserID int `json:"user_id"` + UserName string `json:"user_name"` + // deprecated (only for backwards compatibility) + IssueID int `json:"issue_id"` + Issue *Issue `json:"issue"` +} + +// TrackedTimeList represents a list of tracked times +type TrackedTimeList []*TrackedTime diff --git a/gitea/structs/lfs_lock.go b/gitea/structs/lfs_lock.go new file mode 100644 index 0000000..6b4c0bc --- /dev/null +++ b/gitea/structs/lfs_lock.go @@ -0,0 +1,64 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// LFSLock represent a lock +// for use with the locks API. +type LFSLock struct { + ID string `json:"id"` + Path string `json:"path"` + LockedAt time.Time `json:"locked_at"` + Owner *LFSLockOwner `json:"owner"` +} + +// LFSLockOwner represent a lock owner +// for use with the locks API. +type LFSLockOwner struct { + Name string `json:"name"` +} + +// LFSLockRequest contains the path of the lock to create +// https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md#create-lock +type LFSLockRequest struct { + Path string `json:"path"` +} + +// LFSLockResponse represent a lock created +// https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md#create-lock +type LFSLockResponse struct { + Lock *LFSLock `json:"lock"` +} + +// LFSLockList represent a list of lock requested +// https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md#list-locks +type LFSLockList struct { + Locks []*LFSLock `json:"locks"` + Next string `json:"next_cursor,omitempty"` +} + +// LFSLockListVerify represent a list of lock verification requested +// https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md#list-locks-for-verification +type LFSLockListVerify struct { + Ours []*LFSLock `json:"ours"` + Theirs []*LFSLock `json:"theirs"` + Next string `json:"next_cursor,omitempty"` +} + +// LFSLockError contains information on the error that occurs +type LFSLockError struct { + Message string `json:"message"` + Lock *LFSLock `json:"lock,omitempty"` + Documentation string `json:"documentation_url,omitempty"` + RequestID string `json:"request_id,omitempty"` +} + +// LFSLockDeleteRequest contains params of a delete request +// https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md#delete-lock +type LFSLockDeleteRequest struct { + Force bool `json:"force"` +} diff --git a/gitea/structs/mirror.go b/gitea/structs/mirror.go new file mode 100644 index 0000000..0f9fb19 --- /dev/null +++ b/gitea/structs/mirror.go @@ -0,0 +1,27 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +// CreatePushMirrorOption represents need information to create a push mirror of a repository. +type CreatePushMirrorOption struct { + RemoteAddress string `json:"remote_address"` + RemoteUsername string `json:"remote_username"` + RemotePassword string `json:"remote_password"` + Interval string `json:"interval"` + SyncOnCommit bool `json:"sync_on_commit"` +} + +// PushMirror represents information of a push mirror +type PushMirror struct { + RepoName string `json:"repo_name"` + RemoteName string `json:"remote_name"` + RemoteAddress string `json:"remote_address"` + CreatedUnix time.Time `json:"created"` + LastUpdateUnix *time.Time `json:"last_update"` + LastError string `json:"last_error"` + Interval string `json:"interval"` + SyncOnCommit bool `json:"sync_on_commit"` +} diff --git a/gitea/structs/miscellaneous.go b/gitea/structs/miscellaneous.go new file mode 100644 index 0000000..b37d079 --- /dev/null +++ b/gitea/structs/miscellaneous.go @@ -0,0 +1,103 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// SearchResults results of a successful search +type SearchResults struct { + OK bool `json:"ok"` + Data []*Repository `json:"data"` +} + +// SearchError error of a failed search +type SearchError struct { + OK bool `json:"ok"` + Error string `json:"error"` +} + +// MarkupOption markup options +type MarkupOption struct { + // Text markup to render + // + // in: body + Text string + // Mode to render (markdown, comment, wiki, file) + // + // in: body + Mode string + // URL path for rendering issue, media and file links + // Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir} + // + // in: body + Context string + // Is it a wiki page? (use mode=wiki instead) + // + // Deprecated: true + // in: body + Wiki bool + // File path for detecting extension in file mode + // + // in: body + FilePath string +} + +// MarkupRender is a rendered markup document +type MarkupRender string + +// MarkdownOption markdown options +type MarkdownOption struct { + // Text markdown to render + // + // in: body + Text string + // Mode to render (markdown, comment, wiki, file) + // + // in: body + Mode string + // URL path for rendering issue, media and file links + // Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir} + // + // in: body + Context string + // Is it a wiki page? (use mode=wiki instead) + // + // Deprecated: true + // in: body + Wiki bool +} + +// MarkdownRender is a rendered markdown document +type MarkdownRender string + +// ServerVersion wraps the version of the server +type ServerVersion struct { + Version string `json:"version"` +} + +// GitignoreTemplateInfo name and text of a gitignore template +type GitignoreTemplateInfo struct { + Name string `json:"name"` + Source string `json:"source"` +} + +// LicensesListEntry is used for the API +type LicensesTemplateListEntry struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` +} + +// LicensesInfo contains information about a License +type LicenseTemplateInfo struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + Implementation string `json:"implementation"` + Body string `json:"body"` +} + +// APIError is an api error with a message +type APIError struct { + Message string `json:"message"` + URL string `json:"url"` +} diff --git a/gitea/structs/nodeinfo.go b/gitea/structs/nodeinfo.go new file mode 100644 index 0000000..802c8d3 --- /dev/null +++ b/gitea/structs/nodeinfo.go @@ -0,0 +1,43 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks +type NodeInfo struct { + Version string `json:"version"` + Software NodeInfoSoftware `json:"software"` + Protocols []string `json:"protocols"` + Services NodeInfoServices `json:"services"` + OpenRegistrations bool `json:"openRegistrations"` + Usage NodeInfoUsage `json:"usage"` + Metadata struct{} `json:"metadata"` +} + +// NodeInfoSoftware contains Metadata about server software in use +type NodeInfoSoftware struct { + Name string `json:"name"` + Version string `json:"version"` + Repository string `json:"repository"` + Homepage string `json:"homepage"` +} + +// NodeInfoServices contains the third party sites this server can connect to via their application API +type NodeInfoServices struct { + Inbound []string `json:"inbound"` + Outbound []string `json:"outbound"` +} + +// NodeInfoUsage contains usage statistics for this server +type NodeInfoUsage struct { + Users NodeInfoUsageUsers `json:"users"` + LocalPosts int `json:"localPosts,omitempty"` + LocalComments int `json:"localComments,omitempty"` +} + +// NodeInfoUsageUsers contains statistics about the users of this server +type NodeInfoUsageUsers struct { + Total int `json:"total,omitempty"` + ActiveHalfyear int `json:"activeHalfyear,omitempty"` + ActiveMonth int `json:"activeMonth,omitempty"` +} diff --git a/gitea/structs/notifications.go b/gitea/structs/notifications.go new file mode 100644 index 0000000..50cf40b --- /dev/null +++ b/gitea/structs/notifications.go @@ -0,0 +1,49 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// NotificationThread expose Notification on API +type NotificationThread struct { + ID int `json:"id"` + Repository *Repository `json:"repository"` + Subject *NotificationSubject `json:"subject"` + Unread bool `json:"unread"` + Pinned bool `json:"pinned"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` +} + +// NotificationSubject contains the notification subject (Issue/Pull/Commit) +type NotificationSubject struct { + Title string `json:"title"` + URL string `json:"url"` + LatestCommentURL string `json:"latest_comment_url"` + HTMLURL string `json:"html_url"` + LatestCommentHTMLURL string `json:"latest_comment_html_url"` + Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"` + State StateType `json:"state"` +} + +// NotificationCount number of unread notifications +type NotificationCount struct { + New int `json:"new"` +} + +// NotifySubjectType represent type of notification subject +type NotifySubjectType string + +const ( + // NotifySubjectIssue an issue is subject of an notification + NotifySubjectIssue NotifySubjectType = "Issue" + // NotifySubjectPull an pull is subject of an notification + NotifySubjectPull NotifySubjectType = "Pull" + // NotifySubjectCommit an commit is subject of an notification + NotifySubjectCommit NotifySubjectType = "Commit" + // NotifySubjectRepository an repository is subject of an notification + NotifySubjectRepository NotifySubjectType = "Repository" +) diff --git a/gitea/structs/org.go b/gitea/structs/org.go new file mode 100644 index 0000000..4bed820 --- /dev/null +++ b/gitea/structs/org.go @@ -0,0 +1,68 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Organization represents an organization +type Organization struct { + ID int `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility string `json:"visibility"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` + // deprecated + UserName string `json:"username"` +} + +// OrganizationPermissions list different users permissions on an organization +type OrganizationPermissions struct { + IsOwner bool `json:"is_owner"` + IsAdmin bool `json:"is_admin"` + CanWrite bool `json:"can_write"` + CanRead bool `json:"can_read"` + CanCreateRepository bool `json:"can_create_repository"` +} + +// CreateOrgOption options for creating an organization +type CreateOrgOption struct { + // required: true + UserName string `json:"username" binding:"Required;Username;MaxSize(40)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"MaxSize(255)"` + Description string `json:"description" binding:"MaxSize(255)"` + Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Location string `json:"location" binding:"MaxSize(50)"` + // possible values are `public` (default), `limited` or `private` + // enum: public,limited,private + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` +} + +// TODO: make EditOrgOption fields optional after https://gitea.com/go-chi/binding/pulls/5 got merged + +// EditOrgOption options for editing an organization +type EditOrgOption struct { + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"MaxSize(255)"` + Description string `json:"description" binding:"MaxSize(255)"` + Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Location string `json:"location" binding:"MaxSize(50)"` + // possible values are `public`, `limited` or `private` + // enum: public,limited,private + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` +} + +// RenameOrgOption options when renaming an organization +type RenameOrgOption struct { + // New username for this org. This name cannot be in use yet by any other user. + // + // required: true + // unique: true + NewName string `json:"new_name" binding:"Required"` +} diff --git a/gitea/structs/org_member.go b/gitea/structs/org_member.go new file mode 100644 index 0000000..2df5099 --- /dev/null +++ b/gitea/structs/org_member.go @@ -0,0 +1,9 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// AddOrgMembershipOption add user to organization options +type AddOrgMembershipOption struct { + Role string `json:"role" binding:"Required"` +} diff --git a/gitea/structs/org_team.go b/gitea/structs/org_team.go new file mode 100644 index 0000000..82b15bb --- /dev/null +++ b/gitea/structs/org_team.go @@ -0,0 +1,54 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Team represents a team in an organization +type Team struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Organization *Organization `json:"organization"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + // enum: none,read,write,admin,owner + Permission string `json:"permission"` + // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] + // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. + Units []string `json:"units"` + // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"} + UnitsMap map[string]string `json:"units_map"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` +} + +// CreateTeamOption options for creating a team +type CreateTeamOption struct { + // required: true + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(255)"` + Description string `json:"description" binding:"MaxSize(255)"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + // enum: read,write,admin + Permission string `json:"permission"` + // example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] + // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. + Units []string `json:"units"` + // example: {"repo.actions","repo.packages","repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"} + UnitsMap map[string]string `json:"units_map"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` +} + +// EditTeamOption options for editing a team +type EditTeamOption struct { + // required: true + Name string `json:"name" binding:"AlphaDashDot;MaxSize(255)"` + Description *string `json:"description" binding:"MaxSize(255)"` + IncludesAllRepositories *bool `json:"includes_all_repositories"` + // enum: read,write,admin + Permission string `json:"permission"` + // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] + // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. + Units []string `json:"units"` + // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"} + UnitsMap map[string]string `json:"units_map"` + CanCreateOrgRepo *bool `json:"can_create_org_repo"` +} diff --git a/gitea/structs/package.go b/gitea/structs/package.go new file mode 100644 index 0000000..fad202d --- /dev/null +++ b/gitea/structs/package.go @@ -0,0 +1,32 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Package represents a package +type Package struct { + ID int `json:"id"` + Owner *User `json:"owner"` + Repository *Repository `json:"repository"` + Creator *User `json:"creator"` + Type string `json:"type"` + Name string `json:"name"` + Version string `json:"version"` + HTMLURL string `json:"html_url"` + CreatedAt time.Time `json:"created_at"` +} + +// PackageFile represents a package file +type PackageFile struct { + ID int `json:"id"` + Size int + Name string `json:"name"` + HashMD5 string `json:"md5"` + HashSHA1 string `json:"sha1"` + HashSHA256 string `json:"sha256"` + HashSHA512 string `json:"sha512"` +} diff --git a/gitea/structs/pull.go b/gitea/structs/pull.go new file mode 100644 index 0000000..98f4c81 --- /dev/null +++ b/gitea/structs/pull.go @@ -0,0 +1,116 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// PullRequest represents a pull request +type PullRequest struct { + ID int `json:"id"` + URL string `json:"url"` + Index int `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + RequestedReviewers []*User `json:"requested_reviewers"` + RequestedReviewersTeams []*Team `json:"requested_reviewers_teams"` + State StateType `json:"state"` + Draft bool `json:"draft"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + + // number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR) + ReviewComments int `json:"review_comments,omitempty"` + + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + ChangedFiles *int `json:"changed_files,omitempty"` + + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + AllowMaintainerEdit bool `json:"allow_maintainer_edit"` + + Base *PRBranchInfo `json:"base"` + Head *PRBranchInfo `json:"head"` + MergeBase string `json:"merge_base"` + + Deadline *time.Time `json:"due_date"` + + Created *time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + + PinOrder int `json:"pin_order"` +} + +// PRBranchInfo information about a branch +type PRBranchInfo struct { + Name string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + RepoID int `json:"repo_id"` + Repository *Repository `json:"repo"` +} + +// ListPullRequestsOptions options for listing pull requests +type ListPullRequestsOptions struct { + Page int `json:"page"` + State string `json:"state"` +} + +// CreatePullRequestOption options when creating a pull request +type CreatePullRequestOption struct { + Head string `json:"head" binding:"Required"` + Base string `json:"base" binding:"Required"` + Title string `json:"title" binding:"Required"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` + Labels []int `json:"labels"` + Deadline *time.Time `json:"due_date"` + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` +} + +// EditPullRequestOption options when modify pull request +type EditPullRequestOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Base string `json:"base"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone int `json:"milestone"` + Labels []int `json:"labels"` + State *string `json:"state"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` + AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` +} + +// ChangedFile store information about files affected by the pull request +type ChangedFile struct { + Filename string `json:"filename"` + PreviousFilename string `json:"previous_filename,omitempty"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + HTMLURL string `json:"html_url,omitempty"` + ContentsURL string `json:"contents_url,omitempty"` + RawURL string `json:"raw_url,omitempty"` +} diff --git a/gitea/structs/pull_review.go b/gitea/structs/pull_review.go new file mode 100644 index 0000000..fe666a7 --- /dev/null +++ b/gitea/structs/pull_review.go @@ -0,0 +1,104 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// ReviewStateType review state type +type ReviewStateType string + +const ( + // ReviewStateApproved pr is approved + ReviewStateApproved ReviewStateType = "APPROVED" + // ReviewStatePending pr state is pending + ReviewStatePending ReviewStateType = "PENDING" + // ReviewStateComment is a comment review + ReviewStateComment ReviewStateType = "COMMENT" + // ReviewStateRequestChanges changes for pr are requested + ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" + // ReviewStateRequestReview review is requested from user + ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" + // ReviewStateUnknown state of pr is unknown + ReviewStateUnknown ReviewStateType = "" +) + +// PullReview represents a pull request review +type PullReview struct { + ID int `json:"id"` + Reviewer *User `json:"user"` + ReviewerTeam *Team `json:"team"` + State ReviewStateType `json:"state"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + Stale bool `json:"stale"` + Official bool `json:"official"` + Dismissed bool `json:"dismissed"` + CodeCommentsCount int `json:"comments_count"` + Submitted time.Time `json:"submitted_at"` + Updated time.Time `json:"updated_at"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// PullReviewComment represents a comment on a pull request review +type PullReviewComment struct { + ID int `json:"id"` + Body string `json:"body"` + Poster *User `json:"user"` + Resolver *User `json:"resolver"` + ReviewID int `json:"pull_request_review_id"` + + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + + Path string `json:"path"` + CommitID string `json:"commit_id"` + OrigCommitID string `json:"original_commit_id"` + DiffHunk string `json:"diff_hunk"` + LineNum uint `json:"position"` + OldLineNum uint `json:"original_position"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// CreatePullReviewOptions are options to create a pull review +type CreatePullReviewOptions struct { + Event ReviewStateType `json:"event"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + Comments []CreatePullReviewComment `json:"comments"` +} + +// CreatePullReviewComment represent a review comment for creation api +type CreatePullReviewComment struct { + // the tree path + Path string `json:"path"` + Body string `json:"body"` + // if comment to old file line or 0 + OldLineNum int `json:"old_position"` + // if comment to new file line or 0 + NewLineNum int `json:"new_position"` +} + +// SubmitPullReviewOptions are options to submit a pending pull review +type SubmitPullReviewOptions struct { + Event ReviewStateType `json:"event"` + Body string `json:"body"` +} + +// DismissPullReviewOptions are options to dismiss a pull review +type DismissPullReviewOptions struct { + Message string `json:"message"` + Priors bool `json:"priors"` +} + +// PullReviewRequestOptions are options to add or remove pull review requests +type PullReviewRequestOptions struct { + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` +} diff --git a/gitea/structs/release.go b/gitea/structs/release.go new file mode 100644 index 0000000..2cf42bb --- /dev/null +++ b/gitea/structs/release.go @@ -0,0 +1,49 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Release represents a repository release +type Release struct { + ID int `json:"id"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + TarURL string `json:"tarball_url"` + ZipURL string `json:"zipball_url"` + UploadURL string `json:"upload_url"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` + Publisher *User `json:"author"` + Attachments []*Attachment `json:"assets"` +} + +// CreateReleaseOption options when creating a release +type CreateReleaseOption struct { + // required: true + TagName string `json:"tag_name" binding:"Required"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` +} + +// EditReleaseOption options when editing a release +type EditReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft *bool `json:"draft"` + IsPrerelease *bool `json:"prerelease"` +} diff --git a/gitea/structs/repo.go b/gitea/structs/repo.go new file mode 100644 index 0000000..301435c --- /dev/null +++ b/gitea/structs/repo.go @@ -0,0 +1,410 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "strings" + "time" +) + +// Permission represents a set of permissions +type Permission struct { + Admin bool `json:"admin"` // Admin indicates if the user is an administrator of the repository. + Push bool `json:"push"` // Push indicates if the user can push code to the repository. + Pull bool `json:"pull"` // Pull indicates if the user can pull code from the repository. +} + +// InternalTracker represents settings for internal tracker +type InternalTracker struct { + // Enable time tracking (Built-in issue tracker) + EnableTimeTracker bool `json:"enable_time_tracker"` + // Let only contributors track time (Built-in issue tracker) + AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"` + // Enable dependencies for issues and pull requests (Built-in issue tracker) + EnableIssueDependencies bool `json:"enable_issue_dependencies"` +} + +// ExternalTracker represents settings for external tracker +type ExternalTracker struct { + // URL of external issue tracker. + ExternalTrackerURL string `json:"external_tracker_url"` + // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. + ExternalTrackerFormat string `json:"external_tracker_format"` + // External Issue Tracker Number Format, either `numeric`, `alphanumeric`, or `regexp` + ExternalTrackerStyle string `json:"external_tracker_style"` + // External Issue Tracker issue regular expression + ExternalTrackerRegexpPattern string `json:"external_tracker_regexp_pattern"` +} + +// ExternalWiki represents setting for external wiki +type ExternalWiki struct { + // URL of external wiki. + ExternalWikiURL string `json:"external_wiki_url"` +} + +// Repository represents a repository +type Repository struct { + ID int `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Template bool `json:"template"` + Parent *Repository `json:"parent"` + Mirror bool `json:"mirror"` + Size int `json:"size"` + Language string `json:"language"` + LanguagesURL string `json:"languages_url"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Link string `json:"link"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + OriginalURL string `json:"original_url"` + Website string `json:"website"` + Stars int `json:"stars_count"` + Forks int `json:"forks_count"` + Watchers int `json:"watchers_count"` + OpenIssues int `json:"open_issues_count"` + OpenPulls int `json:"open_pr_counter"` + Releases int `json:"release_counter"` + DefaultBranch string `json:"default_branch"` + Archived bool `json:"archived"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + ArchivedAt time.Time `json:"archived_at"` + Permissions *Permission `json:"permissions,omitempty"` + HasIssues bool `json:"has_issues"` + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + HasWiki bool `json:"has_wiki"` + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + HasPullRequests bool `json:"has_pull_requests"` + HasProjects bool `json:"has_projects"` + ProjectsMode string `json:"projects_mode"` + HasReleases bool `json:"has_releases"` + HasPackages bool `json:"has_packages"` + HasActions bool `json:"has_actions"` + IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` + AllowMerge bool `json:"allow_merge_commits"` + AllowRebase bool `json:"allow_rebase"` + AllowRebaseMerge bool `json:"allow_rebase_explicit"` + AllowSquash bool `json:"allow_squash_merge"` + AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"` + AllowRebaseUpdate bool `json:"allow_rebase_update"` + DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` + DefaultMergeStyle string `json:"default_merge_style"` + DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` + AvatarURL string `json:"avatar_url"` + Internal bool `json:"internal"` + MirrorInterval string `json:"mirror_interval"` + // ObjectFormatName of the underlying git repository + // enum: sha1,sha256 + ObjectFormatName string `json:"object_format_name"` + MirrorUpdated time.Time `json:"mirror_updated,omitempty"` + RepoTransfer *RepoTransfer `json:"repo_transfer"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` +} + +// CreateRepoOption options when creating repository +type CreateRepoOption struct { + // Name of the repository to create + // + // required: true + // unique: true + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` + // Description of the repository to create + Description string `json:"description" binding:"MaxSize(2048)"` + // Whether the repository is private + Private bool `json:"private"` + // Label-Set to use + IssueLabels string `json:"issue_labels"` + // Whether the repository should be auto-initialized? + AutoInit bool `json:"auto_init"` + // Whether the repository is template + Template bool `json:"template"` + // Gitignores to use + Gitignores string `json:"gitignores"` + // License to use + License string `json:"license"` + // Readme of the repository to create + Readme string `json:"readme"` + // DefaultBranch of the repository (used when initializes and in template) + DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"` + // TrustModel of the repository + // enum: default,collaborator,committer,collaboratorcommitter + TrustModel string `json:"trust_model"` + // ObjectFormatName of the underlying git repository + // enum: sha1,sha256 + ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"` +} + +// EditRepoOption options when editing a repository's properties +type EditRepoOption struct { + // name of the repository + // unique: true + Name *string `json:"name,omitempty" binding:"OmitEmpty;AlphaDashDot;MaxSize(100);"` + // a short description of the repository. + Description *string `json:"description,omitempty" binding:"MaxSize(2048)"` + // a URL with more information about the repository. + Website *string `json:"website,omitempty" binding:"MaxSize(1024)"` + // either `true` to make the repository private or `false` to make it public. + // Note: you will get a 422 error if the organization restricts changing repository visibility to organization + // owners and a non-owner tries to change the value of private. + Private *bool `json:"private,omitempty"` + // either `true` to make this repository a template or `false` to make it a normal repository + Template *bool `json:"template,omitempty"` + // either `true` to enable issues for this repository or `false` to disable them. + HasIssues *bool `json:"has_issues,omitempty"` + // set this structure to configure internal issue tracker + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + // set this structure to use external issue tracker + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + // either `true` to enable the wiki for this repository or `false` to disable it. + HasWiki *bool `json:"has_wiki,omitempty"` + // set this structure to use external wiki instead of internal + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + // sets the default branch for this repository. + DefaultBranch *string `json:"default_branch,omitempty"` + // either `true` to allow pull requests, or `false` to prevent pull request. + HasPullRequests *bool `json:"has_pull_requests,omitempty"` + // either `true` to enable project unit, or `false` to disable them. + HasProjects *bool `json:"has_projects,omitempty"` + // `repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both. + ProjectsMode *string `json:"projects_mode,omitempty" binding:"In(repo,owner,all)"` + // either `true` to enable releases unit, or `false` to disable them. + HasReleases *bool `json:"has_releases,omitempty"` + // either `true` to enable packages unit, or `false` to disable them. + HasPackages *bool `json:"has_packages,omitempty"` + // either `true` to enable actions unit, or `false` to disable them. + HasActions *bool `json:"has_actions,omitempty"` + // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. + IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"` + // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. + AllowMerge *bool `json:"allow_merge_commits,omitempty"` + // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. + AllowRebase *bool `json:"allow_rebase,omitempty"` + // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. + AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` + // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. + AllowSquash *bool `json:"allow_squash_merge,omitempty"` + // either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging. + AllowFastForwardOnly *bool `json:"allow_fast_forward_only_merge,omitempty"` + // either `true` to allow mark pr as merged manually, or `false` to prevent it. + AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` + // either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur. + AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"` + // either `true` to allow updating pull request branch by rebase, or `false` to prevent it. + AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"` + // set to `true` to delete pr branch after merge by default + DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` + // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only". + DefaultMergeStyle *string `json:"default_merge_style,omitempty"` + // set to `true` to allow edits from maintainers by default + DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` + // set to `true` to archive this repository. + Archived *bool `json:"archived,omitempty"` + // set to a string like `8h30m0s` to set the mirror interval time + MirrorInterval *string `json:"mirror_interval,omitempty"` + // enable prune - remove obsolete remote-tracking references when mirroring + EnablePrune *bool `json:"enable_prune,omitempty"` +} + +// GenerateRepoOption options when creating repository using a template +type GenerateRepoOption struct { + // The organization or person who will own the new repository + // + // required: true + Owner string `json:"owner"` + // Name of the repository to create + // + // required: true + // unique: true + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` + // Default branch of the new repository + DefaultBranch string `json:"default_branch"` + // Description of the repository to create + Description string `json:"description" binding:"MaxSize(2048)"` + // Whether the repository is private + Private bool `json:"private"` + // include git content of default branch in template repo + GitContent bool `json:"git_content"` + // include topics in template repo + Topics bool `json:"topics"` + // include git hooks in template repo + GitHooks bool `json:"git_hooks"` + // include webhooks in template repo + Webhooks bool `json:"webhooks"` + // include avatar of the template repo + Avatar bool `json:"avatar"` + // include labels in template repo + Labels bool `json:"labels"` + // include protected branches in template repo + ProtectedBranch bool `json:"protected_branch"` +} + +// CreateBranchRepoOption options when creating a branch in a repository +type CreateBranchRepoOption struct { + // Name of the branch to create + // + // required: true + // unique: true + BranchName string `json:"new_branch_name" binding:"Required;GitRefName;MaxSize(100)"` + + // Deprecated: true + // Name of the old branch to create from + // + // unique: true + OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"` + + // Name of the old branch/tag/commit to create from + // + // unique: true + OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"` +} + +// UpdateBranchRepoOption options when updating a branch in a repository +type UpdateBranchRepoOption struct { + // New branch name + // + // required: true + // unique: true + Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"` +} + +// TransferRepoOption options when transfer a repository's ownership +type TransferRepoOption struct { + // required: true + NewOwner string `json:"new_owner"` + // ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories. + TeamIDs *[]int `json:"team_ids"` +} + +// GitServiceType represents a git service +type GitServiceType int + +// enumerate all GitServiceType +const ( + NotMigrated GitServiceType = iota // 0 not migrated from external sites + PlainGitService // 1 plain git service + GithubService // 2 github.com + GiteaService // 3 gitea service + GitlabService // 4 gitlab service + GogsService // 5 gogs service + OneDevService // 6 onedev service + GitBucketService // 7 gitbucket service + CodebaseService // 8 codebase service + CodeCommitService // 9 codecommit service +) + +// Name represents the service type's name +// WARNNING: the name have to be equal to that on goth's library +func (gt GitServiceType) Name() string { + return strings.ToLower(gt.Title()) +} + +// Title represents the service type's proper title +func (gt GitServiceType) Title() string { + switch gt { + case GithubService: + return "GitHub" + case GiteaService: + return "Gitea" + case GitlabService: + return "GitLab" + case GogsService: + return "Gogs" + case OneDevService: + return "OneDev" + case GitBucketService: + return "GitBucket" + case CodebaseService: + return "Codebase" + case CodeCommitService: + return "CodeCommit" + case PlainGitService: + return "Git" + } + return "" +} + +// MigrateRepoOptions options for migrating repository's +// this is used to interact with api v1 +type MigrateRepoOptions struct { + // required: true + CloneAddr string `json:"clone_addr" binding:"Required"` + // deprecated (only for backwards compatibility) + RepoOwnerID int `json:"uid"` + // Name of User or Organisation who will own Repo after migration + RepoOwner string `json:"repo_owner"` + // required: true + RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + + // enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase + Service string `json:"service"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` + + Mirror bool `json:"mirror"` + LFS bool `json:"lfs"` + LFSEndpoint string `json:"lfs_endpoint"` + Private bool `json:"private"` + Description string `json:"description" binding:"MaxSize(2048)"` + Wiki bool `json:"wiki"` + Milestones bool `json:"milestones"` + Labels bool `json:"labels"` + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` + MirrorInterval string `json:"mirror_interval"` + + AWSAccessKeyID string `json:"aws_access_key_id"` + AWSSecretAccessKey string `json:"aws_secret_access_key"` +} + +// TokenAuth represents whether a service type supports token-based auth +func (gt GitServiceType) TokenAuth() bool { + switch gt { + case GithubService, GiteaService, GitlabService: + return true + } + return false +} + +// SupportedFullGitService represents all git services supported to migrate issues/labels/prs and etc. +// TODO: add to this list after new git service added +var SupportedFullGitService = []GitServiceType{ + GithubService, + GitlabService, + GiteaService, + GogsService, + OneDevService, + GitBucketService, + CodebaseService, + CodeCommitService, +} + +// RepoTransfer represents a pending repo transfer +type RepoTransfer struct { + Doer *User `json:"doer"` + Recipient *User `json:"recipient"` + Teams []*Team `json:"teams"` +} + +// NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed +type NewIssuePinsAllowed struct { + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` +} + +// UpdateRepoAvatarUserOption options when updating the repo avatar +type UpdateRepoAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} diff --git a/gitea/structs/repo_actions.go b/gitea/structs/repo_actions.go new file mode 100644 index 0000000..b30ed6f --- /dev/null +++ b/gitea/structs/repo_actions.go @@ -0,0 +1,120 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// ActionTask represents a ActionTask +type ActionTask struct { + ID int `json:"id"` + Name string `json:"name"` + HeadBranch string `json:"head_branch"` + HeadSHA string `json:"head_sha"` + RunNumber int `json:"run_number"` + Event string `json:"event"` + DisplayTitle string `json:"display_title"` + Status string `json:"status"` + WorkflowID string `json:"workflow_id"` + URL string `json:"url"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RunStartedAt time.Time `json:"run_started_at"` +} + +// ActionTaskResponse returns a ActionTask +type ActionTaskResponse struct { + Entries []*ActionTask `json:"workflow_runs"` + TotalCount int `json:"total_count"` +} + +// CreateActionWorkflowDispatch represents the payload for triggering a workflow dispatch event +type CreateActionWorkflowDispatch struct { + // required: true + // example: refs/heads/main + Ref string `json:"ref" binding:"Required"` + // required: false + Inputs map[string]string `json:"inputs,omitempty"` +} + +// ActionWorkflow represents a ActionWorkflow +type ActionWorkflow struct { + ID string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + State string `json:"state"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + BadgeURL string `json:"badge_url"` + DeletedAt time.Time `json:"deleted_at,omitempty"` +} + +// ActionWorkflowResponse returns a ActionWorkflow +type ActionWorkflowResponse struct { + Workflows []*ActionWorkflow `json:"workflows"` + TotalCount int `json:"total_count"` +} + +// ActionArtifact represents a ActionArtifact +type ActionArtifact struct { + ID int `json:"id"` + Name string `json:"name"` + SizeInBytes int `json:"size_in_bytes"` + URL string `json:"url"` + ArchiveDownloadURL string `json:"archive_download_url"` + Expired bool `json:"expired"` + WorkflowRun *ActionWorkflowRun `json:"workflow_run"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ExpiresAt time.Time `json:"expires_at"` +} + +// ActionWorkflowRun represents a WorkflowRun +type ActionWorkflowRun struct { + ID int `json:"id"` + RepositoryID int `json:"repository_id"` + HeadSha string `json:"head_sha"` +} + +// ActionArtifactsResponse returns ActionArtifacts +type ActionArtifactsResponse struct { + Entries []*ActionArtifact `json:"artifacts"` + TotalCount int `json:"total_count"` +} + +// ActionWorkflowStep represents a step of a WorkflowJob +type ActionWorkflowStep struct { + Name string `json:"name"` + Number int `json:"number"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + StartedAt time.Time `json:"started_at,omitempty"` + CompletedAt time.Time `json:"completed_at,omitempty"` +} + +// ActionWorkflowJob represents a WorkflowJob +type ActionWorkflowJob struct { + ID int `json:"id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + RunID int `json:"run_id"` + RunURL string `json:"run_url"` + Name string `json:"name"` + Labels []string `json:"labels"` + RunAttempt int `json:"run_attempt"` + HeadSha string `json:"head_sha"` + HeadBranch string `json:"head_branch,omitempty"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + RunnerID int `json:"runner_id,omitempty"` + RunnerName string `json:"runner_name,omitempty"` + Steps []*ActionWorkflowStep `json:"steps"` + CreatedAt time.Time `json:"created_at"` + StartedAt time.Time `json:"started_at,omitempty"` + CompletedAt time.Time `json:"completed_at,omitempty"` +} diff --git a/gitea/structs/repo_branch.go b/gitea/structs/repo_branch.go new file mode 100644 index 0000000..5321aaf --- /dev/null +++ b/gitea/structs/repo_branch.go @@ -0,0 +1,141 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Branch represents a repository branch +type Branch struct { + Name string `json:"name"` + Commit *PayloadCommit `json:"commit"` + Protected bool `json:"protected"` + RequiredApprovals int `json:"required_approvals"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + UserCanPush bool `json:"user_can_push"` + UserCanMerge bool `json:"user_can_merge"` + EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` +} + +// BranchProtection represents a branch protection for a repository +type BranchProtection struct { + // Deprecated: true + BranchName string `json:"branch_name"` + RuleName string `json:"rule_name"` + Priority int `json:"priority"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableForcePush bool `json:"enable_force_push"` + EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistDeployKeys bool `json:"force_push_allowlist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + IgnoreStaleApprovals bool `json:"ignore_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride bool `json:"block_admin_merge_override"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateBranchProtectionOption options for creating a branch protection +type CreateBranchProtectionOption struct { + // Deprecated: true + BranchName string `json:"branch_name"` + RuleName string `json:"rule_name"` + Priority int `json:"priority"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableForcePush bool `json:"enable_force_push"` + EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistDeployKeys bool `json:"force_push_allowlist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + IgnoreStaleApprovals bool `json:"ignore_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride bool `json:"block_admin_merge_override"` +} + +// EditBranchProtectionOption options for editing a branch protection +type EditBranchProtectionOption struct { + Priority *int `json:"priority"` + EnablePush *bool `json:"enable_push"` + EnablePushWhitelist *bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` + EnableForcePush *bool `json:"enable_force_push"` + EnableForcePushAllowlist *bool `json:"enable_force_push_allowlist"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistDeployKeys *bool `json:"force_push_allowlist_deploy_keys"` + EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck *bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals *int `json:"required_approvals"` + EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` + DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` + IgnoreStaleApprovals *bool `json:"ignore_stale_approvals"` + RequireSignedCommits *bool `json:"require_signed_commits"` + ProtectedFilePatterns *string `json:"protected_file_patterns"` + UnprotectedFilePatterns *string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride *bool `json:"block_admin_merge_override"` +} + +// UpdateBranchProtectionPriories a list to update the branch protection rule priorities +type UpdateBranchProtectionPriories struct { + IDs []int `json:"ids"` +} + +type MergeUpstreamRequest struct { + Branch string `json:"branch"` +} + +type MergeUpstreamResponse struct { + MergeStyle string `json:"merge_type"` +} diff --git a/gitea/structs/repo_collaborator.go b/gitea/structs/repo_collaborator.go new file mode 100644 index 0000000..7d39b5a --- /dev/null +++ b/gitea/structs/repo_collaborator.go @@ -0,0 +1,17 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// AddCollaboratorOption options when adding a user as a collaborator of a repository +type AddCollaboratorOption struct { + // enum: read,write,admin + Permission *string `json:"permission"` +} + +// RepoCollaboratorPermission to get repository permission for a collaborator +type RepoCollaboratorPermission struct { + Permission string `json:"permission"` + RoleName string `json:"role_name"` + User *User `json:"user"` +} diff --git a/gitea/structs/repo_commit.go b/gitea/structs/repo_commit.go new file mode 100644 index 0000000..a1de4ef --- /dev/null +++ b/gitea/structs/repo_commit.go @@ -0,0 +1,69 @@ +// Copyright 2018 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// Identity for a person's identity like an author or committer +type Identity struct { + Name string `json:"name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"MaxSize(254)"` +} + +// CommitMeta contains meta information of a commit in terms of API. +type CommitMeta struct { + URL string `json:"url"` + SHA string `json:"sha"` + Created time.Time `json:"created"` +} + +// CommitUser contains information of a user in the context of a commit. +type CommitUser struct { + Identity + Date string `json:"date"` +} + +// RepoCommit contains information of a commit in the context of a repository. +type RepoCommit struct { + URL string `json:"url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// CommitStats is statistics for a RepoCommit +type CommitStats struct { + Total int `json:"total"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` +} + +// Commit contains information generated from a Git commit. +type Commit struct { + *CommitMeta + HTMLURL string `json:"html_url"` + RepoCommit *RepoCommit `json:"commit"` + Author *User `json:"author"` + Committer *User `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Files []*CommitAffectedFiles `json:"files"` + Stats *CommitStats `json:"stats"` +} + +// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE +type CommitDateOptions struct { + Author time.Time `json:"author"` + Committer time.Time `json:"committer"` +} + +// CommitAffectedFiles store information about files affected by the commit +type CommitAffectedFiles struct { + Filename string `json:"filename"` + Status string `json:"status"` +} diff --git a/gitea/structs/repo_compare.go b/gitea/structs/repo_compare.go new file mode 100644 index 0000000..8a12498 --- /dev/null +++ b/gitea/structs/repo_compare.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Compare represents a comparison between two commits. +type Compare struct { + TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. + Commits []*Commit `json:"commits"` // List of commits in the comparison. +} diff --git a/gitea/structs/repo_file.go b/gitea/structs/repo_file.go new file mode 100644 index 0000000..496e63d --- /dev/null +++ b/gitea/structs/repo_file.go @@ -0,0 +1,172 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// FileOptions options for all file APIs +type FileOptions struct { + // message (optional) for the commit of this file. if not supplied, a default message will be used + Message string `json:"message"` + // branch (optional) to base this file from. if not given, the default branch is used + BranchName string `json:"branch" binding:"GitRefName;MaxSize(100)"` + // new_branch (optional) will make a new branch from `branch` before creating the file + NewBranchName string `json:"new_branch" binding:"GitRefName;MaxSize(100)"` + // `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) + Author Identity `json:"author"` + Committer Identity `json:"committer"` + Dates CommitDateOptions `json:"dates"` + // Add a Signed-off-by trailer by the committer at the end of the commit log message. + Signoff bool `json:"signoff"` +} + +// CreateFileOptions options for creating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type CreateFileOptions struct { + FileOptions + // content must be base64 encoded + // required: true + ContentBase64 string `json:"content"` +} + +// Branch returns branch name +func (o *CreateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + +// DeleteFileOptions options for deleting files (used for other File structs below) +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type DeleteFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required: true + SHA string `json:"sha" binding:"Required"` +} + +// Branch returns branch name +func (o *DeleteFileOptions) Branch() string { + return o.FileOptions.BranchName +} + +// UpdateFileOptions options for updating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type UpdateFileOptions struct { + DeleteFileOptions + // content must be base64 encoded + // required: true + ContentBase64 string `json:"content"` + // from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL + FromPath string `json:"from_path" binding:"MaxSize(500)"` +} + +// Branch returns branch name +func (o *UpdateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + +// ChangeFileOperation for creating, updating or deleting a file +type ChangeFileOperation struct { + // indicates what to do with the file + // required: true + // enum: create,update,delete + Operation string `json:"operation" binding:"Required"` + // path to the existing or new file + // required: true + Path string `json:"path" binding:"Required;MaxSize(500)"` + // new or updated file content, must be base64 encoded + ContentBase64 string `json:"content"` + // sha is the SHA for the file that already exists, required for update or delete + SHA string `json:"sha"` + // old path of the file to move + FromPath string `json:"from_path"` +} + +// ChangeFilesOptions options for creating, updating or deleting multiple files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type ChangeFilesOptions struct { + FileOptions + // list of file operations + // required: true + Files []*ChangeFileOperation `json:"files" binding:"Required"` +} + +// Branch returns branch name +func (o *ChangeFilesOptions) Branch() string { + return o.FileOptions.BranchName +} + +// FileOptionInterface provides a unified interface for the different file options +type FileOptionInterface interface { + Branch() string +} + +// ApplyDiffPatchFileOptions options for applying a diff patch +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type ApplyDiffPatchFileOptions struct { + DeleteFileOptions + // required: true + Content string `json:"content"` +} + +// FileLinksResponse contains the links for a repo's file +type FileLinksResponse struct { + Self *string `json:"self"` + GitURL *string `json:"git"` + HTMLURL *string `json:"html"` +} + +// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content +type ContentsResponse struct { + Name string `json:"name"` + Path string `json:"path"` + SHA string `json:"sha"` + LastCommitSHA string `json:"last_commit_sha"` + // `type` will be `file`, `dir`, `symlink`, or `submodule` + Type string `json:"type"` + Size int `json:"size"` + // `encoding` is populated when `type` is `file`, otherwise null + Encoding *string `json:"encoding"` + // `content` is populated when `type` is `file`, otherwise null + Content *string `json:"content"` + // `target` is populated when `type` is `symlink`, otherwise null + Target *string `json:"target"` + URL *string `json:"url"` + HTMLURL *string `json:"html_url"` + GitURL *string `json:"git_url"` + DownloadURL *string `json:"download_url"` + // `submodule_git_url` is populated when `type` is `submodule`, otherwise null + SubmoduleGitURL *string `json:"submodule_git_url"` + Links *FileLinksResponse `json:"_links"` +} + +// FileCommitResponse contains information generated from a Git commit for a repo's file. +type FileCommitResponse struct { + CommitMeta + HTMLURL string `json:"html_url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` +} + +// FileResponse contains information about a repo's file +type FileResponse struct { + Content *ContentsResponse `json:"content"` + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// FilesResponse contains information about multiple files from a repo +type FilesResponse struct { + Files []*ContentsResponse `json:"files"` + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// FileDeleteResponse contains information about a repo's file that was deleted +type FileDeleteResponse struct { + Content any `json:"content"` // to be set to nil + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} diff --git a/gitea/structs/repo_key.go b/gitea/structs/repo_key.go new file mode 100644 index 0000000..2aab788 --- /dev/null +++ b/gitea/structs/repo_key.go @@ -0,0 +1,39 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// DeployKey a deploy key +type DeployKey struct { + ID int `json:"id"` + KeyID int `json:"key_id"` + Key string `json:"key"` + URL string `json:"url"` + Title string `json:"title"` + Fingerprint string `json:"fingerprint"` + Created time.Time `json:"created_at"` + ReadOnly bool `json:"read_only"` + Repository *Repository `json:"repository,omitempty"` +} + +// CreateKeyOption options when creating a key +type CreateKeyOption struct { + // Title of the key to add + // + // required: true + // unique: true + Title string `json:"title" binding:"Required"` + // An armored SSH key to add + // + // required: true + // unique: true + Key string `json:"key" binding:"Required"` + // Describe if the key has only read access or read/write + // + // required: false + ReadOnly bool `json:"read_only"` +} diff --git a/gitea/structs/repo_note.go b/gitea/structs/repo_note.go new file mode 100644 index 0000000..4eaf5a2 --- /dev/null +++ b/gitea/structs/repo_note.go @@ -0,0 +1,10 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Note contains information related to a git note +type Note struct { + Message string `json:"message"` + Commit *Commit `json:"commit"` +} diff --git a/gitea/structs/repo_refs.go b/gitea/structs/repo_refs.go new file mode 100644 index 0000000..6ffbc74 --- /dev/null +++ b/gitea/structs/repo_refs.go @@ -0,0 +1,18 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Reference represents a Git reference. +type Reference struct { + Ref string `json:"ref"` + URL string `json:"url"` + Object *GitObject `json:"object"` +} + +// GitObject represents a Git object. +type GitObject struct { + Type string `json:"type"` + SHA string `json:"sha"` + URL string `json:"url"` +} diff --git a/gitea/structs/repo_tag.go b/gitea/structs/repo_tag.go new file mode 100644 index 0000000..e0edba6 --- /dev/null +++ b/gitea/structs/repo_tag.go @@ -0,0 +1,66 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +// Tag represents a repository tag +type Tag struct { + Name string `json:"name"` + Message string `json:"message"` + ID string `json:"id"` + Commit *CommitMeta `json:"commit"` + ZipballURL string `json:"zipball_url"` + TarballURL string `json:"tarball_url"` +} + +// AnnotatedTag represents an annotated tag +type AnnotatedTag struct { + Tag string `json:"tag"` + SHA string `json:"sha"` + URL string `json:"url"` + Message string `json:"message"` + Tagger *CommitUser `json:"tagger"` + Object *AnnotatedTagObject `json:"object"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// AnnotatedTagObject contains meta information of the tag object +type AnnotatedTagObject struct { + Type string `json:"type"` + URL string `json:"url"` + SHA string `json:"sha"` +} + +// CreateTagOption options when creating a tag +type CreateTagOption struct { + // required: true + TagName string `json:"tag_name" binding:"Required"` + Message string `json:"message"` + Target string `json:"target"` +} + +// TagProtection represents a tag protection +type TagProtection struct { + ID int `json:"id"` + NamePattern string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateTagProtectionOption options for creating a tag protection +type CreateTagProtectionOption struct { + NamePattern string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` +} + +// EditTagProtectionOption options for editing a tag protection +type EditTagProtectionOption struct { + NamePattern *string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` +} diff --git a/gitea/structs/repo_topic.go b/gitea/structs/repo_topic.go new file mode 100644 index 0000000..8310a1b --- /dev/null +++ b/gitea/structs/repo_topic.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// TopicResponse for returning topics +type TopicResponse struct { + ID int `json:"id"` + Name string `json:"topic_name"` + RepoCount int `json:"repo_count"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +// TopicName a list of repo topic names +type TopicName struct { + TopicNames []string `json:"topics"` +} + +// RepoTopicOptions a collection of repo topic names +type RepoTopicOptions struct { + // list of topic names + Topics []string `json:"topics"` +} diff --git a/gitea/structs/repo_tree.go b/gitea/structs/repo_tree.go new file mode 100644 index 0000000..964348a --- /dev/null +++ b/gitea/structs/repo_tree.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// GitEntry represents a git tree +type GitEntry struct { + Path string `json:"path"` + Mode string `json:"mode"` + Type string `json:"type"` + Size int `json:"size"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// GitTreeResponse returns a git tree +type GitTreeResponse struct { + SHA string `json:"sha"` + URL string `json:"url"` + Entries []GitEntry `json:"tree"` + Truncated bool `json:"truncated"` + Page int `json:"page"` + TotalCount int `json:"total_count"` +} diff --git a/gitea/structs/repo_watch.go b/gitea/structs/repo_watch.go new file mode 100644 index 0000000..0d0b7c4 --- /dev/null +++ b/gitea/structs/repo_watch.go @@ -0,0 +1,18 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// WatchInfo represents an API watch status of one repository +type WatchInfo struct { + Subscribed bool `json:"subscribed"` + Ignored bool `json:"ignored"` + Reason any `json:"reason"` + CreatedAt time.Time `json:"created_at"` + URL string `json:"url"` + RepositoryURL string `json:"repository_url"` +} diff --git a/gitea/structs/repo_wiki.go b/gitea/structs/repo_wiki.go new file mode 100644 index 0000000..61f59ab --- /dev/null +++ b/gitea/structs/repo_wiki.go @@ -0,0 +1,46 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// WikiCommit page commit/revision +type WikiCommit struct { + ID string `json:"sha"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"commiter"` + Message string `json:"message"` +} + +// WikiPage a wiki page +type WikiPage struct { + *WikiPageMetaData + // Page content, base64 encoded + ContentBase64 string `json:"content_base64"` + CommitCount int `json:"commit_count"` + Sidebar string `json:"sidebar"` + Footer string `json:"footer"` +} + +// WikiPageMetaData wiki page meta information +type WikiPageMetaData struct { + Title string `json:"title"` + HTMLURL string `json:"html_url"` + SubURL string `json:"sub_url"` + LastCommit *WikiCommit `json:"last_commit"` +} + +// CreateWikiPageOptions form for creating wiki +type CreateWikiPageOptions struct { + // page title. leave empty to keep unchanged + Title string `json:"title"` + // content must be base64 encoded + ContentBase64 string `json:"content_base64"` + // optional commit message summarizing the change + Message string `json:"message"` +} + +// WikiCommitList commit/revision list +type WikiCommitList struct { + WikiCommits []*WikiCommit `json:"commits"` + Count int `json:"count"` +} diff --git a/gitea/structs/secret.go b/gitea/structs/secret.go new file mode 100644 index 0000000..a009916 --- /dev/null +++ b/gitea/structs/secret.go @@ -0,0 +1,28 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import "time" + +// Secret represents a secret +type Secret struct { + // the secret's name + Name string `json:"name"` + // the secret's description + Description string `json:"description"` + Created time.Time `json:"created_at"` +} + +// CreateOrUpdateSecretOption options when creating or updating secret +type CreateOrUpdateSecretOption struct { + // Data of the secret to update + // + // required: true + Data string `json:"data" binding:"Required"` + + // Description of the secret to update + // + // required: false + Description string `json:"description"` +} diff --git a/gitea/structs/settings.go b/gitea/structs/settings.go new file mode 100644 index 0000000..37508a4 --- /dev/null +++ b/gitea/structs/settings.go @@ -0,0 +1,37 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// GeneralRepoSettings contains global repository settings exposed by API +type GeneralRepoSettings struct { + MirrorsDisabled bool `json:"mirrors_disabled"` + HTTPGitDisabled bool `json:"http_git_disabled"` + MigrationsDisabled bool `json:"migrations_disabled"` + StarsDisabled bool `json:"stars_disabled"` + TimeTrackingDisabled bool `json:"time_tracking_disabled"` + LFSDisabled bool `json:"lfs_disabled"` +} + +// GeneralUISettings contains global ui settings exposed by API +type GeneralUISettings struct { + DefaultTheme string `json:"default_theme"` + AllowedReactions []string `json:"allowed_reactions"` + CustomEmojis []string `json:"custom_emojis"` +} + +// GeneralAPISettings contains global api settings exposed by it +type GeneralAPISettings struct { + MaxResponseItems int `json:"max_response_items"` + DefaultPagingNum int `json:"default_paging_num"` + DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` + DefaultMaxBlobSize int `json:"default_max_blob_size"` +} + +// GeneralAttachmentSettings contains global Attachment settings exposed by API +type GeneralAttachmentSettings struct { + Enabled bool `json:"enabled"` + AllowedTypes string `json:"allowed_types"` + MaxSize int `json:"max_size"` + MaxFiles int `json:"max_files"` +} diff --git a/gitea/structs/status.go b/gitea/structs/status.go new file mode 100644 index 0000000..d032bce --- /dev/null +++ b/gitea/structs/status.go @@ -0,0 +1,40 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// CommitStatus holds a single status of a single Commit +type CommitStatus struct { + ID int `json:"id"` + State CommitStatusState `json:"status"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + URL string `json:"url"` + Context string `json:"context"` + Creator *User `json:"creator"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CombinedStatus holds the combined state of several statuses for a single commit +type CombinedStatus struct { + State CommitStatusState `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []*CommitStatus `json:"statuses"` + Repository *Repository `json:"repository"` + CommitURL string `json:"commit_url"` + URL string `json:"url"` +} + +// CreateStatusOption holds the information needed to create a new CommitStatus for a Commit +type CreateStatusOption struct { + State CommitStatusState `json:"state"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + Context string `json:"context"` +} diff --git a/gitea/structs/task.go b/gitea/structs/task.go new file mode 100644 index 0000000..ed11a33 --- /dev/null +++ b/gitea/structs/task.go @@ -0,0 +1,30 @@ +// Copyright 2019 Gitea. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// TaskType defines task type +type TaskType int + +const TaskTypeMigrateRepo TaskType = iota // migrate repository from external or local disk + +// Name returns the task type name +func (taskType TaskType) Name() string { + switch taskType { + case TaskTypeMigrateRepo: + return "Migrate Repository" + } + return "" +} + +// TaskStatus defines task status +type TaskStatus int + +// enumerate all the kinds of task status +const ( + TaskStatusQueued TaskStatus = iota // 0 task is queued + TaskStatusRunning // 1 task is running + TaskStatusStopped // 2 task is stopped (never used) + TaskStatusFailed // 3 task is failed + TaskStatusFinished // 4 task is finished +) diff --git a/gitea/structs/user.go b/gitea/structs/user.go new file mode 100644 index 0000000..d404e0c --- /dev/null +++ b/gitea/structs/user.go @@ -0,0 +1,130 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" + + "code.gitea.io/gitea/modules/json" +) + +// User represents a user +type User struct { + // the user's id + ID int `json:"id"` + // the user's username + UserName string `json:"login"` + // the user's authentication sign-in name. + // default: empty + LoginName string `json:"login_name"` + // The ID of the user's Authentication Source + SourceID int `json:"source_id"` + // the user's full name + FullName string `json:"full_name"` + Email string `json:"email"` + // URL to the user's avatar + AvatarURL string `json:"avatar_url"` + // URL to the user's gitea page + HTMLURL string `json:"html_url"` + // User locale + Language string `json:"language"` + // Is the user an administrator + IsAdmin bool `json:"is_admin"` + LastLogin time.Time `json:"last_login,omitempty"` + Created time.Time `json:"created,omitempty"` + // Is user restricted + Restricted bool `json:"restricted"` + // Is user active + IsActive bool `json:"active"` + // Is user login prohibited + ProhibitLogin bool `json:"prohibit_login"` + // the user's location + Location string `json:"location"` + // the user's website + Website string `json:"website"` + // the user's description + Description string `json:"description"` + // User visibility level option: public, limited, private + Visibility string `json:"visibility"` + + // user counts + Followers int `json:"followers_count"` + Following int `json:"following_count"` + StarredRepos int `json:"starred_repos_count"` +} + +// MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility +func (u User) MarshalJSON() ([]byte, error) { + // Re-declaring User to avoid recursion + type shadow User + return json.Marshal(struct { + shadow + CompatUserName string `json:"username"` + }{shadow(u), u.UserName}) +} + +// UserSettings represents user settings +type UserSettings struct { + FullName string `json:"full_name"` + Website string `json:"website"` + Description string `json:"description"` + Location string `json:"location"` + Language string `json:"language"` + Theme string `json:"theme"` + DiffViewStyle string `json:"diff_view_style"` + // Privacy + HideEmail bool `json:"hide_email"` + HideActivity bool `json:"hide_activity"` +} + +// UserSettingsOptions represents options to change user settings +type UserSettingsOptions struct { + FullName *string `json:"full_name" binding:"MaxSize(100)"` + Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"` + Description *string `json:"description" binding:"MaxSize(255)"` + Location *string `json:"location" binding:"MaxSize(50)"` + Language *string `json:"language"` + Theme *string `json:"theme"` + DiffViewStyle *string `json:"diff_view_style"` + // Privacy + HideEmail *bool `json:"hide_email"` + HideActivity *bool `json:"hide_activity"` +} + +// RenameUserOption options when renaming a user +type RenameUserOption struct { + // New username for this user. This name cannot be in use yet by any other user. + // + // required: true + // unique: true + NewName string `json:"new_username" binding:"Required"` +} + +// UpdateUserAvatarUserOption options when updating the user avatar +type UpdateUserAvatarOption struct { + // image must be base64 encoded + Image string `json:"image" binding:"Required"` +} + +// Badge represents a user badge +type Badge struct { + ID int `json:"id"` + Slug string `json:"slug"` + Description string `json:"description"` + ImageURL string `json:"image_url"` +} + +// UserBadge represents a user badge +type UserBadge struct { + ID int `json:"id"` + BadgeID int `json:"badge_id"` + UserID int `json:"user_id"` +} + +// UserBadgeOption options for link between users and badges +type UserBadgeOption struct { + // example: ["badge1","badge2"] + BadgeSlugs []string `json:"badge_slugs" binding:"Required"` +} diff --git a/gitea/structs/user_app.go b/gitea/structs/user_app.go new file mode 100644 index 0000000..fceb647 --- /dev/null +++ b/gitea/structs/user_app.go @@ -0,0 +1,51 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// AccessToken represents an API access token. +type AccessToken struct { + ID int `json:"id"` + Name string `json:"name"` + Token string `json:"sha1"` + TokenLastEight string `json:"token_last_eight"` + Scopes []string `json:"scopes"` +} + +// AccessTokenList represents a list of API access token. +type AccessTokenList []*AccessToken + +// CreateAccessTokenOption options when create access token +type CreateAccessTokenOption struct { + // required: true + Name string `json:"name" binding:"Required"` + Scopes []string `json:"scopes"` +} + +// CreateOAuth2ApplicationOptions holds options to create an oauth2 application +type CreateOAuth2ApplicationOptions struct { + Name string `json:"name" binding:"Required"` + ConfidentialClient bool `json:"confidential_client"` + SkipSecondaryAuthorization bool `json:"skip_secondary_authorization"` + RedirectURIs []string `json:"redirect_uris" binding:"Required"` +} + +// OAuth2Application represents an OAuth2 application. +type OAuth2Application struct { + ID int `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + ConfidentialClient bool `json:"confidential_client"` + SkipSecondaryAuthorization bool `json:"skip_secondary_authorization"` + RedirectURIs []string `json:"redirect_uris"` + Created time.Time `json:"created"` +} + +// OAuth2ApplicationList represents a list of OAuth2 applications. +type OAuth2ApplicationList []*OAuth2Application diff --git a/gitea/structs/user_email.go b/gitea/structs/user_email.go new file mode 100644 index 0000000..1cc4f89 --- /dev/null +++ b/gitea/structs/user_email.go @@ -0,0 +1,26 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Email an email address belonging to a user +type Email struct { + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` + UserID int `json:"user_id"` + UserName string `json:"username"` +} + +// CreateEmailOption options when creating email addresses +type CreateEmailOption struct { + // email addresses to add + Emails []string `json:"emails"` +} + +// DeleteEmailOption options when deleting email addresses +type DeleteEmailOption struct { + // email addresses to delete + Emails []string `json:"emails"` +} diff --git a/gitea/structs/user_gpgkey.go b/gitea/structs/user_gpgkey.go new file mode 100644 index 0000000..860b539 --- /dev/null +++ b/gitea/structs/user_gpgkey.go @@ -0,0 +1,50 @@ +// Copyright 2017 Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// GPGKey a user GPG key to sign commit and tag in repository +type GPGKey struct { + ID int `json:"id"` + PrimaryKeyID string `json:"primary_key_id"` + KeyID string `json:"key_id"` + PublicKey string `json:"public_key"` + Emails []*GPGKeyEmail `json:"emails"` + SubsKey []*GPGKey `json:"subkeys"` + CanSign bool `json:"can_sign"` + CanEncryptComms bool `json:"can_encrypt_comms"` + CanEncryptStorage bool `json:"can_encrypt_storage"` + CanCertify bool `json:"can_certify"` + Verified bool `json:"verified"` + Created time.Time `json:"created_at,omitempty"` + Expires time.Time `json:"expires_at,omitempty"` +} + +// GPGKeyEmail an email attached to a GPGKey +type GPGKeyEmail struct { + Email string `json:"email"` + Verified bool `json:"verified"` +} + +// CreateGPGKeyOption options create user GPG key +type CreateGPGKeyOption struct { + // An armored GPG key to add + // + // required: true + // unique: true + ArmoredKey string `json:"armored_public_key" binding:"Required"` + Signature string `json:"armored_signature,omitempty"` +} + +// VerifyGPGKeyOption options verifies user GPG key +type VerifyGPGKeyOption struct { + // An Signature for a GPG key token + // + // required: true + KeyID string `json:"key_id" binding:"Required"` + Signature string `json:"armored_signature" binding:"Required"` +} diff --git a/gitea/structs/user_key.go b/gitea/structs/user_key.go new file mode 100644 index 0000000..a525afb --- /dev/null +++ b/gitea/structs/user_key.go @@ -0,0 +1,21 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// PublicKey publickey is a user key to push code to repository +type PublicKey struct { + ID int `json:"id"` + Key string `json:"key"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Created time.Time `json:"created_at,omitempty"` + Owner *User `json:"user,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + KeyType string `json:"key_type,omitempty"` +} diff --git a/gitea/structs/variable.go b/gitea/structs/variable.go new file mode 100644 index 0000000..f2c3987 --- /dev/null +++ b/gitea/structs/variable.go @@ -0,0 +1,46 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// CreateVariableOption the option when creating variable +type CreateVariableOption struct { + // Value of the variable to create + // + // required: true + Value string `json:"value" binding:"Required"` + + // Description of the variable to create + // + // required: false + Description string `json:"description"` +} + +// UpdateVariableOption the option when updating variable +type UpdateVariableOption struct { + // New name for the variable. If the field is empty, the variable name won't be updated. + Name string `json:"name"` + // Value of the variable to update + // + // required: true + Value string `json:"value" binding:"Required"` + + // Description of the variable to update + // + // required: false + Description string `json:"description"` +} + +// ActionVariable return value of the query API +type ActionVariable struct { + // the owner to which the variable belongs + OwnerID int `json:"owner_id"` + // the repository to which the variable belongs + RepoID int `json:"repo_id"` + // the name of the variable + Name string `json:"name"` + // the value of the variable + Data string `json:"data"` + // the description of the variable + Description string `json:"description"` +} diff --git a/gitea/structs/visible_type.go b/gitea/structs/visible_type.go new file mode 100644 index 0000000..b5ff353 --- /dev/null +++ b/gitea/structs/visible_type.go @@ -0,0 +1,58 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// VisibleType defines the visibility of user and org +type VisibleType int + +const ( + // VisibleTypePublic Visible for everyone + VisibleTypePublic VisibleType = iota + + // VisibleTypeLimited Visible for every connected user + VisibleTypeLimited + + // VisibleTypePrivate Visible only for self or admin user + VisibleTypePrivate +) + +// VisibilityModes is a map of Visibility types +var VisibilityModes = map[string]VisibleType{ + "public": VisibleTypePublic, + "limited": VisibleTypeLimited, + "private": VisibleTypePrivate, +} + +// IsPublic returns true if VisibleType is public +func (vt VisibleType) IsPublic() bool { + return vt == VisibleTypePublic +} + +// IsLimited returns true if VisibleType is limited +func (vt VisibleType) IsLimited() bool { + return vt == VisibleTypeLimited +} + +// IsPrivate returns true if VisibleType is private +func (vt VisibleType) IsPrivate() bool { + return vt == VisibleTypePrivate +} + +// VisibilityString provides the mode string of the visibility type (public, limited, private) +func (vt VisibleType) String() string { + for k, v := range VisibilityModes { + if vt == v { + return k + } + } + return "" +} + +// ExtractKeysFromMapString provides a slice of keys from map +func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) { + for k := range in { + keys = append(keys, k) + } + return keys +} diff --git a/gitea/user.go b/gitea/user.go new file mode 100644 index 0000000..978ab9c --- /dev/null +++ b/gitea/user.go @@ -0,0 +1,25 @@ +package gitea + +import ( + "net/url" + "path" + + gitea_api "sirherobrine23.com.br/go-bds/request/gitea/structs" + "sirherobrine23.com.br/go-bds/request/v2" +) + +// Finder user by username +func (client Gitea) User(username string) (*gitea_api.User, error) { + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*gitea_api.User](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "users", url.PathEscape(username))}).String(), reqOptions) + return res, err +} + +// Return current token User +func (client Gitea) Whoami() (*gitea_api.User, error) { + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*gitea_api.User](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "v1/user")}).String(), reqOptions) + return res, err +} diff --git a/github/apipaginator.go b/github/apipaginator.go new file mode 100644 index 0000000..c4eab59 --- /dev/null +++ b/github/apipaginator.go @@ -0,0 +1,69 @@ +package github + +import ( + "net/http" + "net/url" + "strconv" + "strings" +) + +type githubPagination struct { + NextPage, PrevPage, FirstPage, LastPage int + NextPageToken, Cursor, Before, After string +} + +func parsePaginator(he http.Header) *githubPagination { + r := &githubPagination{} + if links, ok := he["Link"]; ok && len(links) > 0 { + for link := range strings.SplitSeq(links[0], ",") { + segments := strings.Split(strings.TrimSpace(link), ";") + if len(segments) < 2 { + continue + } + if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { + continue + } + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + q := url.Query() + if cursor := q.Get("cursor"); cursor != "" { + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.Cursor = cursor + } + } + continue + } + page := q.Get("page") + since := q.Get("since") + before := q.Get("before") + after := q.Get("after") + if page == "" && before == "" && after == "" && since == "" { + continue + } + if since != "" && page == "" { + page = since + } + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + if r.NextPage, err = strconv.Atoi(page); err != nil { + r.NextPageToken = page + } + r.After = after + case `rel="prev"`: + r.PrevPage, _ = strconv.Atoi(page) + r.Before = before + case `rel="first"`: + r.FirstPage, _ = strconv.Atoi(page) + case `rel="last"`: + r.LastPage, _ = strconv.Atoi(page) + } + } + } + } + return r +} diff --git a/github/github.go b/github/github.go new file mode 100644 index 0000000..343b452 --- /dev/null +++ b/github/github.go @@ -0,0 +1,103 @@ +// Client to Github APIs +package github + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "sirherobrine23.com.br/go-bds/request/v2" +) + +var ( + GithubAPI = &url.URL{Scheme: "https", Host: "api.github.com", Path: "/"} // Defaut github api + + ErrToken error = errors.New("require Token to request") + + err500 = request.RequestStatusFunction(func(res *http.Response) (*http.Response, error) { + buffLog, _ := io.ReadAll(res.Body) + if res.Body != nil { + res.Body.Close() + } + if len(buffLog) == 0 { + buffLog = []byte("not body response") + } + return nil, fmt.Errorf("cannot process request (%s), github api is down: %s", res.Request.URL.String(), buffLog) + }) + + processCodes = request.MapCode{ + 500: err500, + 501: err500, + 401: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + return nil, ErrToken + }, + 403: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + return nil, errors.New("cannot process request, authorization or not have permission") + }, + 429: func(res *http.Response) (*http.Response, error) { + if res.Body != nil { + res.Body.Close() + } + // x-ratelimit-reset + if window, err := strconv.Atoi(res.Header.Get("x-ratelimit-reset")); err == nil { + <-time.After(time.Duration(window)) + } + return request.ReRequest(res) + }, + } +) + +// Create new Github Client with Github.com API host +func NewClient(Username, Repository, Token string) *Github { + return &Github{ + Host: GithubAPI, + Repository: Repository, + Username: Username, + Token: Token, + } +} + +// Create new Github Client +func NewClientHost(apiHost, Username, Repository, Token string) (*Github, error) { + hostUrl, err := url.Parse(apiHost) + if err != nil { + return nil, err + } + return &Github{ + Host: hostUrl, + Repository: Repository, + Username: Username, + Token: Token, + }, nil +} + +// Basic client to Github APIs +type Github struct { + Host *url.URL // Github API host + Token string // Github Token + + // User and Repository name + Username, Repository string +} + +// Return escapated Owner+/+Repository +func (client Github) repoPath() string { + return fmt.Sprintf("%s/%s", url.PathEscape(client.Username), url.PathEscape(client.Repository)) +} + +// Set token to header +func (client Github) authHeader(header *request.Header) { + if client.Token != "" { + (*header)["Authorization"] = fmt.Sprintf("Bearer %s", client.Token) + } +} diff --git a/github/releases.go b/github/releases.go new file mode 100644 index 0000000..f77b9e9 --- /dev/null +++ b/github/releases.go @@ -0,0 +1,194 @@ +package github + +import ( + "fmt" + "io" + "iter" + "net/http" + "net/url" + "path" + "strconv" + "time" + + "sirherobrine23.com.br/go-bds/request/v2" +) + +// File assert +type ReleaseAsset struct { + URL string `json:"url"` + ID int `json:"id"` + NodeID string `json:"node_id"` + Name string `json:"name"` + Label string `json:"label"` + Uploader *User `json:"uploader"` + ContentType string `json:"content_type"` + State string `json:"state"` + Size int `json:"size"` + DownloadCount int `json:"download_count"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + BrowserDownloadURL string `json:"browser_download_url"` +} + +// Release +type Release struct { + ID int `json:"id"` + TagName string `json:"tag_name"` + Name string `json:"name"` + URL string `json:"url"` + AssetsURL string `json:"assets_url"` + UploadURL string `json:"upload_url"` + HTMLURL string `json:"html_url"` + NodeID string `json:"node_id"` + TargetCommitish string `json:"target_commitish"` + Body string `json:"body"` + Draft bool `json:"draft"` + Prerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` + TarballURL string `json:"tarball_url"` + ZipballURL string `json:"zipball_url"` + MentionsCount int `json:"mentions_count,omitempty"` + Author *User `json:"author"` + Assets []*ReleaseAsset `json:"assets"` + Reactions struct { + URL string `json:"url"` + TotalCount int `json:"total_count"` + PlusOne int `json:"+1"` + LessOne int `json:"-1"` + Laugh int `json:"laugh"` + Hooray int `json:"hooray"` + Confused int `json:"confused"` + Heart int `json:"heart"` + Rocket int `json:"rocket"` + Eyes int `json:"eyes"` + } `json:"reactions"` +} + +// Get release by tag name +func (client Github) ReleaseTag(tagName string) (*Release, error) { + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*Release](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "repos", client.repoPath(), "releases/tags", url.PathEscape(tagName))}).String(), reqOptions) + return res, err +} + +// Get release by ID +func (client Github) Release(id int) (*Release, error) { + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*Release](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "repos", client.repoPath(), "releases", strconv.Itoa(id))}).String(), reqOptions) + return res, err +} + +// Return all release from API +func (client Github) ReleaseSeq() iter.Seq2[*Release, error] { + releases, res, err := []*Release{}, (*http.Response)(nil), error(nil) + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + return func(yield func(*Release, error) bool) { + requestUrl := client.Host.ResolveReference(&url.URL{RawQuery: "per_page=100", Path: path.Join(client.Host.Path, "repos", client.repoPath(), "releases")}).String() + for requestUrl != "" { + if len(releases) == 0 { + if releases, res, err = request.JSON[[]*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 Github) AssetsSeq(releaseID int) iter.Seq2[*ReleaseAsset, error] { + asserts, res, err := []*ReleaseAsset{}, (*http.Response)(nil), error(nil) + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + return func(yield func(*ReleaseAsset, error) bool) { + requestUrl := client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "repos", client.repoPath(), "releases", strconv.Itoa(releaseID), "assets")}).String() + for requestUrl != "" { + if len(asserts) == 0 { + if asserts, res, err = request.JSON[[]*ReleaseAsset](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:] + } + } + } +} + +// Return all release in to slice +func (client Github) AllReleases() ([]*Release, error) { + allRels := []*Release{} + for release, err := range client.ReleaseSeq() { + if err != nil { + return nil, err + } + allRels = append(allRels, release) + } + return allRels, nil +} + +// List all release Assets in to slice +func (client Github) AllAssets(releaseID int) ([]*ReleaseAsset, error) { + asserts := []*ReleaseAsset{} + 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 Github) DeleteAsset(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, "repos", client.repoPath(), "releases/assets", strconv.Itoa(assetID))}), reqOptions) + res.Body.Close() + return err +} + +// Upload file to Release +func (client Github) UploadAsset(releaseID int, name string, file io.Reader) (*ReleaseAsset, error) { + reqOptions := &request.Options{ + Method: "POST", + Body: file, + Header: request.Header{"Content-Type": "application/octet-stream"}, + 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) + requestUrl := client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "repos", client.repoPath(), "releases", strconv.Itoa(releaseID), "assets")}) + requestUrl.RawQuery = fmt.Sprintf("name=%s", url.QueryEscape(name)) + fileInfo, _, err := request.JSON[*ReleaseAsset](requestUrl.String(), reqOptions) + return fileInfo, err +} diff --git a/github/users.go b/github/users.go new file mode 100644 index 0000000..2fd3f8f --- /dev/null +++ b/github/users.go @@ -0,0 +1,76 @@ +package github + +import ( + "net/url" + "path" + "time" + + "sirherobrine23.com.br/go-bds/request/v2" +) + +// User info +type User struct { + Login string `json:"login"` + ID int `json:"id"` + NodeID string `json:"node_id"` + AvatarURL string `json:"avatar_url"` + GravatarID string `json:"gravatar_id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + Name string `json:"name"` + Company string `json:"company"` + Blog string `json:"blog"` + Location string `json:"location"` + Email string `json:"email"` + Hireable bool `json:"hireable"` + Bio string `json:"bio"` + TwitterUsername string `json:"twitter_username"` + PublicRepos int `json:"public_repos"` + PublicGists int `json:"public_gists"` + Followers int `json:"followers"` + Following int `json:"following"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PrivateGists int `json:"private_gists"` + TotalPrivateRepos int `json:"total_private_repos"` + OwnedPrivateRepos int `json:"owned_private_repos"` + DiskUsage int `json:"disk_usage"` + Collaborators int `json:"collaborators"` + TwoFactorAuthentication bool `json:"two_factor_authentication"` + Plan struct { + Name string `json:"name"` + Space int `json:"space"` + PrivateRepos int `json:"private_repos"` + Collaborators int `json:"collaborators"` + } `json:"plan"` +} + +// Finder user by username +func (client Github) User(username string) (*User, error) { + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*User](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "users", url.PathEscape(username))}).String(), reqOptions) + return res, err +} + +// Return current token User +func (client Github) Whoami() (*User, error) { + if client.Token == "" { + return nil, ErrToken + } + reqOptions := &request.Options{Method: "GET", Header: request.Header{}, CodeProcess: processCodes} + client.authHeader(&reqOptions.Header) + res, _, err := request.JSON[*User](client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, "user")}).String(), reqOptions) + return res, err +} diff --git a/go.mod b/go.mod index 80e7d5c..1663658 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,20 @@ module sirherobrine23.com.br/go-bds/request go 1.24 require ( + code.gitea.io/gitea v1.23.6 github.com/PuerkitoBio/goquery v1.10.2 github.com/klauspost/compress v1.18.0 + github.com/stretchr/testify v1.10.0 github.com/ulikunitz/xz v0.5.12 + gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/andybalholm/cascadia v1.3.3 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect golang.org/x/net v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index 34f1e66..4f1ab76 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,31 @@ +code.gitea.io/gitea v1.23.6 h1:4MI1moqMFN+QrnOu9/souwwibrPdVKsN7MRkgKhAGsk= +code.gitea.io/gitea v1.23.6/go.mod h1:DHD2Odz4ACaTm0b2ju+zyljlzXO7O3ibwZwAwBFFM7E= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -28,8 +49,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -50,8 +69,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -77,3 +94,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/request.go b/v2/request.go index a965118..b3c99ac 100644 --- a/v2/request.go +++ b/v2/request.go @@ -54,7 +54,7 @@ func (mapCode MapCode) HasCode(httpCode int) bool { if mapCode == nil { return false } - return mapCode[httpCode] != nil && slices.Contains(slices.Collect(maps.Keys(mapCode)), httpCode) + return slices.Contains(slices.Collect(maps.Keys(mapCode)), httpCode) && mapCode[httpCode] != nil } // Exists -1 in code @@ -62,6 +62,12 @@ func (mapCode MapCode) HasAll() bool { return mapCode.HasCode(-1) } +func (mapCode MapCode) Extends(input MapCode) MapCode { + newMap := maps.Clone(mapCode) + maps.Insert(newMap, maps.All(input)) + return newMap +} + // HTTP headers type Header map[string]string @@ -195,8 +201,14 @@ func MakeRequestWithStatus(Url *url.URL, requestOptions *Options) (*http.Respons return response, &ResponseError{response, response.Request} } +// Make new request from old request response +func ReRequest(res *http.Response) (*http.Response, error) { + client := &http.Client{Transport: http.DefaultTransport, CheckRedirect: http.DefaultClient.CheckRedirect, Jar: http.DefaultClient.Jar} + return client.Do(res.Request) +} + // Make request and copy all data from response to [*bytes.Buffer] -func Buffer(Url string, Option *Options) (*bytes.Buffer, *http.Response, error) { +func Buffer(Url string, Option *Options) ([]byte, *http.Response, error) { res, err := Request(Url, Option) if err != nil { return nil, res, err @@ -206,7 +218,7 @@ func Buffer(Url string, Option *Options) (*bytes.Buffer, *http.Response, error) if err != nil { return nil, res, err } - return bytes.NewBuffer(data), res, err + return data, res, err } // Make request and save response in Disk -- 2.49.0