diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb515f26854d..026536868d50 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 1a0ee99a43ad..9731c5a93b6c 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 5e42bc64bf92..bab17a68be5a 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 d35b900cfdf9..5d0b0b06cfe1 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 c27582024280..1ee3ead21709 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 74ffb82a545b..1072813b1b71 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 225dfee20f09..8b83612c2a4f 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 755041efd7ed..06b74dff85d7 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 d1ff4692366d..5d50203b5a5b 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 5e934cde0a02..e6976a46c71e 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 5406129884fb..1472c951eb40 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 8cdad94fd4ae..9322e26bf239 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 43f2e13784b7..e7769b41dd41 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 e30de6cb69d4..9de1dd699d6f 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 c4c7728ad9f6..30bb7596a8a1 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 2aa9fbd3d921..9cf236f0cb42 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 58fb26b1bb51..3de6bd14b273 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 6226772b9a82..c380c8cd8ef5 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 38e36cf82ccf..9dc26fd814a6 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 e494e502dd31..7ba9d8ee88b8 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 93827f6a8842..f2e3f5b58bc3 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 b9488f5e2e3e..a8b715bbcfe6 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 1cf73bc55e6a..66682903f1e9 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 d7ef0a8ca6b3..09b6290293d5 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 acb54d970fdc..1ffff8feb647 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 6cda4f1658fb..a2f4c610e404 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 3189bcf456a6..e7d486b9f56c 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 357afe44a7c0..8e1f91d43f1c 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 19c572fefebf..7fc34f5bef19 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 25232eb981c2..9386a71e35d2 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 c68417a2c326..1af9e545adee 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 138ba26bde09..79ce70a0de01 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 84520666c0b2..625d643856c5 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 b543c8bac826..47ea8acfe05f 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 527ccfbcaa8d..fd67212a10c4 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 d91c3ca97973..44d7a048bc76 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 6feae8d91325..cfab9edd8f42 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 5f36d545846d..2f854ba368a3 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 062cee628320..9c1b9fc0f864 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 2531346fcb0c..970fad83880c 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 1356276f07f4..f1a766d544d7 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 6bc9b849b1fc..e5a75da759ea 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 af4507f896a8..1a36642b6a26 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 c2178b4deeaf..e4626cb71984 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 ed827c48d43f..c5982300eba2 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 c70252158e9e..09acb0bf0442 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 09abad25572d..97940d59251d 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 5c16594f89d1..39ce896cd670 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 7998bb249f6e..a84763d6f641 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 85c1681dfec1..8653b0bc809a 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 078af1f6ff8e..d636220f62cf 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 f2fd901efa95..975b9cab2a9c 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 55fcefaccc38..a3f9aa14f997 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 da0a2c501c8a..74860fd72f2c 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 7395d4fdd4ce..ff003b840bf6 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 d62ca813149a..13e7de46b1f1 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 a4a2261b9a10..82a9ffe10bb7 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 b27b746eb23b..e9d8fbab2666 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 903cef7104ec..98ee2b4de5cf 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 ca0d8392b8cb..1de5705aa28e 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 07b3897bc16c..be1da18c5d12 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 9be6228bfdf8..0a903101c753 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 323904f45c0e..55b5178305ed 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 97b90079f264..6438c6e9b4ff 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 d222c9b08054..0f85d917d19d 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 3af0a4ac125a..5fa42c3244e4 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 841f60bb565c..b884432f73b8 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 37bf3c29d466..dae92969ba60 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 c95fb63f859e..8d42e63a489f 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 a7c52e0bccbd..fc277cb3fe52 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 afd209f2f079..f0f7cb4159b6 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 4d316425cd09..e273ac6a0207 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 ec03e305ba1b..f32d60d03816 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 04252524b729..36b1c17b895f 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 0331abef1004..5116c17ab2af 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 937dcb477f27..8ee167685639 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 e00c8d476dd1..a5e70de54830 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 ab656f3229ff..f32ce7359864 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 da53053c7fc0..0c1f381d3007 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 2989637a6127..84aaa28f6053 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 b21b94d64d67..7e6fc5bf4cd9 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 6050bb5c23d4..ff83be441026 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 ca77e0976e57..3ba27dd19afb 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 2f1561073770..20253a7cae0a 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 6c8645226f64..2c703fc1aff4 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 79f47470f25a..0ceb9bc1dc0e 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 b07e21fad977..3aa45706201a 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 46002c895c3b..00b2ef05b87c 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) }