Skip to content

Commit 2289580

Browse files
authored
[API] generalize list header (#16551)
* Add info about list endpoints to CONTRIBUTING.md * Let all list endpoints return X-Total-Count header * Add TODOs for GetCombinedCommitStatusByRef * Fix models/issue_stopwatch.go * Rrefactor models.ListDeployKeys * Introduce helper func and use them for SetLinkHeader related func
1 parent ca13e1d commit 2289580

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+636
-328
lines changed

CONTRIBUTING.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ In general, HTTP methods are chosen as follows:
207207

208208
An endpoint which changes/edits an object expects all fields to be optional (except ones to identify the object, which are required).
209209

210+
### Endpoints returning lists should
211+
* support pagination (`page` & `limit` options in query)
212+
* set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
213+
210214

211215
## Developer Certificate of Origin (DCO)
212216

@@ -231,8 +235,8 @@ on, finishing, and issuing releases. The overall goal is to make a
231235
minor release every three or four months, which breaks down into two or three months of
232236
general development followed by one month of testing and polishing
233237
known as the release freeze. All the feature pull requests should be
234-
merged before feature freeze. And, during the frozen period, a corresponding
235-
release branch is open for fixes backported from main branch. Release candidates
238+
merged before feature freeze. And, during the frozen period, a corresponding
239+
release branch is open for fixes backported from main branch. Release candidates
236240
are made during this period for user testing to
237241
obtain a final version that is maintained in this branch. A release is
238242
maintained by issuing patch releases to only correct critical problems

integrations/api_issue_tracked_time_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestAPIGetTrackedTimes(t *testing.T) {
3030
resp := session.MakeRequest(t, req, http.StatusOK)
3131
var apiTimes api.TrackedTimeList
3232
DecodeJSON(t, resp, &apiTimes)
33-
expect, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue2.ID})
33+
expect, err := models.GetTrackedTimes(&models.FindTrackedTimesOptions{IssueID: issue2.ID})
3434
assert.NoError(t, err)
3535
assert.Len(t, apiTimes, 3)
3636

integrations/api_repo_topic_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package integrations
77
import (
88
"fmt"
99
"net/http"
10+
"net/url"
1011
"testing"
1112

1213
"code.gitea.io/gitea/models"
@@ -15,6 +16,38 @@ import (
1516
"github.com/stretchr/testify/assert"
1617
)
1718

19+
func TestAPITopicSearch(t *testing.T) {
20+
defer prepareTestEnv(t)()
21+
searchURL, _ := url.Parse("/api/v1/topics/search")
22+
var topics struct {
23+
TopicNames []*api.TopicResponse `json:"topics"`
24+
}
25+
26+
query := url.Values{"page": []string{"1"}, "limit": []string{"4"}}
27+
28+
searchURL.RawQuery = query.Encode()
29+
res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
30+
DecodeJSON(t, res, &topics)
31+
assert.Len(t, topics.TopicNames, 4)
32+
assert.EqualValues(t, "6", res.Header().Get("x-total-count"))
33+
34+
query.Add("q", "topic")
35+
searchURL.RawQuery = query.Encode()
36+
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
37+
DecodeJSON(t, res, &topics)
38+
assert.Len(t, topics.TopicNames, 2)
39+
40+
query.Set("q", "database")
41+
searchURL.RawQuery = query.Encode()
42+
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
43+
DecodeJSON(t, res, &topics)
44+
if assert.Len(t, topics.TopicNames, 1) {
45+
assert.EqualValues(t, 2, topics.TopicNames[0].ID)
46+
assert.EqualValues(t, "database", topics.TopicNames[0].Name)
47+
assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount)
48+
}
49+
}
50+
1851
func TestAPIRepoTopic(t *testing.T) {
1952
defer prepareTestEnv(t)()
2053
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of repo2

models/access.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
246246
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
247247
}
248248

