@@ -24,6 +24,7 @@ import (
2424 "code.gitea.io/gitea/modules/setting"
2525 api "code.gitea.io/gitea/modules/structs"
2626 "code.gitea.io/gitea/modules/timeutil"
27+ "code.gitea.io/gitea/modules/util"
2728 "code.gitea.io/gitea/modules/web"
2829 "code.gitea.io/gitea/routers/api/v1/utils"
2930 "code.gitea.io/gitea/routers/common"
@@ -32,6 +33,60 @@ import (
3233 issue_service "code.gitea.io/gitea/services/issue"
3334)
3435
36+ // buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
37+ // It returns repoIDs, allPublic flag, and any error that occurred.
38+ func buildSearchIssuesRepoIDs (ctx * context.APIContext ) (repoIDs []int64 , allPublic bool , err error ) {
39+ opts := repo_model.SearchRepoOptions {
40+ Private : false ,
41+ AllPublic : true ,
42+ TopicOnly : false ,
43+ Collaborate : optional .None [bool ](),
44+ // This needs to be a column that is not nil in fixtures or
45+ // MySQL will return different results when sorting by null in some cases
46+ OrderBy : db .SearchOrderByAlphabetically ,
47+ Actor : ctx .Doer ,
48+ }
49+ if ctx .IsSigned {
50+ opts .Private = ! ctx .PublicOnly
51+ opts .AllLimited = true
52+ }
53+ if ctx .FormString ("owner" ) != "" {
54+ owner , err := user_model .GetUserByName (ctx , ctx .FormString ("owner" ))
55+ if err != nil {
56+ return nil , false , err
57+ }
58+ opts .OwnerID = owner .ID
59+ opts .AllLimited = false
60+ opts .AllPublic = false
61+ opts .Collaborate = optional .Some (false )
62+ }
63+ if ctx .FormString ("team" ) != "" {
64+ if ctx .FormString ("owner" ) == "" {
65+ return nil , false , util .NewInvalidArgumentErrorf ("owner organisation is required for filtering on team" )
66+ }
67+ team , err := organization .GetTeam (ctx , opts .OwnerID , ctx .FormString ("team" ))
68+ if err != nil {
69+ return nil , false , err
70+ }
71+ opts .TeamID = team .ID
72+ }
73+
74+ if opts .AllPublic {
75+ allPublic = true
76+ opts .AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
77+ }
78+ repoIDs , _ , err = repo_model .SearchRepositoryIDs (ctx , opts )
79+ if err != nil {
80+ return nil , false , err
81+ }
82+ if len (repoIDs ) == 0 {
83+ // no repos found, don't let the indexer return all repos
84+ repoIDs = []int64 {0 }
85+ }
86+
87+ return repoIDs , allPublic , nil
88+ }
89+
3590// SearchIssues searches for issues across the repositories that the user has access to
3691func SearchIssues (ctx * context.APIContext ) {
3792 // swagger:operation GET /repos/issues/search issue issueSearchIssues
@@ -58,11 +113,6 @@ func SearchIssues(ctx *context.APIContext) {
58113 // in: query
59114 // description: Search string
60115 // type: string
61- // - name: priority_repo_id
62- // in: query
63- // description: Repository ID to prioritize in the results
64- // type: integer
65- // format: int64
66116 // - name: type
67117 // in: query
68118 // description: Filter by issue type
@@ -136,97 +186,24 @@ func SearchIssues(ctx *context.APIContext) {
136186 return
137187 }
138188
139- var isClosed optional.Option [bool ]
140- switch ctx .FormString ("state" ) {
141- case "closed" :
142- isClosed = optional .Some (true )
143- case "all" :
144- isClosed = optional .None [bool ]()
145- default :
146- isClosed = optional .Some (false )
147- }
189+ isClosed := common .ParseIssueFilterStateIsClosed (ctx .FormString ("state" ))
148190
149- var (
150- repoIDs []int64
151- allPublic bool
152- )
153- {
154- // find repos user can access (for issue search)
155- opts := repo_model.SearchRepoOptions {
156- Private : false ,
157- AllPublic : true ,
158- TopicOnly : false ,
159- Collaborate : optional .None [bool ](),
160- // This needs to be a column that is not nil in fixtures or
161- // MySQL will return different results when sorting by null in some cases
162- OrderBy : db .SearchOrderByAlphabetically ,
163- Actor : ctx .Doer ,
164- }
165- if ctx .IsSigned {
166- opts .Private = ! ctx .PublicOnly
167- opts .AllLimited = true
168- }
169- if ctx .FormString ("owner" ) != "" {
170- owner , err := user_model .GetUserByName (ctx , ctx .FormString ("owner" ))
171- if err != nil {
172- if user_model .IsErrUserNotExist (err ) {
173- ctx .APIError (http .StatusBadRequest , err )
174- } else {
175- ctx .APIErrorInternal (err )
176- }
177- return
178- }
179- opts .OwnerID = owner .ID
180- opts .AllLimited = false
181- opts .AllPublic = false
182- opts .Collaborate = optional .Some (false )
183- }
184- if ctx .FormString ("team" ) != "" {
185- if ctx .FormString ("owner" ) == "" {
186- ctx .APIError (http .StatusBadRequest , "Owner organisation is required for filtering on team" )
187- return
188- }
189- team , err := organization .GetTeam (ctx , opts .OwnerID , ctx .FormString ("team" ))
190- if err != nil {
191- if organization .IsErrTeamNotExist (err ) {
192- ctx .APIError (http .StatusBadRequest , err )
193- } else {
194- ctx .APIErrorInternal (err )
195- }
196- return
197- }
198- opts .TeamID = team .ID
199- }
200-
201- if opts .AllPublic {
202- allPublic = true
203- opts .AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
204- }
205- repoIDs , _ , err = repo_model .SearchRepositoryIDs (ctx , opts )
206- if err != nil {
191+ repoIDs , allPublic , err := buildSearchIssuesRepoIDs (ctx )
192+ if err != nil {
193+ if errors .Is (err , util .ErrNotExist ) || errors .Is (err , util .ErrInvalidArgument ) {
194+ ctx .APIError (http .StatusBadRequest , err )
195+ } else {
207196 ctx .APIErrorInternal (err )
208- return
209- }
210- if len (repoIDs ) == 0 {
211- // no repos found, don't let the indexer return all repos
212- repoIDs = []int64 {0 }
213197 }
198+ return
214199 }
215200
216201 keyword := ctx .FormTrim ("q" )
217202 if strings .IndexByte (keyword , 0 ) >= 0 {
218203 keyword = ""
219204 }
220205
221- var isPull optional.Option [bool ]
222- switch ctx .FormString ("type" ) {
223- case "pulls" :
224- isPull = optional .Some (true )
225- case "issues" :
226- isPull = optional .Some (false )
227- default :
228- isPull = optional .None [bool ]()
229- }
206+ isPull := common .ParseIssueFilterTypeIsPull (ctx .FormString ("type" ))
230207
231208 var includedAnyLabels []int64
232209 {
@@ -256,14 +233,7 @@ func SearchIssues(ctx *context.APIContext) {
256233 }
257234 }
258235
259- // this api is also used in UI,
260- // so the default limit is set to fit UI needs
261- limit := ctx .FormInt ("limit" )
262- if limit == 0 {
263- limit = setting .UI .IssuePagingNum
264- } else if limit > setting .API .MaxResponseItems {
265- limit = setting .API .MaxResponseItems
266- }
236+ limit := util .IfZero (ctx .FormInt ("limit" ), setting .API .DefaultPagingNum )
267237
268238 searchOpt := & issue_indexer.SearchOptions {
269239 Paginator : & db.ListOptions {
@@ -306,10 +276,6 @@ func SearchIssues(ctx *context.APIContext) {
306276 }
307277 }
308278
309- // FIXME: It's unsupported to sort by priority repo when searching by indexer,
310- // it's indeed an regression, but I think it is worth to support filtering by indexer first.
311- _ = ctx .FormInt64 ("priority_repo_id" )
312-
313279 ids , total , err := issue_indexer .SearchIssues (ctx , searchOpt )
314280 if err != nil {
315281 ctx .APIErrorInternal (err )
@@ -409,16 +375,7 @@ func ListIssues(ctx *context.APIContext) {
409375 return
410376 }
411377
412- var isClosed optional.Option [bool ]
413- switch ctx .FormString ("state" ) {
414- case "closed" :
415- isClosed = optional .Some (true )
416- case "all" :
417- isClosed = optional .None [bool ]()
418- default :
419- isClosed = optional .Some (false )
420- }
421-
378+ isClosed := common .ParseIssueFilterStateIsClosed (ctx .FormString ("state" ))
422379 keyword := ctx .FormTrim ("q" )
423380 if strings .IndexByte (keyword , 0 ) >= 0 {
424381 keyword = ""
0 commit comments