Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize list header for API #16551

Merged
merged 65 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
041d023
Add info about list endpoints to CONTRIBUTING.md
6543 Jul 26, 2021
0b2f858
Let List Notifications return X-Total-Count header
6543 Jul 26, 2021
b1f875f
ListAccessTokens too
6543 Jul 26, 2021
c1f2c9c
ListCronTasks
6543 Jul 26, 2021
29657d9
ListForks
6543 Jul 26, 2021
28662da
ListStargazers
6543 Jul 26, 2021
cab3f66
Add only TODOs
6543 Jul 26, 2021
b933671
nit
6543 Jul 26, 2021
e22371a
models: webhook: introduce ListWebhooksByOpts
6543 Jul 26, 2021
6897dee
ListHooks
6543 Jul 26, 2021
5a9a23c
ListLabels
6543 Jul 26, 2021
0b89620
Org#ListMembers
6543 Jul 26, 2021
c6102e7
GetTeamMembers
6543 Jul 26, 2021
bf5accc
OrgListTeams
6543 Jul 26, 2021
516354d
fix
6543 Jul 26, 2021
55e0021
ListUserTeams
6543 Jul 26, 2021
d04f6c5
ListCollaborators
6543 Jul 26, 2021
0b4984e
fix bug
6543 Jul 26, 2021
99dbf98
Repo#ListHooks
6543 Jul 26, 2021
6c243ac
ListIssueComments, ListRepoIssueComments
6543 Jul 26, 2021
251fe3d
Fix models/issue_stopwatch.go
6543 Jul 26, 2021
a4577d8
GetStopwatches
6543 Jul 26, 2021
badb37e
TrackedTimes
6543 Jul 26, 2021
c45c9d0
refactor models.ListDeployKeys
6543 Jul 26, 2021
6c8ec0a
Repo#ListDeployKeys
6543 Jul 26, 2021
3216180
Update models/issue_stopwatch.go
6543 Jul 26, 2021
aff32b3
Merge branch 'master' into api-generalize-list-header
6543 Jul 26, 2021
5a39632
CONTRIBUTING: mention Access-Control-Expose-Headers
6543 Jul 26, 2021
27398cf
fix CountComments
6543 Jul 26, 2021
7ae321d
Repo#ListLabels
6543 Jul 26, 2021
2696598
ListMilestones
6543 Jul 26, 2021
faf8817
ListPullReviews
6543 Jul 26, 2021
5eacdb7
fix getLatestCommitStatus
6543 Jul 26, 2021
e32426a
todo ...
6543 Jul 26, 2021
a57a8d1
ListSubscribers
6543 Jul 26, 2021
69ae323
no pagination supportet here
6543 Jul 26, 2021
9334a42
ListTags
6543 Jul 26, 2021
0d4632d
ListOauth2Applications
6543 Jul 26, 2021
02ada65
ListTopics
6543 Jul 26, 2021
02d19a7
TopicSearch
6543 Jul 27, 2021
0193622
Followers and Following
6543 Jul 27, 2021
b132c7a
listGPGKeys
6543 Jul 27, 2021
dceacf9
listPublicKeys
6543 Jul 27, 2021
fae949e
GetMyStarredRepos
6543 Jul 27, 2021
6505d16
getWatchedRepos
6543 Jul 27, 2021
dd766da
Merge branch 'main' into api-generalize-list-header
6543 Jul 27, 2021
2f5545f
fix CountTrackedTimes
6543 Jul 27, 2021
b26aa31
Merge branch 'master' into api-generalize-list-header
6543 Jul 27, 2021
c706d0f
Fix APIRepoTopic
6543 Jul 27, 2021
0ed695b
Add TestAPITopicSearch
6543 Jul 27, 2021
4435617
refert back routers/api/v1/org/member.go
6543 Jul 27, 2021
3861004
Just use count topics instead of getting the topics from the db
zeripath Jul 28, 2021
89158d2
Merge branch 'master' into api-generalize-list-header
6543 Jul 29, 2021
8958bbf
oops
zeripath Jul 29, 2021
db237b7
Merge branch 'main' into api-generalize-list-header
6543 Jul 29, 2021
bd82612
Merge branch 'main' into api-generalize-list-header
6543 Aug 9, 2021
7aae98c
introduce helper func and use them for SetLinkHeader related func
6543 Aug 9, 2021
8fa7079
use SetTotalCountHeader everywhere
6543 Aug 10, 2021
af4086d
update docs
6543 Aug 10, 2021
cc15aa6
Merge branch 'main' into api-generalize-list-header
6543 Aug 10, 2021
fb5934d
Merge branch 'main' into api-generalize-list-header
6543 Aug 11, 2021
f9453ea
Merge branch 'main' into api-generalize-list-header
6543 Aug 11, 2021
261109e
Merge branch 'main' into api-generalize-list-header
6543 Aug 12, 2021
926e468
Merge branch 'main' into api-generalize-list-header
6543 Aug 12, 2021
d499354
Merge branch 'main' into api-generalize-list-header
6543 Aug 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion integrations/api_issue_tracked_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
33 changes: 33 additions & 0 deletions integrations/api_repo_topic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package integrations
import (
"fmt"
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models"
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion models/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion models/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
lunny marked this conversation as resolved.
Show resolved Hide resolved
}

// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
Expand Down
5 changes: 5 additions & 0 deletions models/gpg_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
})
Expand Down
13 changes: 11 additions & 2 deletions models/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions models/issue_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
}

// ________
// \_____ \_______ ____
// / | \_ __ \/ ___\
Expand Down Expand Up @@ -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{})
}

// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
Expand Down
24 changes: 17 additions & 7 deletions models/issue_milestone.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions models/issue_milestone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down Expand Up @@ -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,
})
Expand All @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions models/issue_stopwatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 20 additions & 11 deletions models/issue_tracked_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
Loading