Skip to content

Commit 7cfa3d5

Browse files
author
dasv
committed
Api endpoint for searching teams.
Signed-off-by: dasv <[email protected]>
1 parent 5fcef38 commit 7cfa3d5

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed

models/org_team.go

+68
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"code.gitea.io/gitea/modules/setting"
1616

1717
"github.com/go-xorm/xorm"
18+
"xorm.io/builder"
1819
)
1920

2021
const ownerTeamName = "Owners"
@@ -34,6 +35,73 @@ type Team struct {
3435
Units []*TeamUnit `xorm:"-"`
3536
}
3637

38+
// SearchTeamOptions holds the search options
39+
type SearchTeamOptions struct {
40+
UserID int64
41+
UserIsAdmin bool
42+
Keyword string
43+
OrgID int64
44+
IncludeDesc bool
45+
Limit int
46+
}
47+
48+
// SearchTeam search for teams
49+
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
50+
if opts.Limit <= 0 {
51+
opts.Limit = 10
52+
}
53+
var cond = builder.NewCond()
54+
55+
if len(opts.Keyword) > 0 {
56+
lowerKeyword := strings.ToLower(opts.Keyword)
57+
var keywordCond = builder.NewCond()
58+
if opts.IncludeDesc {
59+
keywordCond = builder.Or(
60+
builder.Like{"lower_name", lowerKeyword},
61+
builder.Like{"LOWER(description)", lowerKeyword},
62+
)
63+
} else {
64+
keywordCond = builder.Or(
65+
builder.Like{"lower_name", lowerKeyword},
66+
)
67+
}
68+
cond = cond.And(keywordCond)
69+
}
70+
71+
if opts.OrgID > 0 {
72+
cond = cond.And(builder.Eq{"org_id": opts.OrgID})
73+
} else if !opts.UserIsAdmin {
74+
// Limit search to organizations where user is member
75+
cond = cond.And(
76+
builder.In("org_id", builder.Select("`org_user`.org_id").
77+
From("org_user").
78+
Where(builder.Eq{"`org_user`.uid": opts.UserID}).
79+
Join("INNER", "org_user", "`org_user`.org_id = `team`.org_id")))
80+
}
81+
82+
sess := x.NewSession()
83+
defer sess.Close()
84+
85+
count, err := sess.
86+
Where(cond).
87+
Count(new(Team))
88+
89+
if err != nil {
90+
return nil, 0, fmt.Errorf("Count: %v", err)
91+
}
92+
93+
teams := make([]*Team, 0, opts.Limit)
94+
if err = sess.
95+
Where(cond).
96+
OrderBy("lower_name").
97+
Limit(opts.Limit).
98+
Find(&teams); err != nil {
99+
return nil, 0, fmt.Errorf("Team: %v", err)
100+
}
101+
102+
return teams, count, nil
103+
}
104+
37105
// ColorFormat provides a basic color format for a Team
38106
func (t *Team) ColorFormat(s fmt.State) {
39107
log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",

routers/api/v1/api.go

+4
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,10 @@ func RegisterRoutes(m *macaron.Macaron) {
831831
})
832832
}, orgAssignment(false, true), reqToken(), reqTeamMembership())
833833

834+
m.Group("/teams", func() {
835+
m.Get("/search", org.SearchTeam)
836+
})
837+
834838
m.Any("/*", func(ctx *context.APIContext) {
835839
ctx.NotFound()
836840
})

routers/api/v1/org/team.go

+102
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package org
77

