Skip to content

Commit

Permalink
Api endpoint for searching teams.
Browse files Browse the repository at this point in the history
Signed-off-by: dasv <david.svantesson@qrtech.se>
  • Loading branch information
dasv committed Sep 5, 2019
1 parent 5fcef38 commit 7cfa3d5
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 0 deletions.
68 changes: 68 additions & 0 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/setting"

"github.com/go-xorm/xorm"
"xorm.io/builder"
)

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

// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
UserID int64
UserIsAdmin bool
Keyword string
OrgID int64
IncludeDesc bool
Limit int
}

// SearchTeam search for teams
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
if opts.Limit <= 0 {
opts.Limit = 10
}
var cond = builder.NewCond()

if len(opts.Keyword) > 0 {
lowerKeyword := strings.ToLower(opts.Keyword)
var keywordCond = builder.NewCond()
if opts.IncludeDesc {
keywordCond = builder.Or(
builder.Like{"lower_name", lowerKeyword},
builder.Like{"LOWER(description)", lowerKeyword},
)
} else {
keywordCond = builder.Or(
builder.Like{"lower_name", lowerKeyword},
)
}
cond = cond.And(keywordCond)
}

if opts.OrgID > 0 {
cond = cond.And(builder.Eq{"org_id": opts.OrgID})
} else if !opts.UserIsAdmin {
// Limit search to organizations where user is member
cond = cond.And(
builder.In("org_id", builder.Select("`org_user`.org_id").
From("org_user").
Where(builder.Eq{"`org_user`.uid": opts.UserID}).
Join("INNER", "org_user", "`org_user`.org_id = `team`.org_id")))
}

sess := x.NewSession()
defer sess.Close()

count, err := sess.
Where(cond).
Count(new(Team))

if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
}

teams := make([]*Team, 0, opts.Limit)
if err = sess.
Where(cond).
OrderBy("lower_name").
Limit(opts.Limit).
Find(&teams); err != nil {
return nil, 0, fmt.Errorf("Team: %v", err)
}

return teams, count, nil
}

// ColorFormat provides a basic color format for a Team
func (t *Team) ColorFormat(s fmt.State) {
log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
Expand Down
4 changes: 4 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,10 @@ func RegisterRoutes(m *macaron.Macaron) {
})
}, orgAssignment(false, true), reqToken(), reqTeamMembership())

m.Group("/teams", func() {
m.Get("/search", org.SearchTeam)
})

m.Any("/*", func(ctx *context.APIContext) {
ctx.NotFound()
})
Expand Down
102 changes: 102 additions & 0 deletions routers/api/v1/org/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package org

import (
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -495,3 +497,103 @@ func RemoveTeamRepository(ctx *context.APIContext) {
}
ctx.Status(204)
}

// SearchTeam api for searching teams
func SearchTeam(ctx *context.APIContext) {
// swagger:operation GET /teams/search organization teamSearch
// ---
// summary: Search for teams
// produces:
// - application/json
// parameters:
// - name: q
// in: query
// description: keywords to search
// required: true
// type: string
// - name: org_id
// in: query
// description: search only teams within organization
// type: integer
// format: int64
// required: false
// - name: inclDesc
// in: query
// description: include search within team description (defaults to true)
// type: boolean
// - name: limit
// in: query
// description: limit size of results
// type: integer
// responses:
// "200":
// description: "SearchResults of a successful search"
// schema:
// type: object
// properties:
// ok:
// type: boolean
// data:
// type: array
// items:
// "$ref": "#/definitions/Team"
opts := &models.SearchTeamOptions{
UserID: ctx.Data["SignedUserID"].(int64),
UserIsAdmin: ctx.IsUserSiteAdmin(),
Keyword: strings.Trim(ctx.Query("q"), " "),
OrgID: ctx.QueryInt64("org_id"),
IncludeDesc: (ctx.Query("inclDesc") == "" || ctx.QueryBool("inclDesc")),
Limit: ctx.QueryInt("limit"),
}

// If searching in a specific organization, require organization membership
if opts.OrgID > 0 {
if isMember, err := models.IsOrganizationMember(opts.OrgID, opts.UserID); err != nil {
ctx.Error(500, "IsOrganizationMember", err)
return
} else if !isMember && !ctx.IsUserSiteAdmin() {
ctx.Error(403, "", "Must be an organization member")
return
}
}

teams, _, err := models.SearchTeam(opts)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"ok": false,
"error": err.Error(),
})
return
}

apiTeams := make([]*api.Team, len(teams))
cache := make(map[int64]*api.Organization)
for i := range teams {
if err := teams[i].GetUnits(); err != nil {
ctx.Error(500, "GetUnits", err)
return
}
apiTeams[i] = convert.ToTeam(teams[i])

if opts.OrgID <= 0 {
apiOrg, ok := cache[teams[i].OrgID]
if !ok {
org, err := models.GetUserByID(teams[i].OrgID)
if err != nil {
ctx.Error(500, "GetUserByID", err)
return
}
apiOrg = convert.ToOrganization(org)
cache[teams[i].OrgID] = apiOrg
}
apiTeams[i] = convert.ToTeam(teams[i])
apiTeams[i].Organization = apiOrg
}
}

ctx.JSON(200, map[string]interface{}{
"ok": true,
"data": apiTeams,
})

}
59 changes: 59 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5627,6 +5627,65 @@
}
}
},
"/teams/search": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Search for teams",
"operationId": "teamSearch",
"parameters": [
{
"type": "string",
"description": "keywords to search",
"name": "q",
"in": "query",
"required": true
},
{
"type": "integer",
"format": "int64",
"description": "search only teams within organization",
"name": "org_id",
"in": "query"
},
{
"type": "boolean",
"description": "include search within team description (defaults to true)",
"name": "inclDesc",
"in": "query"
},
{
"type": "integer",
"description": "limit size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "SearchResults of a successful search",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Team"
}
},
"ok": {
"type": "boolean"
}
}
}
}
}
}
},
"/teams/{id}": {
"get": {
"produces": [
Expand Down

0 comments on commit 7cfa3d5

Please sign in to comment.