249-
if err = repo.Owner.getTeams(e); err != nil {
249+
if err = repo.Owner.loadTeams(e); err != nil {
250250
return err
251251
}
252252

models/commit_status.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func getLatestCommitStatus(e Engine, repoID int64, sha string, listOptions ListO
159159
if len(ids) == 0 {
160160
return statuses, nil
161161
}
162-
return statuses, x.In("id", ids).Find(&statuses)
162+
return statuses, e.In("id", ids).Find(&statuses)
163163
}
164164

165165
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts

models/gpg_key.go

+5
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error
7171
return keys, sess.Find(&keys)
7272
}
7373

74+
// CountUserGPGKeys return number of gpg keys a user own
75+
func CountUserGPGKeys(userID int64) (int64, error) {
76+
return x.Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{})
77+
}
78+
7479
// GetGPGKeyByID returns public key by given ID.
7580
func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
7681
key := new(GPGKey)

models/issue.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func init() {
8989

9090
func (issue *Issue) loadTotalTimes(e Engine) (err error) {
9191
opts := FindTrackedTimesOptions{IssueID: issue.ID}
92-
issue.TotalTrackedTime, err = opts.ToSession(e).SumInt(&TrackedTime{}, "time")
92+
issue.TotalTrackedTime, err = opts.toSession(e).SumInt(&TrackedTime{}, "time")
9393
if err != nil {
9494
return err
9595
}
@@ -214,7 +214,7 @@ func (issue *Issue) loadCommentsByType(e Engine, tp CommentType) (err error) {
214214
if issue.Comments != nil {
215215
return nil
216216
}
217-
issue.Comments, err = findComments(e, FindCommentsOptions{
217+
issue.Comments, err = findComments(e, &FindCommentsOptions{
218218
IssueID: issue.ID,
219219
Type: tp,
220220
})

models/issue_comment.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ func (opts *FindCommentsOptions) toConds() builder.Cond {
999999
return cond
10001000
}
10011001

1002-
func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) {
1002+
func findComments(e Engine, opts *FindCommentsOptions) ([]*Comment, error) {
10031003
comments := make([]*Comment, 0, 10)
10041004
sess := e.Where(opts.toConds())
10051005
if opts.RepoID > 0 {
@@ -1019,10 +1019,19 @@ func findComments(e Engine, opts FindCommentsOptions) ([]*Comment, error) {
10191019
}
10201020

10211021
// FindComments returns all comments according options
1022-
func FindComments(opts FindCommentsOptions) ([]*Comment, error) {
1022+
func FindComments(opts *FindCommentsOptions) ([]*Comment, error) {
10231023
return findComments(x, opts)
10241024
}
10251025

1026+
// CountComments count all comments according options by ignoring pagination
1027+
func CountComments(opts *FindCommentsOptions) (int64, error) {
1028+
sess := x.Where(opts.toConds())
1029+
if opts.RepoID > 0 {
1030+
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
1031+
}
1032+
return sess.Count(&Comment{})
1033+
}
1034+
10261035
// UpdateComment updates information of comment.
10271036
func UpdateComment(c *Comment, doer *User) error {
10281037
sess := x.NewSession()

models/issue_label.go

+10
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,11 @@ func GetLabelsByRepoID(repoID int64, sortType string, listOptions ListOptions) (
444444
return getLabelsByRepoID(x, repoID, sortType, listOptions)
445445
}
446446

447+
// CountLabelsByRepoID count number of all labels that belong to given repository by ID.
448+
func CountLabelsByRepoID(repoID int64) (int64, error) {
449+
return x.Where("repo_id = ?", repoID).Count(&Label{})
450+
}
451+
447452
// ________
448453
// \_____ \_______ ____
449454
// / | \_ __ \/ ___\
@@ -556,6 +561,11 @@ func GetLabelsByOrgID(orgID int64, sortType string, listOptions ListOptions) ([]
556561
return getLabelsByOrgID(x, orgID, sortType, listOptions)
557562
}
558563

564+
// CountLabelsByOrgID count all labels that belong to given organization by ID.
565+
func CountLabelsByOrgID(orgID int64) (int64, error) {
566+
return x.Where("org_id = ?", orgID).Count(&Label{})
567+
}
568+
559569
// .___
560570
// | | ______ ________ __ ____
561571
// | |/ ___// ___/ | \_/ __ \

models/issue_milestone.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -380,24 +380,33 @@ type GetMilestonesOption struct {
380380
SortType string
381381
}
382382

383-
// GetMilestones returns milestones filtered by GetMilestonesOption's
384-
func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) {
385-
sess := x.Where("repo_id = ?", opts.RepoID)
383+
func (opts GetMilestonesOption) toCond() builder.Cond {
384+
cond := builder.NewCond()
385+
if opts.RepoID != 0 {
386+
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
387+
}
386388

387389
switch opts.State {
388390
case api.StateClosed:
389-
sess = sess.And("is_closed = ?", true)
391+
cond = cond.And(builder.Eq{"is_closed": true})
390392
case api.StateAll:
391393
break
392394
// api.StateOpen:
393395
default:
394-
sess = sess.And("is_closed = ?", false)
396+
cond = cond.And(builder.Eq{"is_closed": false})
395397
}
396398

397399
if len(opts.Name) != 0 {
398-
sess = sess.And(builder.Like{"name", opts.Name})
400+
cond = cond.And(builder.Like{"name", opts.Name})
399401
}
400402

403+
return cond
404+
}
405+
406+
// GetMilestones returns milestones filtered by GetMilestonesOption's
407+
func GetMilestones(opts GetMilestonesOption) (MilestoneList, int64, error) {
408+
sess := x.Where(opts.toCond())
409+
401410
if opts.Page != 0 {
402411
sess = opts.setSessionPagination(sess)
403412
}
@@ -420,7 +429,8 @@ func GetMilestones(opts GetMilestonesOption) (MilestoneList, error) {
420429
}
421430

422431
miles := make([]*Milestone, 0, opts.PageSize)
423-
return miles, sess.Find(&miles)
432+
total, err := sess.FindAndCount(&miles)
433+
return miles, total, err
424434
}
425435

426436
// SearchMilestones search milestones

models/issue_milestone_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
5050
assert.NoError(t, PrepareTestDatabase())
5151
test := func(repoID int64, state api.StateType) {
5252
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
53-
milestones, err := GetMilestones(GetMilestonesOption{
53+
milestones, _, err := GetMilestones(GetMilestonesOption{
5454
RepoID: repo.ID,
5555
State: state,
5656
})
@@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
8787
test(3, api.StateClosed)
8888
test(3, api.StateAll)
8989

90-
milestones, err := GetMilestones(GetMilestonesOption{
90+
milestones, _, err := GetMilestones(GetMilestonesOption{
9191
RepoID: NonexistentID,
9292
State: api.StateOpen,
9393
})
@@ -100,7 +100,7 @@ func TestGetMilestones(t *testing.T) {
100100
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
101101
test := func(sortType string, sortCond func(*Milestone) int) {
102102
for _, page := range []int{0, 1} {
103-
milestones, err := GetMilestones(GetMilestonesOption{
103+
milestones, _, err := GetMilestones(GetMilestonesOption{
104104
ListOptions: ListOptions{
105105
Page: page,
106106
PageSize: setting.UI.IssuePagingNum,
@@ -117,7 +117,7 @@ func TestGetMilestones(t *testing.T) {
117117
}
118118
assert.True(t, sort.IntsAreSorted(values))
119119

120-
milestones, err = GetMilestones(GetMilestonesOption{
120+
milestones, _, err = GetMilestones(GetMilestonesOption{
121121
ListOptions: ListOptions{
122122
Page: page,
123123
PageSize: setting.UI.IssuePagingNum,

models/issue_stopwatch.go

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ func GetUserStopwatches(userID int64, listOptions ListOptions) ([]*Stopwatch, er
5555
return sws, nil
5656
}
5757

58+
// CountUserStopwatches return count of all stopwatches of a user
59+
func CountUserStopwatches(userID int64) (int64, error) {
60+
return x.Where("user_id = ?", userID).Count(&Stopwatch{})
61+
}
62+
5863
// StopwatchExists returns true if the stopwatch exists
5964
func StopwatchExists(userID, issueID int64) bool {
6065
_, exists, _ := getStopwatch(x, userID, issueID)

models/issue_tracked_time.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ type FindTrackedTimesOptions struct {
7979
CreatedBeforeUnix int64
8080
}
8181

82-
// ToCond will convert each condition into a xorm-Cond
83-
func (opts *FindTrackedTimesOptions) ToCond() builder.Cond {
82+
// toCond will convert each condition into a xorm-Cond
83+
func (opts *FindTrackedTimesOptions) toCond() builder.Cond {
8484
cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false})
8585
if opts.IssueID != 0 {
8686
cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
@@ -103,14 +103,14 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond {
103103
return cond
104104
}
105105

106-
// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
107-
func (opts *FindTrackedTimesOptions) ToSession(e Engine) Engine {
106+
// toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required
107+
func (opts *FindTrackedTimesOptions) toSession(e Engine) Engine {
108108
sess := e
109109
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
110110
sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
111111
}
112112

113-
sess = sess.Where(opts.ToCond())
113+
sess = sess.Where(opts.toCond())
114114

115115
if opts.Page != 0 {
116116
sess = opts.setEnginePagination(sess)
@@ -119,18 +119,27 @@ func (opts *FindTrackedTimesOptions) ToSession(e Engine) Engine {
119119
return sess
120120
}
121121

122-
func getTrackedTimes(e Engine, options FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
123-
err = options.ToSession(e).Find(&trackedTimes)
122+
func getTrackedTimes(e Engine, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
123+
err = options.toSession(e).Find(&trackedTimes)
124124
return
125125
}
126126

127127
// GetTrackedTimes returns all tracked times that fit to the given options.
128-
func GetTrackedTimes(opts FindTrackedTimesOptions) (TrackedTimeList, error) {
128+
func GetTrackedTimes(opts *FindTrackedTimesOptions) (TrackedTimeList, error) {
129129
return getTrackedTimes(x, opts)
130130
}
131131

132+
// CountTrackedTimes returns count of tracked times that fit to the given options.
133+
func CountTrackedTimes(opts *FindTrackedTimesOptions) (int64, error) {
134+
sess := x.Where(opts.toCond())
135+
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
136+
sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id")
137+
}
138+
return sess.Count(&TrackedTime{})
139+
}
140+
132141
func getTrackedSeconds(e Engine, opts FindTrackedTimesOptions) (trackedSeconds int64, err error) {
133-
return opts.ToSession(e).SumInt(&TrackedTime{}, "time")
142+
return opts.toSession(e).SumInt(&TrackedTime{}, "time")
134143
}
135144

136145
// GetTrackedSeconds return sum of seconds
@@ -188,7 +197,7 @@ func addTime(e Engine, user *User, issue *Issue, amount int64, created time.Time
188197
}
189198

190199
// TotalTimes returns the spent time for each user by an issue
191-
func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) {
200+
func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) {
192201
trackedTimes, err := GetTrackedTimes(options)
193202
if err != nil {
194203
return nil, err
@@ -288,7 +297,7 @@ func deleteTimes(e Engine, opts FindTrackedTimesOptions) (removedTime int64, err
288297
return
289298
}
290299

291-
_, err = opts.ToSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true})
300+
_, err = opts.toSession(e).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true})
292301
return
293302
}
294303

0 commit comments

Comments
 (0)