Skip to content

Commit 70a5b58

Browse files
committed
refactor: extract helper functions from SearchIssues
- Add ErrOwnerOrgRequired custom error type to replace string comparison - Refactor error handling to use proper Go error pattern
1 parent b671d50 commit 70a5b58

File tree

1 file changed

+102
-81
lines changed

1 file changed

+102
-81
lines changed

routers/api/v1/repo/issue.go

Lines changed: 102 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,100 @@ import (
3232
issue_service "code.gitea.io/gitea/services/issue"
3333
)
3434

35+
// ErrOwnerOrgRequired represents an error when owner organisation is required for filtering on team
36+
type ErrOwnerOrgRequired struct{}
37+
38+
// IsErrOwnerOrgRequired checks if an error is an ErrOwnerOrgRequired
39+
func IsErrOwnerOrgRequired(err error) bool {
40+
_, ok := err.(ErrOwnerOrgRequired)
41+
return ok
42+
}
43+
44+
func (err ErrOwnerOrgRequired) Error() string {
45+
return "owner organisation is required for filtering on team"
46+
}
47+
48+
// parseIssueIsClosed parses the "state" query parameter and returns the corresponding isClosed option
49+
func parseIssueIsClosed(ctx *context.APIContext) optional.Option[bool] {
50+
switch ctx.FormString("state") {
51+
case "closed":
52+
return optional.Some(true)
53+
case "all":
54+
return optional.None[bool]()
55+
default:
56+
return optional.Some(false)
57+
}
58+
}
59+
60+
// parseIssueIsPull parses the "type" query parameter and returns the corresponding isPull option
61+
func parseIssueIsPull(ctx *context.APIContext) optional.Option[bool] {
62+
switch ctx.FormString("type") {
63+
case "pulls":
64+
return optional.Some(true)
65+
case "issues":
66+
return optional.Some(false)
67+
default:
68+
return optional.None[bool]()
69+
}
70+
}
71+
72+
// buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
73+
// It returns repoIDs, allPublic flag, and any error that occurred.
74+
func buildSearchIssuesRepoIDs(ctx *context.APIContext) ([]int64, bool, error) {
75+
var repoIDs []int64
76+
var allPublic bool
77+
78+
opts := repo_model.SearchRepoOptions{
79+
Private: false,
80+
AllPublic: true,
81+
TopicOnly: false,
82+
Collaborate: optional.None[bool](),
83+
// This needs to be a column that is not nil in fixtures or
84+
// MySQL will return different results when sorting by null in some cases
85+
OrderBy: db.SearchOrderByAlphabetically,
86+
Actor: ctx.Doer,
87+
}
88+
if ctx.IsSigned {
89+
opts.Private = !ctx.PublicOnly
90+
opts.AllLimited = true
91+
}
92+
if ctx.FormString("owner") != "" {
93+
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
94+
if err != nil {
95+
return nil, false, err
96+
}
97+
opts.OwnerID = owner.ID
98+
opts.AllLimited = false
99+
opts.AllPublic = false
100+
opts.Collaborate = optional.Some(false)
101+
}
102+
if ctx.FormString("team") != "" {
103+
if ctx.FormString("owner") == "" {
104+
return nil, false, ErrOwnerOrgRequired{}
105+
}
106+
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
107+
if err != nil {
108+
return nil, false, err
109+
}
110+
opts.TeamID = team.ID
111+
}
112+
113+
if opts.AllPublic {
114+
allPublic = true
115+
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
116+
}
117+
repoIDs, _, err := repo_model.SearchRepositoryIDs(ctx, opts)
118+
if err != nil {
119+
return nil, false, err
120+
}
121+
if len(repoIDs) == 0 {
122+
// no repos found, don't let the indexer return all repos
123+
repoIDs = []int64{0}
124+
}
125+
126+
return repoIDs, allPublic, nil
127+
}
128+
35129
// SearchIssues searches for issues across the repositories that the user has access to
36130
func SearchIssues(ctx *context.APIContext) {
37131
// swagger:operation GET /repos/issues/search issue issueSearchIssues
@@ -136,97 +230,24 @@ func SearchIssues(ctx *context.APIContext) {
136230
return
137231
}
138232

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-
}
148-
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-
}
233+
isClosed := parseIssueIsClosed(ctx)
200234

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 {
235+
repoIDs, allPublic, err := buildSearchIssuesRepoIDs(ctx)
236+
if err != nil {
237+
if user_model.IsErrUserNotExist(err) || organization.IsErrTeamNotExist(err) || IsErrOwnerOrgRequired(err) {
238+
ctx.APIError(http.StatusBadRequest, err)
239+
} else {
207240
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}
213241
}
242+
return
214243
}
215244

216245
keyword := ctx.FormTrim("q")
217246
if strings.IndexByte(keyword, 0) >= 0 {
218247
keyword = ""
219248
}
220249

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-
}
250+
isPull := parseIssueIsPull(ctx)
230251

231252
var includedAnyLabels []int64
232253
{

0 commit comments

Comments
 (0)