88
import (
9+
"strings"
10+
911
"code.gitea.io/gitea/models"
1012
"code.gitea.io/gitea/modules/context"
1113
api "code.gitea.io/gitea/modules/structs"
@@ -495,3 +497,103 @@ func RemoveTeamRepository(ctx *context.APIContext) {
495497
}
496498
ctx.Status(204)
497499
}
500+
501+
// SearchTeam api for searching teams
502+
func SearchTeam(ctx *context.APIContext) {
503+
// swagger:operation GET /teams/search organization teamSearch
504+
// ---
505+
// summary: Search for teams
506+
// produces:
507+
// - application/json
508+
// parameters:
509+
// - name: q
510+
// in: query
511+
// description: keywords to search
512+
// required: true
513+
// type: string
514+
// - name: org_id
515+
// in: query
516+
// description: search only teams within organization
517+
// type: integer
518+
// format: int64
519+
// required: false
520+
// - name: inclDesc
521+
// in: query
522+
// description: include search within team description (defaults to true)
523+
// type: boolean
524+
// - name: limit
525+
// in: query
526+
// description: limit size of results
527+
// type: integer
528+
// responses:
529+
// "200":
530+
// description: "SearchResults of a successful search"
531+
// schema:
532+
// type: object
533+
// properties:
534+
// ok:
535+
// type: boolean
536+
// data:
537+
// type: array
538+
// items:
539+
// "$ref": "#/definitions/Team"
540+
opts := &models.SearchTeamOptions{
541+
UserID: ctx.Data["SignedUserID"].(int64),
542+
UserIsAdmin: ctx.IsUserSiteAdmin(),
543+
Keyword: strings.Trim(ctx.Query("q"), " "),
544+
OrgID: ctx.QueryInt64("org_id"),
545+
IncludeDesc: (ctx.Query("inclDesc") == "" || ctx.QueryBool("inclDesc")),
546+
Limit: ctx.QueryInt("limit"),
547+
}
548+
549+
// If searching in a specific organization, require organization membership
550+
if opts.OrgID > 0 {
551+
if isMember, err := models.IsOrganizationMember(opts.OrgID, opts.UserID); err != nil {
552+
ctx.Error(500, "IsOrganizationMember", err)
553+
return
554+
} else if !isMember && !ctx.IsUserSiteAdmin() {
555+
ctx.Error(403, "", "Must be an organization member")
556+
return
557+
}
558+
}
559+
560+
teams, _, err := models.SearchTeam(opts)
561+
if err != nil {
562+
ctx.JSON(500, map[string]interface{}{
563+
"ok": false,
564+
"error": err.Error(),
565+
})
566+
return
567+
}
568+
569+
apiTeams := make([]*api.Team, len(teams))
570+
cache := make(map[int64]*api.Organization)
571+
for i := range teams {
572+
if err := teams[i].GetUnits(); err != nil {
573+
ctx.Error(500, "GetUnits", err)
574+
return
575+
}
576+
apiTeams[i] = convert.ToTeam(teams[i])
577+
578+
if opts.OrgID <= 0 {
579+
apiOrg, ok := cache[teams[i].OrgID]
580+
if !ok {
581+
org, err := models.GetUserByID(teams[i].OrgID)
582+
if err != nil {
583+
ctx.Error(500, "GetUserByID", err)
584+
return
585+
}
586+
apiOrg = convert.ToOrganization(org)
587+
cache[teams[i].OrgID] = apiOrg
588+
}
589+
apiTeams[i] = convert.ToTeam(teams[i])
590+
apiTeams[i].Organization = apiOrg
591+
}
592+
}
593+
594+
ctx.JSON(200, map[string]interface{}{
595+
"ok": true,
596+
"data": apiTeams,
597+
})
598+
599+
}

templates/swagger/v1_json.tmpl

+59
Original file line numberDiff line numberDiff line change
@@ -5627,6 +5627,65 @@
56275627
}
56285628
}
56295629
},
5630+
"/teams/search": {
5631+
"get": {
5632+
"produces": [
5633+
"application/json"
5634+
],
5635+
"tags": [
5636+
"organization"
5637+
],
5638+
"summary": "Search for teams",
5639+
"operationId": "teamSearch",
5640+
"parameters": [
5641+
{
5642+
"type": "string",
5643+
"description": "keywords to search",
5644+
"name": "q",
5645+
"in": "query",
5646+
"required": true
5647+
},
5648+
{
5649+
"type": "integer",
5650+
"format": "int64",
5651+
"description": "search only teams within organization",
5652+
"name": "org_id",
5653+
"in": "query"
5654+
},
5655+
{
5656+
"type": "boolean",
5657+
"description": "include search within team description (defaults to true)",
5658+
"name": "inclDesc",
5659+
"in": "query"
5660+
},
5661+
{
5662+
"type": "integer",
5663+
"description": "limit size of results",
5664+
"name": "limit",
5665+
"in": "query"
5666+
}
5667+
],
5668+
"responses": {
5669+
"200": {
5670+
"description": "SearchResults of a successful search",
5671+
"schema": {
5672+
"type": "object",
5673+
"properties": {
5674+
"data": {
5675+
"type": "array",
5676+
"items": {
5677+
"$ref": "#/definitions/Team"
5678+
}
5679+
},
5680+
"ok": {
5681+
"type": "boolean"
5682+
}
5683+
}
5684+
}
5685+
}
5686+
}
5687+
}
5688+
},
56305689
"/teams/{id}": {
56315690
"get": {
56325691
"produces": [

0 commit comments

Comments
 (0)