diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb515f26854d2..026536868d50c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -207,6 +207,10 @@ In general, HTTP methods are chosen as follows: An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required). +### Endpoints returning lists should + * support pagination (`page` & `limit` options in query) + * set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444)) + ## Developer Certificate of Origin (DCO) @@ -231,8 +235,8 @@ on, finishing, and issuing releases. The overall goal is to make a minor release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. All the feature pull requests should be -merged before feature freeze. And, during the frozen period, a corresponding -release branch is open for fixes backported from main branch. Release candidates +merged before feature freeze. And, during the frozen period, a corresponding +release branch is open for fixes backported from main branch. Release candidates are made during this period for user testing to obtain a final version that is maintained in this branch. A release is maintained by issuing patch releases to only correct critical problems diff --git a/integrations/api_issue_tracked_time_test.go b/integrations/api_issue_tracked_time_test.go index 1a0ee99a43ad8..9731c5a93b6c7 100644 --- a/integrations/api_issue_tracked_time_test.go +++ b/integrations/api_issue_tracked_time_test.go @@ -30,7 +30,7 @@ func TestAPIGetTrackedTimes(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) var apiTimes api.TrackedTimeList DecodeJSON(t, resp, &apiTimes) - expect, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue2.ID}) + expect, err := models.GetTrackedTimes(&models.FindTrackedTimesOptions{IssueID: issue2.ID}) assert.NoError(t, err) assert.Len(t, apiTimes, 3) diff --git a/integrations/api_repo_topic_test.go b/integrations/api_repo_topic_test.go index 5e42bc64bf921..bab17a68be5a5 100644 --- a/integrations/api_repo_topic_test.go +++ b/integrations/api_repo_topic_test.go @@ -7,6 +7,7 @@ package integrations import ( "fmt" "net/http" + "net/url" "testing" "code.gitea.io/gitea/models" @@ -15,6 +16,38 @@ import ( "github.com/stretchr/testify/assert" ) +func TestAPITopicSearch(t *testing.T) { + defer prepareTestEnv(t)() + searchURL, _ := url.Parse("/api/v1/topics/search") + var topics struct { + TopicNames []*api.TopicResponse `json:"topics"` + } + + query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} + + searchURL.RawQuery = query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 4) + assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + + query.Add("q", "topic") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 2) + + query.Set("q", "database") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + if assert.Len(t, topics.TopicNames, 1) { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.EqualValues(t, "database", topics.TopicNames[0].Name) + assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + } +} + func TestAPIRepoTopic(t *testing.T) { defer prepareTestEnv(t)() user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of repo2 diff --git a/models/access.go b/models/access.go index d35b900cfdf94..5d0b0b06cfe18 100644 --- a/models/access.go +++ b/models/access.go @@ -246,7 +246,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err return fmt.Errorf("refreshCollaboratorAccesses: %v", err) } - if err = repo.Owner.getTeams(e); err != nil { + if err = repo.Owner.loadTeams(e); err != nil { return err } diff --git a/models/commit_status.go b/models/commit_status.go index c27582024280f..1ee3ead21709f 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -159,7 +159,7 @@ func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListO if len(ids) == 0 { return statuses, nil } - return statuses, x.In("id", ids).Find(&statuses) + return statuses, e.In("id", ids).Find(&statuses) } // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts diff --git a/models/gpg_key.go b/models/gpg_key.go index 74ffb82a545b5..1072813b1b71c 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -71,6 +71,11 @@ func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error return keys, sess.Find(&keys) } +// CountUserGPGKeys return number of gpg keys a user own +func CountUserGPGKeys(userID int64) (int64, error) { + return x.Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) +} + // GetGPGKeyByID returns public key by given ID. func GetGPGKeyByID(keyID int64) (*GPGKey, error) { key := new(GPGKey) diff --git a/models/issue.go b/models/issue.go index 225dfee20f095..8b83612c2a4fd 100644 --- a/models/issue.go +++ b/models/issue.go @@ -89,7 +89,7 @@ func init() { func (issue *Issue) loadTotalTimes(e Engine) (err error) { opts := FindTrackedTimesOptions{IssueID: issue.ID} - issue.TotalTrackedTime, err = opts.ToSession(e).SumInt(&TrackedTime{}, "time") + issue.TotalTrackedTime, err = opts.toSession(e).SumInt(&TrackedTime{}, "time") if err != nil { return err } @@ -214,7 +214,7 @@ func (issue *Issue) loadCommentsByType(e Engine, tp CommentType) (err error) { if issue.Comments != nil { return nil } - issue.Comments, err = findComments(e, FindCommentsOptions{ + issue.Comments, err = findComments(e, &FindCommentsOptions{ IssueID: issue.ID, Type: tp, }) diff --git a/models/issue_comment.go b/models/issue_comment.go index 755041efd7ed1..06b74dff85d7e 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -999,7 +999,7 @@ func (opts *FindCommentsOptions) toConds() builder.Cond { return cond } -func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) { +func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) { comments := make([]*Comment, 0, 10) sess := e.Where(opts.toConds()) if opts.RepoID > 0 { @@ -1019,10 +1019,19 @@ func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) { } // FindComments returns all comments according options -func FindComments(opts FindCommentsOptions) ([]*Comment, error) { +func FindComments(opts *FindCommentsOptions) ([]*Comment, error) { return findComments(x, opts) } +// CountComments count all comments according options by ignoring pagination +func CountComments(opts *FindCommentsOptions) (int64, error) { + sess := x.Where(opts.toConds()) + if opts.RepoID > 0 { + sess.Join("INNER", "issue", "issue.id = comment.issue_id") + } + return sess.Count(&Comment{}) +} + // UpdateComment updates information of comment. func UpdateComment(c *Comment, doer *User) error { sess := x.NewSession() diff --git a/models/issue_label.go b/models/issue_label.go index d1ff4692366d0..5d50203b5a5ba 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -444,6 +444,11 @@ func GetLabelsByRepoID(repoID int64, sortType string, listOptions ListOptions) ( return getLabelsByRepoID(x, repoID, sortType, listOptions) } +// CountLabelsByRepoID count number of all labels that belong to given repository by ID. +func CountLabelsByRepoID(repoID int64) (int64, error) { + return x.Where("repo_id = ?", repoID).Count(&Label{}) +} + // ________ // \_____ \_______ ____ // / | \_ __ \/ ___\ @@ -556,6 +561,11 @@ func GetLabelsByOrgID(orgID int64, sortType string, listOptions ListOptions) ([] return getLabelsByOrgID(x, orgID, sortType, listOptions) } +// CountLabelsByOrgID count all labels that belong to given organization by ID. +func CountLabelsByOrgID(orgID int64) (int64, error) { + return x.Where("org_id = ?", orgID).Count(&Label{}) +} + // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/models/issue_milestone.go b/models/issue_milestone.go index 5e934cde0a02f..e6976a46c71e2 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -380,24 +380,33 @@ type GetMilestonesOption struct { SortType string } -// GetMilestones returns milestones filtered by GetMilestonesOption's -func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) { - sess := x.Where("repo_id = ?", opts.RepoID) +func (opts GetMilestonesOption) toCond() builder.Cond { + cond := builder.NewCond() + if opts.RepoID != 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } switch opts.State { case api.StateClosed: - sess = sess.And("is_closed = ?", true) + cond = cond.And(builder.Eq{"is_closed": true}) case api.StateAll: break // api.StateOpen: default: - sess = sess.And("is_closed = ?", false) + cond = cond.And(builder.Eq{"is_closed": false}) } if len(opts.Name) != 0 { - sess = sess.And(builder.Like{"name", opts.Name}) + cond = cond.And(builder.Like{"name", opts.Name}) } + return cond +} + +// GetMilestones returns milestones filtered by GetMilestonesOption's +func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) { + sess := x.Where(opts.toCond()) + if opts.Page != 0 { sess = opts.setSessionPagination(sess) } @@ -420,7 +429,8 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) { } miles := make([]*Milestone, 0, opts.PageSize) - return miles, sess.Find(&miles) + total, err := sess.FindAndCount(&miles) + return miles, total, err } // SearchMilestones search milestones diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index 5406129884fb0..1472c951eb405 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -50,7 +50,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) test := func(repoID int64, state api.StateType) { repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - milestones, err := GetMilestones(GetMilestonesOption{ + milestones, _, err := GetMilestones(GetMilestonesOption{ RepoID: repo.ID, State: state, }) @@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { test(3, api.StateClosed) test(3, api.StateAll) - milestones, err := GetMilestones(GetMilestonesOption{ + milestones, _, err := GetMilestones(GetMilestonesOption{ RepoID: NonexistentID, State: api.StateOpen, }) @@ -100,7 +100,7 @@ func TestGetMilestones(t *testing.T) { repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) test := func(sortType string, sortCond func(*Milestone) int) { for _, page := range []int{0, 1} { - milestones, err := GetMilestones(GetMilestonesOption{ + milestones, _, err := GetMilestones(GetMilestonesOption{ ListOptions: ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, @@ -117,7 +117,7 @@ func TestGetMilestones(t *testing.T) { } assert.True(t, sort.IntsAreSorted(values)) - milestones, err = GetMilestones(GetMilestonesOption{ + milestones, _, err = GetMilestones(GetMilestonesOption{ ListOptions: ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index 8cdad94fd4ae1..9322e26bf239b 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -55,6 +55,11 @@ func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, er return sws, nil } +// CountUserStopwatches return count of all stopwatches of a user +func CountUserStopwatches(userID int64) (int64, error) { + return x.Where("user_id = ?", userID).Count(&Stopwatch{}) +} + // StopwatchExists returns true if the stopwatch exists func StopwatchExists(userID, issueID int64) bool { _, exists, _ := getStopwatch(x, userID, issueID) diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 43f2e13784b78..e7769b41dd415 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -79,8 +79,8 @@ type FindTrackedTimesOptions struct { CreatedBeforeUnix int64 } -// ToCond will convert each condition into a xorm-Cond -func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { +// toCond will convert each condition into a xorm-Cond +func (opts *FindTrackedTimesOptions) toCond() builder.Cond { cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false}) if opts.IssueID != 0 { cond = cond.And(builder.Eq{"issue_id": opts.IssueID}) @@ -103,14 +103,14 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond { return cond } -// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required -func (opts *FindTrackedTimesOptions) ToSession(e Engine) Engine { +// toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required +func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine { sess := e if opts.RepositoryID > 0 || opts.MilestoneID > 0 { sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id") } - sess = sess.Where(opts.ToCond()) + sess = sess.Where(opts.toCond()) if opts.Page != 0 { sess = opts.setEnginePagination(sess) @@ -119,18 +119,27 @@ func (opts *FindTrackedTimesOptions) ToSession(e Engine) Engine { return sess } -func getTrackedTimes(e Engine, options FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) { - err = options.ToSession(e).Find(&trackedTimes) +func getTrackedTimes(e Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) { + err = options.toSession(e).Find(&trackedTimes) return } // GetTrackedTimes returns all tracked times that fit to the given options. -func GetTrackedTimes(opts FindTrackedTimesOptions) (TrackedTimeList, error) { +func GetTrackedTimes(opts *FindTrackedTimesOptions) (TrackedTimeList, error) { return getTrackedTimes(x, opts) } +// CountTrackedTimes returns count of tracked times that fit to the given options. +func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) { + sess := x.Where(opts.toCond()) + if opts.RepositoryID > 0 || opts.MilestoneID > 0 { + sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id") + } + return sess.Count(&TrackedTime{}) +} + func getTrackedSeconds(e Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) { - return opts.ToSession(e).SumInt(&TrackedTime{}, "time") + return opts.toSession(e).SumInt(&TrackedTime{}, "time") } // GetTrackedSeconds return sum of seconds @@ -188,7 +197,7 @@ func addTime(e Engine, user *User, issue *Issue, amount int64, created time.Time } // TotalTimes returns the spent time for each user by an issue -func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) { +func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) { trackedTimes, err := GetTrackedTimes(options) if err != nil { return nil, err @@ -288,7 +297,7 @@ func deleteTimes(e Engine, opts FindTrackedTimesOptions) (removedTime int64, err return } - _, err = opts.ToSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true}) + _, err = opts.toSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true}) return } diff --git a/models/issue_tracked_time_test.go b/models/issue_tracked_time_test.go index e30de6cb69d40..9de1dd699d6f4 100644 --- a/models/issue_tracked_time_test.go +++ b/models/issue_tracked_time_test.go @@ -38,27 +38,27 @@ func TestGetTrackedTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) // by Issue - times, err := GetTrackedTimes(FindTrackedTimesOptions{IssueID: 1}) + times, err := GetTrackedTimes(&FindTrackedTimesOptions{IssueID: 1}) assert.NoError(t, err) assert.Len(t, times, 1) assert.Equal(t, int64(400), times[0].Time) - times, err = GetTrackedTimes(FindTrackedTimesOptions{IssueID: -1}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{IssueID: -1}) assert.NoError(t, err) assert.Len(t, times, 0) // by User - times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 1}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{UserID: 1}) assert.NoError(t, err) assert.Len(t, times, 3) assert.Equal(t, int64(400), times[0].Time) - times, err = GetTrackedTimes(FindTrackedTimesOptions{UserID: 3}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{UserID: 3}) assert.NoError(t, err) assert.Len(t, times, 0) // by Repo - times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 2}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{RepositoryID: 2}) assert.NoError(t, err) assert.Len(t, times, 3) assert.Equal(t, int64(1), times[0].Time) @@ -66,11 +66,11 @@ func TestGetTrackedTimes(t *testing.T) { assert.NoError(t, err) assert.Equal(t, issue.RepoID, int64(2)) - times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 1}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{RepositoryID: 1}) assert.NoError(t, err) assert.Len(t, times, 5) - times, err = GetTrackedTimes(FindTrackedTimesOptions{RepositoryID: 10}) + times, err = GetTrackedTimes(&FindTrackedTimesOptions{RepositoryID: 10}) assert.NoError(t, err) assert.Len(t, times, 0) } @@ -78,7 +78,7 @@ func TestGetTrackedTimes(t *testing.T) { func TestTotalTimes(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - total, err := TotalTimes(FindTrackedTimesOptions{IssueID: 1}) + total, err := TotalTimes(&FindTrackedTimesOptions{IssueID: 1}) assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { @@ -86,7 +86,7 @@ func TestTotalTimes(t *testing.T) { assert.Equal(t, "6min 40s", time) } - total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 2}) + total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 2}) assert.NoError(t, err) assert.Len(t, total, 2) for user, time := range total { @@ -99,7 +99,7 @@ func TestTotalTimes(t *testing.T) { } } - total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 5}) + total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 5}) assert.NoError(t, err) assert.Len(t, total, 1) for user, time := range total { @@ -107,7 +107,7 @@ func TestTotalTimes(t *testing.T) { assert.Equal(t, "1s", time) } - total, err = TotalTimes(FindTrackedTimesOptions{IssueID: 4}) + total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 4}) assert.NoError(t, err) assert.Len(t, total, 2) } diff --git a/models/notification.go b/models/notification.go index c4c7728ad9f6b..30bb7596a8a1d 100644 --- a/models/notification.go +++ b/models/notification.go @@ -125,6 +125,11 @@ func GetNotifications(opts *FindNotificationOptions) (NotificationList, error) { return getNotifications(x, opts) } +// CountNotifications count all notifications that fit to the given options and ignore pagination. +func CountNotifications(opts *FindNotificationOptions) (int64, error) { + return x.Where(opts.ToCond()).Count(&Notification{}) +} + // CreateRepoTransferNotification creates notification for the user a repository was transferred to func CreateRepoTransferNotification(doer, newOwner *User, repo *Repository) error { sess := x.NewSession() diff --git a/models/oauth2_application.go b/models/oauth2_application.go index 2aa9fbd3d9217..9cf236f0cb427 100644 --- a/models/oauth2_application.go +++ b/models/oauth2_application.go @@ -269,7 +269,7 @@ func DeleteOAuth2Application(id, userid int64) error { } // ListOAuth2Applications returns a list of oauth2 applications belongs to given user. -func ListOAuth2Applications(uid int64, listOptions ListOptions) ([]*OAuth2Application, error) { +func ListOAuth2Applications(uid int64, listOptions ListOptions) ([]*OAuth2Application, int64, error) { sess := x. Where("uid=?", uid). Desc("id") @@ -278,11 +278,13 @@ func ListOAuth2Applications(uid int64, listOptions ListOptions) ([]*OAuth2Applic sess = listOptions.setSessionPagination(sess) apps := make([]*OAuth2Application, 0, listOptions.PageSize) - return apps, sess.Find(&apps) + total, err := sess.FindAndCount(&apps) + return apps, total, err } apps := make([]*OAuth2Application, 0, 5) - return apps, sess.Find(&apps) + total, err := sess.FindAndCount(&apps) + return apps, total, err } ////////////////////////////////////////////////////// diff --git a/models/org.go b/models/org.go index 58fb26b1bb511..3de6bd14b273c 100644 --- a/models/org.go +++ b/models/org.go @@ -52,7 +52,7 @@ func (org *User) GetOwnerTeam() (*Team, error) { return org.getOwnerTeam(x) } -func (org *User) getTeams(e Engine) error { +func (org *User) loadTeams(e Engine) error { if org.Teams != nil { return nil } @@ -62,13 +62,9 @@ func (org *User) getTeams(e Engine) error { Find(&org.Teams) } -// GetTeams returns paginated teams that belong to organization. -func (org *User) GetTeams(opts *SearchTeamOptions) error { - if opts.Page != 0 { - return org.getTeams(opts.getPaginatedSession()) - } - - return org.getTeams(x) +// LoadTeams load teams if not loaded. +func (org *User) LoadTeams() error { + return org.loadTeams(x) } // GetMembers returns all members of organization. @@ -87,7 +83,7 @@ type FindOrgMembersOpts struct { } // CountOrgMembers counts the organization's members -func CountOrgMembers(opts FindOrgMembersOpts) (int64, error) { +func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) { sess := x.Where("org_id=?", opts.OrgID) if opts.PublicOnly { sess.And("is_public = ?", true) diff --git a/models/org_team.go b/models/org_team.go index 6226772b9a828..c380c8cd8ef58 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -790,16 +790,6 @@ func GetTeamMembers(teamID int64) ([]*User, error) { return getTeamMembers(x, teamID) } -func getUserTeams(e Engine, userID int64, listOptions ListOptions) (teams []*Team, err error) { - sess := e. - Join("INNER", "team_user", "team_user.team_id = team.id"). - Where("team_user.uid=?", userID) - if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) - } - return teams, sess.Find(&teams) -} - func getUserOrgTeams(e Engine, orgID, userID int64) (teams []*Team, err error) { return teams, e. Join("INNER", "team_user", "team_user.team_id = team.id"). @@ -823,11 +813,6 @@ func GetUserOrgTeams(orgID, userID int64) ([]*Team, error) { return getUserOrgTeams(x, orgID, userID) } -// GetUserTeams returns all teams that user belongs across all organizations. -func GetUserTeams(userID int64, listOptions ListOptions) ([]*Team, error) { - return getUserTeams(x, userID, listOptions) -} - // AddTeamMember adds new membership of given team to given organization, // the user will have membership to given organization automatically when needed. func AddTeamMember(team *Team, userID int64) error { diff --git a/models/org_team_test.go b/models/org_team_test.go index 38e36cf82ccf5..9dc26fd814a67 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -286,7 +286,7 @@ func TestGetTeamMembers(t *testing.T) { func TestGetUserTeams(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) test := func(userID int64) { - teams, err := GetUserTeams(userID, ListOptions{}) + teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID}) assert.NoError(t, err) for _, team := range teams { AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) diff --git a/models/org_test.go b/models/org_test.go index e494e502dd31e..7ba9d8ee88b8e 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -86,7 +86,7 @@ func TestUser_GetOwnerTeam(t *testing.T) { func TestUser_GetTeams(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - assert.NoError(t, org.GetTeams(&SearchTeamOptions{})) + assert.NoError(t, org.LoadTeams()) if assert.Len(t, org.Teams, 4) { assert.Equal(t, int64(1), org.Teams[0].ID) assert.Equal(t, int64(2), org.Teams[1].ID) diff --git a/models/repo.go b/models/repo.go index 93827f6a8842c..f2e3f5b58bc30 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1125,8 +1125,8 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO // Give access to all members in teams with access to all repositories. if u.IsOrganization() { - if err := u.getTeams(ctx.e); err != nil { - return fmt.Errorf("GetTeams: %v", err) + if err := u.loadTeams(ctx.e); err != nil { + return fmt.Errorf("loadTeams: %v", err) } for _, t := range u.Teams { if t.IncludesAllRepositories { @@ -1439,7 +1439,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { return err } if org.IsOrganization() { - if err = org.getTeams(sess); err != nil { + if err = org.loadTeams(sess); err != nil { return err } } @@ -1453,7 +1453,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { } // Delete Deploy Keys - deployKeys, err := listDeployKeys(sess, repo.ID, ListOptions{}) + deployKeys, err := listDeployKeys(sess, &ListDeployKeysOptions{RepoID: repoID}) if err != nil { return fmt.Errorf("listDeployKeys: %v", err) } diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index b9488f5e2e3ea..a8b715bbcfe63 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -102,6 +102,11 @@ func (repo *Repository) GetCollaborators(listOptions ListOptions) ([]*Collaborat return repo.getCollaborators(x, listOptions) } +// CountCollaborators returns total number of collaborators for a repository +func (repo *Repository) CountCollaborators() (int64, error) { + return x.Where("repo_id = ? ", repo.ID).Count(&Collaboration{}) +} + func (repo *Repository) getCollaboration(e Engine, uid int64) (*Collaboration, error) { collaboration := &Collaboration{ RepoID: repo.ID, diff --git a/models/repo_generate.go b/models/repo_generate.go index 1cf73bc55e6ad..66682903f1e97 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -111,7 +111,7 @@ func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) err // GenerateWebhooks generates webhooks from a template repository func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) error { - templateWebhooks, err := GetWebhooksByRepoID(templateRepo.ID, ListOptions{}) + templateWebhooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: templateRepo.ID}) if err != nil { return err } diff --git a/models/repo_transfer.go b/models/repo_transfer.go index d7ef0a8ca6b3b..09b6290293d57 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -291,8 +291,8 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err e } if newOwner.IsOrganization() { - if err := newOwner.getTeams(sess); err != nil { - return fmt.Errorf("GetTeams: %v", err) + if err := newOwner.loadTeams(sess); err != nil { + return fmt.Errorf("LoadTeams: %v", err) } for _, t := range newOwner.Teams { if t.IncludesAllRepositories { diff --git a/models/review.go b/models/review.go index acb54d970fdc8..1ffff8feb6470 100644 --- a/models/review.go +++ b/models/review.go @@ -208,6 +208,11 @@ func FindReviews(opts FindReviewOptions) ([]*Review, error) { return findReviews(x, opts) } +// CountReviews returns count of reviews passing FindReviewOptions +func CountReviews(opts FindReviewOptions) (int64, error) { + return x.Where(opts.toCond()).Count(&Review{}) +} + // CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required. type CreateReviewOptions struct { Content string diff --git a/models/ssh_key.go b/models/ssh_key.go index 6cda4f1658fb2..a2f4c610e4047 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -205,6 +205,12 @@ func ListPublicKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) { return keys, sess.Find(&keys) } +// CountPublicKeys count public keys a user has +func CountPublicKeys(userID int64) (int64, error) { + sess := x.Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal) + return sess.Count(&PublicKey{}) +} + // ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source. func ListPublicKeysBySource(uid, loginSourceID int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) diff --git a/models/ssh_key_deploy.go b/models/ssh_key_deploy.go index 3189bcf456a64..e7d486b9f56cb 100644 --- a/models/ssh_key_deploy.go +++ b/models/ssh_key_deploy.go @@ -264,17 +264,40 @@ func deleteDeployKey(sess Engine, doer *User, id int64) error { return nil } -// ListDeployKeys returns all deploy keys by given repository ID. -func ListDeployKeys(repoID int64, listOptions ListOptions) ([]*DeployKey, error) { - return listDeployKeys(x, repoID, listOptions) +// ListDeployKeysOptions are options for ListDeployKeys +type ListDeployKeysOptions struct { + ListOptions + RepoID int64 + KeyID int64 + Fingerprint string +} + +func (opt ListDeployKeysOptions) toCond() builder.Cond { + cond := builder.NewCond() + if opt.RepoID != 0 { + cond = cond.And(builder.Eq{"repo_id": opt.RepoID}) + } + if opt.KeyID != 0 { + cond = cond.And(builder.Eq{"key_id": opt.KeyID}) + } + if opt.Fingerprint != "" { + cond = cond.And(builder.Eq{"fingerprint": opt.Fingerprint}) + } + return cond } -func listDeployKeys(e Engine, repoID int64, listOptions ListOptions) ([]*DeployKey, error) { - sess := e.Where("repo_id = ?", repoID) - if listOptions.Page != 0 { - sess = listOptions.setSessionPagination(sess) +// ListDeployKeys returns a list of deploy keys matching the provided arguments. +func ListDeployKeys(opts *ListDeployKeysOptions) ([]*DeployKey, error) { + return listDeployKeys(x, opts) +} - keys := make([]*DeployKey, 0, listOptions.PageSize) +func listDeployKeys(e Engine, opts *ListDeployKeysOptions) ([]*DeployKey, error) { + sess := e.Where(opts.toCond()) + + if opts.Page != 0 { + sess = opts.setSessionPagination(sess) + + keys := make([]*DeployKey, 0, opts.PageSize) return keys, sess.Find(&keys) } @@ -282,18 +305,7 @@ func listDeployKeys(e Engine, repoID int64, listOptions ListOptions) ([]*DeployK return keys, sess.Find(&keys) } -// SearchDeployKeys returns a list of deploy keys matching the provided arguments. -func SearchDeployKeys(repoID, keyID int64, fingerprint string) ([]*DeployKey, error) { - keys := make([]*DeployKey, 0, 5) - cond := builder.NewCond() - if repoID != 0 { - cond = cond.And(builder.Eq{"repo_id": repoID}) - } - if keyID != 0 { - cond = cond.And(builder.Eq{"key_id": keyID}) - } - if fingerprint != "" { - cond = cond.And(builder.Eq{"fingerprint": fingerprint}) - } - return keys, x.Where(cond).Find(&keys) +// CountDeployKeys returns count deploy keys matching the provided arguments. +func CountDeployKeys(opts *ListDeployKeysOptions) (int64, error) { + return x.Where(opts.toCond()).Count(&DeployKey{}) } diff --git a/models/token.go b/models/token.go index 357afe44a7c0b..8e1f91d43f1cc 100644 --- a/models/token.go +++ b/models/token.go @@ -122,6 +122,15 @@ func UpdateAccessToken(t *AccessToken) error { return err } +// CountAccessTokens count access tokens belongs to given user by options +func CountAccessTokens(opts ListAccessTokensOptions) (int64, error) { + sess := x.Where("uid=?", opts.UserID) + if len(opts.Name) != 0 { + sess = sess.Where("name=?", opts.Name) + } + return sess.Count(&AccessToken{}) +} + // DeleteAccessTokenByID deletes access token by given ID. func DeleteAccessTokenByID(id, userID int64) error { cnt, err := x.ID(id).Delete(&AccessToken{ diff --git a/models/topic.go b/models/topic.go index 19c572fefebf6..7fc34f5bef19e 100644 --- a/models/topic.go +++ b/models/topic.go @@ -184,7 +184,7 @@ func (opts *FindTopicOptions) toConds() builder.Cond { } // FindTopics retrieves the topics via FindTopicOptions -func FindTopics(opts *FindTopicOptions) (topics []*Topic, err error) { +func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { sess := x.Select("topic.*").Where(opts.toConds()) if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") @@ -192,7 +192,18 @@ func FindTopics(opts *FindTopicOptions) (topics []*Topic, err error) { if opts.PageSize != 0 && opts.Page != 0 { sess = opts.setSessionPagination(sess) } - return topics, sess.Desc("topic.repo_count").Find(&topics) + topics := make([]*Topic, 0, 10) + total, err := sess.Desc("topic.repo_count").FindAndCount(&topics) + return topics, total, err +} + +// CountTopics counts the number of topics matching the FindTopicOptions +func CountTopics(opts *FindTopicOptions) (int64, error) { + sess := x.Where(opts.toConds()) + if opts.RepoID > 0 { + sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") + } + return sess.Count(new(Topic)) } // GetRepoTopicByName retrieves topic from name for a repo if it exist @@ -269,7 +280,7 @@ func DeleteTopic(repoID int64, topicName string) (*Topic, error) { // SaveTopics save topics to a repository func SaveTopics(repoID int64, topicNames ...string) error { - topics, err := FindTopics(&FindTopicOptions{ + topics, _, err := FindTopics(&FindTopicOptions{ RepoID: repoID, }) if err != nil { diff --git a/models/topic_test.go b/models/topic_test.go index 25232eb981c24..9386a71e35d23 100644 --- a/models/topic_test.go +++ b/models/topic_test.go @@ -17,17 +17,18 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - topics, err := FindTopics(&FindTopicOptions{}) + topics, _, err := FindTopics(&FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, err = FindTopics(&FindTopicOptions{ + topics, total, err := FindTopics(&FindTopicOptions{ ListOptions: ListOptions{Page: 1, PageSize: 2}, }) assert.NoError(t, err) assert.Len(t, topics, 2) + assert.EqualValues(t, 6, total) - topics, err = FindTopics(&FindTopicOptions{ + topics, _, err = FindTopics(&FindTopicOptions{ RepoID: 1, }) assert.NoError(t, err) @@ -35,11 +36,11 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, SaveTopics(2, "golang")) repo2NrOfTopics = 1 - topics, err = FindTopics(&FindTopicOptions{}) + topics, _, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, err = FindTopics(&FindTopicOptions{ + topics, _, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) @@ -52,11 +53,11 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, topic.RepoCount) - topics, err = FindTopics(&FindTopicOptions{}) + topics, _, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, err = FindTopics(&FindTopicOptions{ + topics, _, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) diff --git a/models/user.go b/models/user.go index c68417a2c3265..1af9e545adee2 100644 --- a/models/user.go +++ b/models/user.go @@ -1704,7 +1704,7 @@ func GetStarredRepos(userID int64, private bool, listOptions ListOptions) ([]*Re } // GetWatchedRepos returns the repos watched by a particular user -func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Repository, error) { +func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Repository, int64, error) { sess := x.Where("watch.user_id=?", userID). And("`watch`.mode<>?", RepoWatchModeDont). Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") @@ -1716,11 +1716,13 @@ func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Re sess = listOptions.setSessionPagination(sess) repos := make([]*Repository, 0, listOptions.PageSize) - return repos, sess.Find(&repos) + total, err := sess.FindAndCount(&repos) + return repos, total, err } repos := make([]*Repository, 0, 10) - return repos, sess.Find(&repos) + total, err := sess.FindAndCount(&repos) + return repos, total, err } // IterateUser iterate users diff --git a/models/webhook.go b/models/webhook.go index 138ba26bde090..79ce70a0de012 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -16,8 +16,10 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" gouuid "github.com/google/uuid" + "xorm.io/builder" ) // HookContentType is the content type of a web hook @@ -387,53 +389,51 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { }) } -// GetActiveWebhooksByRepoID returns all active webhooks of repository. -func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) { - return getActiveWebhooksByRepoID(x, repoID) +// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts +type ListWebhookOptions struct { + ListOptions + RepoID int64 + OrgID int64 + IsActive util.OptionalBool } -func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, e.Where("is_active=?", true). - Find(&webhooks, &Webhook{RepoID: repoID}) -} - -// GetWebhooksByRepoID returns all webhooks of a repository. -func GetWebhooksByRepoID(repoID int64, listOptions ListOptions) ([]*Webhook, error) { - if listOptions.Page == 0 { - webhooks := make([]*Webhook, 0, 5) - return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID}) +func (opts *ListWebhookOptions) toCond() builder.Cond { + cond := builder.NewCond() + if opts.RepoID != 0 { + cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID}) + } + if opts.OrgID != 0 { + cond = cond.And(builder.Eq{"webhook.org_id": opts.OrgID}) + } + if !opts.IsActive.IsNone() { + cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()}) } + return cond +} - sess := listOptions.getPaginatedSession() - webhooks := make([]*Webhook, 0, listOptions.PageSize) +func listWebhooksByOpts(e Engine, opts *ListWebhookOptions) ([]*Webhook, error) { + sess := e.Where(opts.toCond()) - return webhooks, sess.Find(&webhooks, &Webhook{RepoID: repoID}) -} + if opts.Page != 0 { + sess = opts.setSessionPagination(sess) + webhooks := make([]*Webhook, 0, opts.PageSize) + err := sess.Find(&webhooks) + return webhooks, err + } -// GetActiveWebhooksByOrgID returns all active webhooks for an organization. -func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) { - return getActiveWebhooksByOrgID(x, orgID) + webhooks := make([]*Webhook, 0, 10) + err := sess.Find(&webhooks) + return webhooks, err } -func getActiveWebhooksByOrgID(e Engine, orgID int64) (ws []*Webhook, err error) { - err = e. - Where("org_id=?", orgID). - And("is_active=?", true). - Find(&ws) - return ws, err +// ListWebhooksByOpts return webhooks based on options +func ListWebhooksByOpts(opts *ListWebhookOptions) ([]*Webhook, error) { + return listWebhooksByOpts(x, opts) } -// GetWebhooksByOrgID returns paginated webhooks for an organization. -func GetWebhooksByOrgID(orgID int64, listOptions ListOptions) ([]*Webhook, error) { - if listOptions.Page == 0 { - ws := make([]*Webhook, 0, 5) - return ws, x.Find(&ws, &Webhook{OrgID: orgID}) - } - - sess := listOptions.getPaginatedSession() - ws := make([]*Webhook, 0, listOptions.PageSize) - return ws, sess.Find(&ws, &Webhook{OrgID: orgID}) +// CountWebhooksByOpts count webhooks based on options and ignore pagination +func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) { + return x.Where(opts.toCond()).Count(&Webhook{}) } // GetDefaultWebhooks returns all admin-default webhooks. diff --git a/models/webhook_test.go b/models/webhook_test.go index 84520666c0b22..625d643856c5f 100644 --- a/models/webhook_test.go +++ b/models/webhook_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -118,7 +119,7 @@ func TestGetWebhookByOrgID(t *testing.T) { func TestGetActiveWebhooksByRepoID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - hooks, err := GetActiveWebhooksByRepoID(1) + hooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: 1, IsActive: util.OptionalBoolTrue}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { assert.Equal(t, int64(1), hooks[0].ID) @@ -128,7 +129,7 @@ func TestGetActiveWebhooksByRepoID(t *testing.T) { func TestGetWebhooksByRepoID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - hooks, err := GetWebhooksByRepoID(1, ListOptions{}) + hooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: 1}) assert.NoError(t, err) if assert.Len(t, hooks, 2) { assert.Equal(t, int64(1), hooks[0].ID) @@ -138,7 +139,7 @@ func TestGetWebhooksByRepoID(t *testing.T) { func TestGetActiveWebhooksByOrgID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - hooks, err := GetActiveWebhooksByOrgID(3) + hooks, err := ListWebhooksByOpts(&ListWebhookOptions{OrgID: 3, IsActive: util.OptionalBoolTrue}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { assert.Equal(t, int64(3), hooks[0].ID) @@ -148,7 +149,7 @@ func TestGetActiveWebhooksByOrgID(t *testing.T) { func TestGetWebhooksByOrgID(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - hooks, err := GetWebhooksByOrgID(3, ListOptions{}) + hooks, err := ListWebhooksByOpts(&ListWebhookOptions{OrgID: 3}) assert.NoError(t, err) if assert.Len(t, hooks, 1) { assert.Equal(t, int64(3), hooks[0].ID) diff --git a/modules/context/api.go b/modules/context/api.go index b543c8bac826d..47ea8acfe05f3 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -181,6 +181,23 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) { if len(links) > 0 { ctx.Header().Set("Link", strings.Join(links, ",")) + ctx.AppendAccessControlExposeHeaders("Link") + } +} + +// SetTotalCountHeader set "X-Total-Count" header +func (ctx *APIContext) SetTotalCountHeader(total int64) { + ctx.Header().Set("X-Total-Count", fmt.Sprint(total)) + ctx.AppendAccessControlExposeHeaders("X-Total-Count") +} + +// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header +func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) { + val := ctx.Header().Get("Access-Control-Expose-Headers") + if len(val) != 0 { + ctx.Header().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) + } else { + ctx.Header().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) } } diff --git a/modules/context/org.go b/modules/context/org.go index 527ccfbcaa8da..fd67212a10c43 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -123,8 +123,8 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { // Team. if ctx.Org.IsMember { if ctx.Org.IsOwner { - if err := org.GetTeams(&models.SearchTeamOptions{}); err != nil { - ctx.ServerError("GetTeams", err) + if err := org.LoadTeams(); err != nil { + ctx.ServerError("LoadTeams", err) return } } else { diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index d91c3ca97973c..44d7a048bc767 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) // TagPrefix tags prefix path on the repository @@ -160,24 +161,18 @@ func (repo *Repository) GetTag(name string) (*Tag, error) { } // GetTagInfos returns all tag infos of the repository. -func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, error) { +func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { // TODO this a slow implementation, makes one git command per tag stdout, err := NewCommand("tag").RunInDir(repo.Path) if err != nil { - return nil, err + return nil, 0, err } tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n") + tagsTotal := len(tagNames) if page != 0 { - skip := (page - 1) * pageSize - if skip >= len(tagNames) { - return nil, nil - } - if (len(tagNames) - skip) < pageSize { - pageSize = len(tagNames) - skip - } - tagNames = tagNames[skip : skip+pageSize] + tagNames = util.PaginateSlice(tagNames, page, pageSize).([]string) } var tags = make([]*Tag, 0, len(tagNames)) @@ -189,13 +184,13 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, error) { tag, err := repo.GetTag(tagName) if err != nil { - return nil, err + return nil, tagsTotal, err } tag.Name = tagName tags = append(tags, tag) } sortTagsByTime(tags) - return tags, nil + return tags, tagsTotal, nil } // GetTagType gets the type of the tag, either commit (simple) or tag (annotated) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 6feae8d913256..cfab9edd8f42e 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -18,9 +18,10 @@ func TestRepository_GetTags(t *testing.T) { assert.NoError(t, err) defer bareRepo1.Close() - tags, err := bareRepo1.GetTagInfos(0, 0) + tags, total, err := bareRepo1.GetTagInfos(0, 0) assert.NoError(t, err) assert.Len(t, tags, 1) + assert.Equal(t, len(tags), total) assert.EqualValues(t, "test", tags[0].Name) assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String()) assert.EqualValues(t, "tag", tags[0].Type) diff --git a/modules/migrations/gitea_uploader_test.go b/modules/migrations/gitea_uploader_test.go index 5f36d545846dd..2f854ba368a39 100644 --- a/modules/migrations/gitea_uploader_test.go +++ b/modules/migrations/gitea_uploader_test.go @@ -54,14 +54,14 @@ func TestGiteaUploadRepo(t *testing.T) { assert.True(t, repo.HasWiki()) assert.EqualValues(t, models.RepositoryReady, repo.Status) - milestones, err := models.GetMilestones(models.GetMilestonesOption{ + milestones, _, err := models.GetMilestones(models.GetMilestonesOption{ RepoID: repo.ID, State: structs.StateOpen, }) assert.NoError(t, err) assert.Len(t, milestones, 1) - milestones, err = models.GetMilestones(models.GetMilestonesOption{ + milestones, _, err = models.GetMilestones(models.GetMilestonesOption{ RepoID: repo.ID, State: structs.StateClosed, }) diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go index 062cee628320f..9c1b9fc0f8645 100644 --- a/routers/api/v1/admin/adopt.go +++ b/routers/api/v1/admin/adopt.go @@ -5,7 +5,6 @@ package admin import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -47,8 +46,7 @@ func ListUnadoptedRepositories(ctx *context.APIContext) { ctx.InternalServerError(err) } - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count") + ctx.SetTotalCountHeader(int64(count)) ctx.JSON(http.StatusOK, repoNames) } diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go index 2531346fcb0cd..970fad83880c0 100644 --- a/routers/api/v1/admin/cron.go +++ b/routers/api/v1/admin/cron.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/cron" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -36,12 +37,10 @@ func ListCronTasks(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" tasks := cron.ListTasks() - listOpts := utils.GetListOptions(ctx) - start, end := listOpts.GetStartEnd() + count := len(tasks) - if len(tasks) > listOpts.PageSize { - tasks = tasks[start:end] - } + listOpts := utils.GetListOptions(ctx) + tasks = util.PaginateSlice(tasks, listOpts.Page, listOpts.PageSize).(cron.TaskTable) res := make([]structs.Cron, len(tasks)) for i, task := range tasks { @@ -53,6 +52,8 @@ func ListCronTasks(ctx *context.APIContext) { ExecTimes: task.ExecTimes, } } + + ctx.SetTotalCountHeader(int64(count)) ctx.JSON(http.StatusOK, res) } diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 1356276f07f4f..f1a766d544d71 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -6,7 +6,6 @@ package admin import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -121,7 +120,6 @@ func GetAllOrgs(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &orgs) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 6bc9b849b1fc9..e5a75da759ea2 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -423,7 +423,6 @@ func GetAllUsers(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &results) } diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index af4507f896a8d..1a36642b6a26b 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -108,6 +108,12 @@ func ListRepoNotifications(ctx *context.APIContext) { } opts.RepoID = ctx.Repo.Repository.ID + totalCount, err := models.CountNotifications(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + nl, err := models.GetNotifications(opts) if err != nil { ctx.InternalServerError(err) @@ -119,6 +125,8 @@ func ListRepoNotifications(ctx *context.APIContext) { return } + ctx.SetTotalCountHeader(totalCount) + ctx.JSON(http.StatusOK, convert.ToNotifications(nl)) } diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index c2178b4deeafd..e4626cb719845 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -68,6 +68,12 @@ func ListNotifications(ctx *context.APIContext) { return } + totalCount, err := models.CountNotifications(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + nl, err := models.GetNotifications(opts) if err != nil { ctx.InternalServerError(err) @@ -79,6 +85,7 @@ func ListNotifications(ctx *context.APIContext) { return } + ctx.SetTotalCountHeader(totalCount) ctx.JSON(http.StatusOK, convert.ToNotifications(nl)) } diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go index ed827c48d43f6..c5982300eba2c 100644 --- a/routers/api/v1/org/hook.go +++ b/routers/api/v1/org/hook.go @@ -40,16 +40,29 @@ func ListHooks(ctx *context.APIContext) { // "200": // "$ref": "#/responses/HookList" - org := ctx.Org.Organization - orgHooks, err := models.GetWebhooksByOrgID(org.ID, utils.GetListOptions(ctx)) + opts := &models.ListWebhookOptions{ + ListOptions: utils.GetListOptions(ctx), + OrgID: ctx.Org.Organization.ID, + } + + count, err := models.CountWebhooksByOpts(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetWebhooksByOrgID", err) + ctx.InternalServerError(err) return } + + orgHooks, err := models.ListWebhooksByOpts(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + hooks := make([]*api.Hook, len(orgHooks)) for i, hook := range orgHooks { - hooks[i] = convert.ToHook(org.HomeLink(), hook) + hooks[i] = convert.ToHook(ctx.Org.Organization.HomeLink(), hook) } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, hooks) } diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index c70252158e9e8..09acb0bf04426 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -49,6 +49,13 @@ func ListLabels(ctx *context.APIContext) { return } + count, err := models.CountLabelsByOrgID(ctx.Org.Organization.ID) + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) } diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 09abad25572d9..97940d59251da 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -18,15 +18,21 @@ import ( // listMembers list an organization's members func listMembers(ctx *context.APIContext, publicOnly bool) { - var members []*models.User - - members, _, err := models.FindOrgMembers(&models.FindOrgMembersOpts{ + opts := &models.FindOrgMembersOpts{ OrgID: ctx.Org.Organization.ID, PublicOnly: publicOnly, ListOptions: utils.GetListOptions(ctx), - }) + } + + count, err := models.CountOrgMembers(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + members, _, err := models.FindOrgMembers(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err) + ctx.InternalServerError(err) return } @@ -35,6 +41,7 @@ func listMembers(ctx *context.APIContext, publicOnly bool) { apiMembers[i] = convert.ToUser(member, ctx.User) } + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiMembers) } diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 5c16594f89d18..39ce896cd6707 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -6,7 +6,6 @@ package org import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -38,9 +37,8 @@ func listUserOrgs(ctx *context.APIContext, u *models.User) { apiOrgs[i] = convert.ToOrganization(orgs[i]) } - ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetLinkHeader(maxResults, listOptions.PageSize) + ctx.SetTotalCountHeader(int64(maxResults)) ctx.JSON(http.StatusOK, &apiOrgs) } @@ -145,8 +143,7 @@ func GetAll(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &orgs) } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 7998bb249f6e8..a84763d6f6410 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -6,7 +6,6 @@ package org import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -44,23 +43,27 @@ func ListTeams(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TeamList" - org := ctx.Org.Organization - if err := org.GetTeams(&models.SearchTeamOptions{ + teams, count, err := models.SearchTeam(&models.SearchTeamOptions{ ListOptions: utils.GetListOptions(ctx), - }); err != nil { - ctx.Error(http.StatusInternalServerError, "GetTeams", err) + OrgID: ctx.Org.Organization.ID, + }) + + if err != nil { + ctx.Error(http.StatusInternalServerError, "LoadTeams", err) return } - apiTeams := make([]*api.Team, len(org.Teams)) - for i := range org.Teams { - if err := org.Teams[i].GetUnits(); err != nil { + apiTeams := make([]*api.Team, len(teams)) + for i := range teams { + if err := teams[i].GetUnits(); err != nil { ctx.Error(http.StatusInternalServerError, "GetUnits", err) return } - apiTeams[i] = convert.ToTeam(org.Teams[i]) + apiTeams[i] = convert.ToTeam(teams[i]) } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiTeams) } @@ -84,7 +87,10 @@ func ListUserTeams(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TeamList" - teams, err := models.GetUserTeams(ctx.User.ID, utils.GetListOptions(ctx)) + teams, count, err := models.SearchTeam(&models.SearchTeamOptions{ + ListOptions: utils.GetListOptions(ctx), + UserID: ctx.User.ID, + }) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserTeams", err) return @@ -106,6 +112,8 @@ func ListUserTeams(ctx *context.APIContext) { apiTeams[i] = convert.ToTeam(teams[i]) apiTeams[i].Organization = apiOrg } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiTeams) } @@ -327,17 +335,19 @@ func GetTeamMembers(ctx *context.APIContext) { ctx.NotFound() return } - team := ctx.Org.Team - if err := team.GetMembers(&models.SearchMembersOptions{ + + if err := ctx.Org.Team.GetMembers(&models.SearchMembersOptions{ ListOptions: utils.GetListOptions(ctx), }); err != nil { ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err) return } - members := make([]*api.User, len(team.Members)) - for i, member := range team.Members { + members := make([]*api.User, len(ctx.Org.Team.Members)) + for i, member := range ctx.Org.Team.Members { members[i] = convert.ToUser(member, ctx.User) } + + ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers)) ctx.JSON(http.StatusOK, members) } @@ -687,8 +697,7 @@ func SearchTeam(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, map[string]interface{}{ "ok": true, "data": apiTeams, diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 85c1681dfec1b..8653b0bc809aa 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -282,9 +282,8 @@ func ListBranches(ctx *context.APIContext) { } } - ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", totalNumOfBranches)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) + ctx.SetTotalCountHeader(int64(totalNumOfBranches)) ctx.JSON(http.StatusOK, &apiBranches) } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 078af1f6ff8e9..d636220f62cf9 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -47,15 +47,24 @@ func ListCollaborators(ctx *context.APIContext) { // "200": // "$ref": "#/responses/UserList" + count, err := ctx.Repo.Repository.CountCollaborators() + if err != nil { + ctx.InternalServerError(err) + return + } + collaborators, err := ctx.Repo.Repository.GetCollaborators(utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) return } + users := make([]*api.User, len(collaborators)) for i, collaborator := range collaborators { users[i] = convert.ToUser(collaborator.User, ctx.User) } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, users) } diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index f2fd901efa950..975b9cab2a9ce 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -200,16 +200,16 @@ func GetAllCommits(ctx *context.APIContext) { } } + ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize) + ctx.SetTotalCountHeader(commitsCountTotal) + // kept for backwards compatibility ctx.Header().Set("X-Page", strconv.Itoa(listOptions.Page)) ctx.Header().Set("X-PerPage", strconv.Itoa(listOptions.PageSize)) ctx.Header().Set("X-Total", strconv.FormatInt(commitsCountTotal, 10)) ctx.Header().Set("X-PageCount", strconv.Itoa(pageCount)) ctx.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < pageCount)) - - ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", commitsCountTotal)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, X-PerPage, X-Total, X-PageCount, X-HasMore, Link") + ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-Total", "X-PageCount", "X-HasMore") ctx.JSON(http.StatusOK, &apiCommits) } diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 55fcefaccc382..a3f9aa14f997b 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -62,6 +62,8 @@ func ListForks(ctx *context.APIContext) { } apiForks[i] = convert.ToRepo(fork, access) } + + ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumForks)) ctx.JSON(http.StatusOK, apiForks) } diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go index da0a2c501c8a1..74860fd72f2c4 100644 --- a/routers/api/v1/repo/hook.go +++ b/routers/api/v1/repo/hook.go @@ -48,9 +48,20 @@ func ListHooks(ctx *context.APIContext) { // "200": // "$ref": "#/responses/HookList" - hooks, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID, utils.GetListOptions(ctx)) + opts := &models.ListWebhookOptions{ + ListOptions: utils.GetListOptions(ctx), + RepoID: ctx.Repo.Repository.ID, + } + + count, err := models.CountWebhooksByOpts(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetWebhooksByRepoID", err) + ctx.InternalServerError(err) + return + } + + hooks, err := models.ListWebhooksByOpts(opts) + if err != nil { + ctx.InternalServerError(err) return } @@ -58,6 +69,8 @@ func ListHooks(ctx *context.APIContext) { for i := range hooks { apiHooks[i] = convert.ToHook(ctx.Repo.RepoLink, hooks[i]) } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiHooks) } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 7395d4fdd4ce5..ff003b840bf68 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -232,8 +232,7 @@ func SearchIssues(ctx *context.APIContext) { } ctx.SetLinkHeader(int(filteredCount), setting.UI.IssuePagingNum) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", filteredCount)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(filteredCount) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) } @@ -442,8 +441,7 @@ func ListIssues(ctx *context.APIContext) { } ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", filteredCount)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(filteredCount) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) } diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index d62ca813149a3..13e7de46b1f12 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -68,17 +68,25 @@ func ListIssueComments(ctx *context.APIContext) { } issue.Repo = ctx.Repo.Repository - comments, err := models.FindComments(models.FindCommentsOptions{ + opts := &models.FindCommentsOptions{ IssueID: issue.ID, Since: since, Before: before, Type: models.CommentTypeComment, - }) + } + + comments, err := models.FindComments(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "FindComments", err) return } + totalCount, err := models.CountComments(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + if err := models.CommentList(comments).LoadPosters(); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return @@ -89,6 +97,8 @@ func ListIssueComments(ctx *context.APIContext) { comment.Issue = issue apiComments[i] = convert.ToComment(comments[i]) } + + ctx.SetTotalCountHeader(totalCount) ctx.JSON(http.StatusOK, &apiComments) } @@ -138,18 +148,26 @@ func ListRepoIssueComments(ctx *context.APIContext) { return } - comments, err := models.FindComments(models.FindCommentsOptions{ + opts := &models.FindCommentsOptions{ ListOptions: utils.GetListOptions(ctx), RepoID: ctx.Repo.Repository.ID, Type: models.CommentTypeComment, Since: since, Before: before, - }) + } + + comments, err := models.FindComments(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "FindComments", err) return } + totalCount, err := models.CountComments(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + if err = models.CommentList(comments).LoadPosters(); err != nil { ctx.Error(http.StatusInternalServerError, "LoadPosters", err) return @@ -171,6 +189,8 @@ func ListRepoIssueComments(ctx *context.APIContext) { for i := range comments { apiComments[i] = convert.ToComment(comments[i]) } + + ctx.SetTotalCountHeader(totalCount) ctx.JSON(http.StatusOK, &apiComments) } diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go index a4a2261b9a109..82a9ffe10bb73 100644 --- a/routers/api/v1/repo/issue_stopwatch.go +++ b/routers/api/v1/repo/issue_stopwatch.go @@ -225,11 +225,18 @@ func GetStopwatches(ctx *context.APIContext) { return } + count, err := models.CountUserStopwatches(ctx.User.ID) + if err != nil { + ctx.InternalServerError(err) + return + } + apiSWs, err := convert.ToStopWatches(sws) if err != nil { ctx.Error(http.StatusInternalServerError, "APIFormat", err) return } + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiSWs) } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index b27b746eb23b0..e9d8fbab2666c 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -83,7 +83,7 @@ func ListTrackedTimes(ctx *context.APIContext) { return } - opts := models.FindTrackedTimesOptions{ + opts := &models.FindTrackedTimesOptions{ ListOptions: utils.GetListOptions(ctx), RepositoryID: ctx.Repo.Repository.ID, IssueID: issue.ID, @@ -119,6 +119,12 @@ func ListTrackedTimes(ctx *context.APIContext) { } } + count, err := models.CountTrackedTimes(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + trackedTimes, err := models.GetTrackedTimes(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err) @@ -128,6 +134,8 @@ func ListTrackedTimes(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(trackedTimes)) } @@ -423,7 +431,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { return } - opts := models.FindTrackedTimesOptions{ + opts := &models.FindTrackedTimesOptions{ UserID: user.ID, RepositoryID: ctx.Repo.Repository.ID, } @@ -493,7 +501,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { return } - opts := models.FindTrackedTimesOptions{ + opts := &models.FindTrackedTimesOptions{ ListOptions: utils.GetListOptions(ctx), RepositoryID: ctx.Repo.Repository.ID, } @@ -530,6 +538,12 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { } } + count, err := models.CountTrackedTimes(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + trackedTimes, err := models.GetTrackedTimes(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "GetTrackedTimes", err) @@ -539,6 +553,8 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(trackedTimes)) } @@ -573,7 +589,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TrackedTimeList" - opts := models.FindTrackedTimesOptions{ + opts := &models.FindTrackedTimesOptions{ ListOptions: utils.GetListOptions(ctx), UserID: ctx.User.ID, } @@ -584,6 +600,12 @@ func ListMyTrackedTimes(ctx *context.APIContext) { return } + count, err := models.CountTrackedTimes(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + trackedTimes, err := models.GetTrackedTimes(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "GetTrackedTimesByUser", err) @@ -595,5 +617,6 @@ func ListMyTrackedTimes(ctx *context.APIContext) { return } + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, convert.ToTrackedTimeList(trackedTimes)) } diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go index 903cef7104ec8..98ee2b4de5cfd 100644 --- a/routers/api/v1/repo/key.go +++ b/routers/api/v1/repo/key.go @@ -75,26 +75,29 @@ func ListDeployKeys(ctx *context.APIContext) { // "200": // "$ref": "#/responses/DeployKeyList" - var keys []*models.DeployKey - var err error + opts := &models.ListDeployKeysOptions{ + ListOptions: utils.GetListOptions(ctx), + RepoID: ctx.Repo.Repository.ID, + KeyID: ctx.FormInt64("key_id"), + Fingerprint: ctx.FormString("fingerprint"), + } - fingerprint := ctx.FormString("fingerprint") - keyID := ctx.FormInt64("key_id") - if fingerprint != "" || keyID != 0 { - keys, err = models.SearchDeployKeys(ctx.Repo.Repository.ID, keyID, fingerprint) - } else { - keys, err = models.ListDeployKeys(ctx.Repo.Repository.ID, utils.GetListOptions(ctx)) + keys, err := models.ListDeployKeys(opts) + if err != nil { + ctx.InternalServerError(err) + return } + count, err := models.CountDeployKeys(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "ListDeployKeys", err) + ctx.InternalServerError(err) return } apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) apiKeys := make([]*api.DeployKey, len(keys)) for i := range keys { - if err = keys[i].GetContent(); err != nil { + if err := keys[i].GetContent(); err != nil { ctx.Error(http.StatusInternalServerError, "GetContent", err) return } @@ -104,6 +107,7 @@ func ListDeployKeys(ctx *context.APIContext) { } } + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiKeys) } diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index ca0d8392b8cba..1de5705aa28e4 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -55,6 +55,13 @@ func ListLabels(ctx *context.APIContext) { return } + count, err := models.CountLabelsByRepoID(ctx.Repo.Repository.ID) + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, convert.ToLabelList(labels)) } diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index 07b3897bc16c2..be1da18c5d12c 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -57,7 +57,7 @@ func ListMilestones(ctx *context.APIContext) { // "200": // "$ref": "#/responses/MilestoneList" - milestones, err := models.GetMilestones(models.GetMilestonesOption{ + milestones, total, err := models.GetMilestones(models.GetMilestonesOption{ ListOptions: utils.GetListOptions(ctx), RepoID: ctx.Repo.Repository.ID, State: api.StateType(ctx.FormString("state")), @@ -72,6 +72,8 @@ func ListMilestones(ctx *context.APIContext) { for i := range milestones { apiMilestones[i] = convert.ToAPIMilestone(milestones[i]) } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &apiMilestones) } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 9be6228bfdf8f..0a903101c7536 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -119,8 +119,7 @@ func ListPullRequests(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &apiPrs) } @@ -1232,13 +1231,14 @@ func GetPullRequestCommits(ctx *context.APIContext) { apiCommits = append(apiCommits, apiCommit) } - ctx.SetLinkHeader(int(totalNumberOfCommits), listOptions.PageSize) + ctx.SetLinkHeader(totalNumberOfCommits, listOptions.PageSize) + ctx.SetTotalCountHeader(int64(totalNumberOfCommits)) ctx.Header().Set("X-Page", strconv.Itoa(listOptions.Page)) ctx.Header().Set("X-PerPage", strconv.Itoa(listOptions.PageSize)) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", totalNumberOfCommits)) ctx.Header().Set("X-PageCount", strconv.Itoa(totalNumberOfPages)) ctx.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, X-PerPage, X-Total, X-PageCount, X-HasMore, Link") + ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore") + ctx.JSON(http.StatusOK, &apiCommits) } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 323904f45c0e3..55b5178305edf 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -78,14 +78,21 @@ func ListPullReviews(ctx *context.APIContext) { return } - allReviews, err := models.FindReviews(models.FindReviewOptions{ + opts := models.FindReviewOptions{ ListOptions: utils.GetListOptions(ctx), Type: models.ReviewTypeUnknown, IssueID: pr.IssueID, - }) + } + + allReviews, err := models.FindReviews(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + count, err := models.CountReviews(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "FindReviews", err) + ctx.InternalServerError(err) return } @@ -95,6 +102,7 @@ func ListPullReviews(ctx *context.APIContext) { return } + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiReviews) } diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 97b90079f264d..6438c6e9b4ff2 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -5,7 +5,6 @@ package repo import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -142,8 +141,7 @@ func ListReleases(ctx *context.APIContext) { } ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprint(filteredCount)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(filteredCount) ctx.JSON(http.StatusOK, rels) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index d222c9b080547..0f85d917d19df 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -230,8 +230,7 @@ func Search(ctx *context.APIContext) { } ctx.SetLinkHeader(int(count), opts.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, api.SearchResults{ OK: true, Data: results, diff --git a/routers/api/v1/repo/star.go b/routers/api/v1/repo/star.go index 3af0a4ac125a3..5fa42c3244e42 100644 --- a/routers/api/v1/repo/star.go +++ b/routers/api/v1/repo/star.go @@ -52,5 +52,7 @@ func ListStargazers(ctx *context.APIContext) { for i, stargazer := range stargazers { users[i] = convert.ToUser(stargazer, ctx.User) } + + ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumStars)) ctx.JSON(http.StatusOK, users) } diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 841f60bb565c6..b884432f73b86 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -204,8 +204,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, apiStatuses) } @@ -267,5 +266,6 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) { combiStatus := convert.ToCombinedStatus(statuses, convert.ToRepo(repo, ctx.Repo.AccessMode)) + // TODO: ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, combiStatus) } diff --git a/routers/api/v1/repo/subscriber.go b/routers/api/v1/repo/subscriber.go index 37bf3c29d466e..dae92969ba60a 100644 --- a/routers/api/v1/repo/subscriber.go +++ b/routers/api/v1/repo/subscriber.go @@ -52,5 +52,7 @@ func ListSubscribers(ctx *context.APIContext) { for i, subscriber := range subscribers { users[i] = convert.ToUser(subscriber, ctx.User) } + + ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumWatches)) ctx.JSON(http.StatusOK, users) } diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index c95fb63f859ee..8d42e63a489f0 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -50,7 +50,7 @@ func ListTags(ctx *context.APIContext) { listOpts := utils.GetListOptions(ctx) - tags, err := ctx.Repo.GitRepo.GetTagInfos(listOpts.Page, listOpts.PageSize) + tags, total, err := ctx.Repo.GitRepo.GetTagInfos(listOpts.Page, listOpts.PageSize) if err != nil { ctx.Error(http.StatusInternalServerError, "GetTags", err) return @@ -61,6 +61,7 @@ func ListTags(ctx *context.APIContext) { apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i]) } + ctx.SetTotalCountHeader(int64(total)) ctx.JSON(http.StatusOK, &apiTags) } diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go index a7c52e0bccbd7..fc277cb3fe528 100644 --- a/routers/api/v1/repo/topic.go +++ b/routers/api/v1/repo/topic.go @@ -47,12 +47,13 @@ func ListTopics(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TopicNames" - topics, err := models.FindTopics(&models.FindTopicOptions{ + opts := &models.FindTopicOptions{ ListOptions: utils.GetListOptions(ctx), RepoID: ctx.Repo.Repository.ID, - }) + } + + topics, total, err := models.FindTopics(opts) if err != nil { - log.Error("ListTopics failed: %v", err) ctx.InternalServerError(err) return } @@ -61,6 +62,8 @@ func ListTopics(ctx *context.APIContext) { for i, topic := range topics { topicNames[i] = topic.Name } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, map[string]interface{}{ "topics": topicNames, }) @@ -164,15 +167,15 @@ func AddTopic(ctx *context.APIContext) { } // Prevent adding more topics than allowed to repo - topics, err := models.FindTopics(&models.FindTopicOptions{ + count, err := models.CountTopics(&models.FindTopicOptions{ RepoID: ctx.Repo.Repository.ID, }) if err != nil { - log.Error("AddTopic failed: %v", err) + log.Error("CountTopics failed: %v", err) ctx.InternalServerError(err) return } - if len(topics) >= 25 { + if count >= 25 { ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ "message": "Exceeding maximum allowed topics per repo.", }) @@ -269,21 +272,13 @@ func TopicSearch(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" - if ctx.User == nil { - ctx.Error(http.StatusForbidden, "UserIsNil", "Only owners could change the topics.") - return + opts := &models.FindTopicOptions{ + Keyword: ctx.FormString("q"), + ListOptions: utils.GetListOptions(ctx), } - kw := ctx.FormString("q") - - listOptions := utils.GetListOptions(ctx) - - topics, err := models.FindTopics(&models.FindTopicOptions{ - Keyword: kw, - ListOptions: listOptions, - }) + topics, total, err := models.FindTopics(opts) if err != nil { - log.Error("SearchTopics failed: %v", err) ctx.InternalServerError(err) return } @@ -292,6 +287,8 @@ func TopicSearch(ctx *context.APIContext) { for i, topic := range topics { topicResponses[i] = convert.ToTopicResponse(topic) } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, map[string]interface{}{ "topics": topicResponses, }) diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index afd209f2f0791..f0f7cb4159b65 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -44,9 +44,16 @@ func ListAccessTokens(ctx *context.APIContext) { // "200": // "$ref": "#/responses/AccessTokenList" - tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID, ListOptions: utils.GetListOptions(ctx)}) + opts := models.ListAccessTokensOptions{UserID: ctx.User.ID, ListOptions: utils.GetListOptions(ctx)} + + count, err := models.CountAccessTokens(opts) + if err != nil { + ctx.InternalServerError(err) + return + } + tokens, err := models.ListAccessTokens(opts) if err != nil { - ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) + ctx.InternalServerError(err) return } @@ -58,6 +65,8 @@ func ListAccessTokens(ctx *context.APIContext) { TokenLastEight: tokens[i].TokenLastEight, } } + + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiTokens) } @@ -242,7 +251,7 @@ func ListOauth2Applications(ctx *context.APIContext) { // "200": // "$ref": "#/responses/OAuth2ApplicationList" - apps, err := models.ListOAuth2Applications(ctx.User.ID, utils.GetListOptions(ctx)) + apps, total, err := models.ListOAuth2Applications(ctx.User.ID, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "ListOAuth2Applications", err) return @@ -253,6 +262,8 @@ func ListOauth2Applications(ctx *context.APIContext) { apiApps[i] = convert.ToOAuth2Application(apps[i]) apiApps[i].ClientSecret = "" // Hide secret on application list } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &apiApps) } diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 4d316425cd096..e273ac6a02071 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -29,6 +29,8 @@ func listUserFollowers(ctx *context.APIContext, u *models.User) { ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) return } + + ctx.SetTotalCountHeader(int64(u.NumFollowers)) responseAPIUsers(ctx, users) } @@ -93,6 +95,8 @@ func listUserFollowing(ctx *context.APIContext, u *models.User) { ctx.Error(http.StatusInternalServerError, "GetFollowing", err) return } + + ctx.SetTotalCountHeader(int64(u.NumFollowing)) responseAPIUsers(ctx, users) } diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index ec03e305ba1b2..f32d60d03816a 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -28,6 +28,13 @@ func listGPGKeys(ctx *context.APIContext, uid int64, listOptions models.ListOpti apiKeys[i] = convert.ToGPGKey(keys[i]) } + total, err := models.CountUserGPGKeys(uid) + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &apiKeys) } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 04252524b729f..36b1c17b895f8 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -47,6 +47,7 @@ func composePublicKeysAPILink() string { func listPublicKeys(ctx *context.APIContext, user *models.User) { var keys []*models.PublicKey var err error + var count int fingerprint := ctx.FormString("fingerprint") username := ctx.Params("username") @@ -60,7 +61,15 @@ func listPublicKeys(ctx *context.APIContext, user *models.User) { // Unrestricted keys, err = models.SearchPublicKey(0, fingerprint) } + count = len(keys) } else { + total, err2 := models.CountPublicKeys(user.ID) + if err2 != nil { + ctx.InternalServerError(err) + return + } + count = int(total) + // Use ListPublicKeys keys, err = models.ListPublicKeys(user.ID, utils.GetListOptions(ctx)) } @@ -79,6 +88,7 @@ func listPublicKeys(ctx *context.APIContext, user *models.User) { } } + ctx.SetTotalCountHeader(int64(count)) ctx.JSON(http.StatusOK, &apiKeys) } diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go index 0331abef10046..5116c17ab2afe 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -6,7 +6,6 @@ package user import ( "net/http" - "strconv" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -43,8 +42,7 @@ func listUserRepos(ctx *context.APIContext, u *models.User, private bool) { } ctx.SetLinkHeader(int(count), opts.PageSize) - ctx.Header().Set("X-Total-Count", strconv.FormatInt(count, 10)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &apiRepos) } @@ -130,8 +128,7 @@ func ListMyRepos(ctx *context.APIContext) { } ctx.SetLinkHeader(int(count), opts.ListOptions.PageSize) - ctx.Header().Set("X-Total-Count", strconv.FormatInt(count, 10)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, &results) } diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go index 937dcb477f27f..8ee167685639c 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -92,6 +92,8 @@ func GetMyStarredRepos(ctx *context.APIContext) { if err != nil { ctx.Error(http.StatusInternalServerError, "getStarredRepos", err) } + + ctx.SetTotalCountHeader(int64(ctx.User.NumStars)) ctx.JSON(http.StatusOK, &repos) } diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index e00c8d476dd16..a5e70de54830f 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -6,7 +6,6 @@ package user import ( - "fmt" "net/http" "code.gitea.io/gitea/models" @@ -73,8 +72,7 @@ func Search(ctx *context.APIContext) { } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) - ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults)) - ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link") + ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, map[string]interface{}{ "ok": true, diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index ab656f3229ffb..f32ce7359864a 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -14,23 +14,22 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" ) -// getWatchedRepos returns the repos that the user with the specified userID is -// watching -func getWatchedRepos(user *models.User, private bool, listOptions models.ListOptions) ([]*api.Repository, error) { - watchedRepos, err := models.GetWatchedRepos(user.ID, private, listOptions) +// getWatchedRepos returns the repos that the user with the specified userID is watching +func getWatchedRepos(user *models.User, private bool, listOptions models.ListOptions) ([]*api.Repository, int64, error) { + watchedRepos, total, err := models.GetWatchedRepos(user.ID, private, listOptions) if err != nil { - return nil, err + return nil, 0, err } repos := make([]*api.Repository, len(watchedRepos)) for i, watched := range watchedRepos { access, err := models.AccessLevel(user, watched) if err != nil { - return nil, err + return nil, 0, err } repos[i] = convert.ToRepo(watched, access) } - return repos, nil + return repos, total, nil } // GetWatchedRepos returns the repos that the user specified in ctx is watching @@ -60,10 +59,12 @@ func GetWatchedRepos(ctx *context.APIContext) { user := GetUserByParams(ctx) private := user.ID == ctx.User.ID - repos, err := getWatchedRepos(user, private, utils.GetListOptions(ctx)) + repos, total, err := getWatchedRepos(user, private, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err) } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &repos) } @@ -87,10 +88,12 @@ func GetMyWatchedRepos(ctx *context.APIContext) { // "200": // "$ref": "#/responses/RepositoryList" - repos, err := getWatchedRepos(ctx.User, true, utils.GetListOptions(ctx)) + repos, total, err := getWatchedRepos(ctx.User, true, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "getWatchedRepos", err) } + + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, &repos) } diff --git a/routers/web/org/home.go b/routers/web/org/home.go index da53053c7fc0b..0c1f381d30072 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -107,7 +107,7 @@ func Home(ctx *context.Context) { return } - var opts = models.FindOrgMembersOpts{ + var opts = &models.FindOrgMembersOpts{ OrgID: org.ID, PublicOnly: true, ListOptions: models.ListOptions{Page: 1, PageSize: 25}, @@ -122,7 +122,7 @@ func Home(ctx *context.Context) { opts.PublicOnly = !isMember && !ctx.User.IsAdmin } - members, _, err := models.FindOrgMembers(&opts) + members, _, err := models.FindOrgMembers(opts) if err != nil { ctx.ServerError("FindOrgMembers", err) return diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 2989637a6127c..84aaa28f60530 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -31,7 +31,7 @@ func Members(ctx *context.Context) { page = 1 } - var opts = models.FindOrgMembersOpts{ + var opts = &models.FindOrgMembersOpts{ OrgID: org.ID, PublicOnly: true, } @@ -54,7 +54,7 @@ func Members(ctx *context.Context) { pager := context.NewPagination(int(total), setting.UI.MembersPagingNum, page, 5) opts.ListOptions.Page = page opts.ListOptions.PageSize = setting.UI.MembersPagingNum - members, membersIsPublic, err := models.FindOrgMembers(&opts) + members, membersIsPublic, err := models.FindOrgMembers(opts) if err != nil { ctx.ServerError("GetMembers", err) return diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index b21b94d64d67c..7e6fc5bf4cd95 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -186,7 +186,7 @@ func Webhooks(ctx *context.Context) { ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks" ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc") - ws, err := models.GetWebhooksByOrgID(ctx.Org.Organization.ID, models.ListOptions{}) + ws, err := models.ListWebhooksByOpts(&models.ListWebhookOptions{OrgID: ctx.Org.Organization.ID}) if err != nil { ctx.ServerError("GetWebhooksByOrgId", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 6050bb5c23d48..ff83be441026a 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -378,7 +378,7 @@ func Issues(ctx *context.Context) { var err error // Get milestones - ctx.Data["Milestones"], err = models.GetMilestones(models.GetMilestonesOption{ + ctx.Data["Milestones"], _, err = models.GetMilestones(models.GetMilestonesOption{ RepoID: ctx.Repo.Repository.ID, State: api.StateType(ctx.FormString("state")), }) @@ -395,7 +395,7 @@ func Issues(ctx *context.Context) { // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) { var err error - ctx.Data["OpenMilestones"], err = models.GetMilestones(models.GetMilestonesOption{ + ctx.Data["OpenMilestones"], _, err = models.GetMilestones(models.GetMilestonesOption{ RepoID: repo.ID, State: api.StateOpen, }) @@ -403,7 +403,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos ctx.ServerError("GetMilestones", err) return } - ctx.Data["ClosedMilestones"], err = models.GetMilestones(models.GetMilestonesOption{ + ctx.Data["ClosedMilestones"], _, err = models.GetMilestones(models.GetMilestonesOption{ RepoID: repo.ID, State: api.StateClosed, }) @@ -1265,7 +1265,7 @@ func ViewIssue(ctx *context.Context) { } else { ctx.Data["CanUseTimetracker"] = false } - if ctx.Data["WorkingUsers"], err = models.TotalTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { + if ctx.Data["WorkingUsers"], err = models.TotalTimes(&models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { ctx.ServerError("TotalTimes", err) return } @@ -2584,8 +2584,8 @@ func handleTeamMentions(ctx *context.Context) { } if isAdmin { - if err := ctx.Repo.Owner.GetTeams(&models.SearchTeamOptions{}); err != nil { - ctx.ServerError("GetTeams", err) + if err := ctx.Repo.Owner.LoadTeams(); err != nil { + ctx.ServerError("LoadTeams", err) return } } else { diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index ca77e0976e575..3ba27dd19afb2 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -53,17 +53,12 @@ func Milestones(ctx *context.Context) { page = 1 } - var total int - var state structs.StateType - if !isShowClosed { - total = int(stats.OpenCount) - state = structs.StateOpen - } else { - total = int(stats.ClosedCount) + state := structs.StateOpen + if isShowClosed { state = structs.StateClosed } - miles, err := models.GetMilestones(models.GetMilestonesOption{ + miles, total, err := models.GetMilestones(models.GetMilestonesOption{ ListOptions: models.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, @@ -106,7 +101,7 @@ func Milestones(ctx *context.Context) { ctx.Data["Keyword"] = keyword ctx.Data["IsShowClosed"] = isShowClosed - pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) + pager := context.NewPagination(int(total), setting.UI.IssuePagingNum, page, 5) pager.AddParam(ctx, "state", "State") pager.AddParam(ctx, "q", "Keyword") ctx.Data["Page"] = pager diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 2f1561073770a..20253a7cae0a4 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -1002,7 +1002,7 @@ func DeployKeys(ctx *context.Context) { ctx.Data["PageIsSettingsKeys"] = true ctx.Data["DisableSSH"] = setting.SSH.Disabled - keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID, models.ListOptions{}) + keys, err := models.ListDeployKeys(&models.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) if err != nil { ctx.ServerError("ListDeployKeys", err) return @@ -1018,7 +1018,7 @@ func DeployKeysPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") ctx.Data["PageIsSettingsKeys"] = true - keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID, models.ListOptions{}) + keys, err := models.ListDeployKeys(&models.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) if err != nil { ctx.ServerError("ListDeployKeys", err) return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6c8645226f643..2c703fc1aff40 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -674,7 +674,7 @@ func renderLanguageStats(ctx *context.Context) { } func renderRepoTopics(ctx *context.Context) { - topics, err := models.FindTopics(&models.FindTopicOptions{ + topics, _, err := models.FindTopics(&models.FindTopicOptions{ RepoID: ctx.Repo.Repository.ID, }) if err != nil { diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 79f47470f25a5..0ceb9bc1dc0eb 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -41,7 +41,7 @@ func Webhooks(ctx *context.Context) { ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks" ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://docs.gitea.io/en-us/webhooks/") - ws, err := models.GetWebhooksByRepoID(ctx.Repo.Repository.ID, models.ListOptions{}) + ws, err := models.ListWebhooksByOpts(&models.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID}) if err != nil { ctx.ServerError("GetWebhooksByRepoID", err) return diff --git a/services/pull/review.go b/services/pull/review.go index b07e21fad9774..3aa45706201a4 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -132,7 +132,7 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models head := pr.GetGitRefName() if line > 0 { if reviewID != 0 { - first, err := models.FindComments(models.FindCommentsOptions{ + first, err := models.FindComments(&models.FindCommentsOptions{ ReviewID: reviewID, Line: line, TreePath: treePath, diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go index 46002c895c3b8..00b2ef05b87c3 100644 --- a/services/webhook/webhook.go +++ b/services/webhook/webhook.go @@ -14,6 +14,8 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" + "github.com/gobwas/glob" ) @@ -187,7 +189,10 @@ func PrepareWebhooks(repo *models.Repository, event models.HookEventType, p api. } func prepareWebhooks(repo *models.Repository, event models.HookEventType, p api.Payloader) error { - ws, err := models.GetActiveWebhooksByRepoID(repo.ID) + ws, err := models.ListWebhooksByOpts(&models.ListWebhookOptions{ + RepoID: repo.ID, + IsActive: util.OptionalBoolTrue, + }) if err != nil { return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err) } @@ -195,7 +200,10 @@ func prepareWebhooks(repo *models.Repository, event models.HookEventType, p api. // check if repo belongs to org and append additional webhooks if repo.MustOwner().IsOrganization() { // get hooks for org - orgHooks, err := models.GetActiveWebhooksByOrgID(repo.OwnerID) + orgHooks, err := models.ListWebhooksByOpts(&models.ListWebhookOptions{ + OrgID: repo.OwnerID, + IsActive: util.OptionalBoolTrue, + }) if err != nil { return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err) }