[API] List, Check, Add & delete endpoints for repository teams (#13630)

* List, Check, Add & delete endpoints for repository teams

* return units on single team responce too

* Add Tests
This commit is contained in:
6543 2021-02-01 22:57:12 +01:00 committed by GitHub
parent a9188631b9
commit 6d27703f14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 483 additions and 0 deletions

View file

@ -0,0 +1,77 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestAPIRepoTeams(t *testing.T) {
defer prepareTestEnv(t)()
// publicOrgRepo = user3/repo21
publicOrgRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 32}).(*models.Repository)
// user4
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User)
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session)
// ListTeams
url := fmt.Sprintf("/api/v1/repos/%s/teams?token=%s", publicOrgRepo.FullName(), token)
req := NewRequest(t, "GET", url)
res := session.MakeRequest(t, req, http.StatusOK)
var teams []*api.Team
DecodeJSON(t, res, &teams)
if assert.Len(t, teams, 2) {
assert.EqualValues(t, "Owners", teams[0].Name)
assert.EqualValues(t, false, teams[0].CanCreateOrgRepo)
assert.EqualValues(t, []string{"repo.code", "repo.issues", "repo.pulls", "repo.releases", "repo.wiki", "repo.ext_wiki", "repo.ext_issues"}, teams[0].Units)
assert.EqualValues(t, "owner", teams[0].Permission)
assert.EqualValues(t, "test_team", teams[1].Name)
assert.EqualValues(t, false, teams[1].CanCreateOrgRepo)
assert.EqualValues(t, []string{"repo.issues"}, teams[1].Units)
assert.EqualValues(t, "write", teams[1].Permission)
}
// IsTeam
url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "Test_Team", token)
req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK)
var team *api.Team
DecodeJSON(t, res, &team)
assert.EqualValues(t, teams[1], team)
url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "NonExistingTeam", token)
req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusNotFound)
// AddTeam with user4
url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token)
req = NewRequest(t, "PUT", url)
res = session.MakeRequest(t, req, http.StatusForbidden)
// AddTeam with user2
user = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
session = loginUser(t, user.Name)
token = getTokenForLoggedInUser(t, session)
url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token)
req = NewRequest(t, "PUT", url)
res = session.MakeRequest(t, req, http.StatusNoContent)
res = session.MakeRequest(t, req, http.StatusUnprocessableEntity) // test duplicate request
// DeleteTeam
url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token)
req = NewRequest(t, "DELETE", url)
res = session.MakeRequest(t, req, http.StatusNoContent)
res = session.MakeRequest(t, req, http.StatusUnprocessableEntity) // test duplicate request
}

View file

@ -727,6 +727,12 @@ func Routes() *web.Route {
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
Delete(reqAdmin(), repo.DeleteCollaborator)
}, reqToken())
m.Group("/teams", func() {
m.Get("", reqAnyRepoReader(), repo.ListTeams)
m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam).
Put(reqAdmin(), repo.AddTeam).
Delete(reqAdmin(), repo.DeleteTeam)
}, reqToken())
m.Get("/raw/*", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks).

View file

@ -0,0 +1,233 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
)
// ListTeams list a repository's teams
func ListTeams(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/teams repository repoListTeams
// ---
// summary: List a repository's teams
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/TeamList"
if !ctx.Repo.Owner.IsOrganization() {
ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
return
}
teams, err := ctx.Repo.Repository.GetRepoTeams()
if err != nil {
ctx.InternalServerError(err)
return
}
apiTeams := make([]*api.Team, len(teams))
for i := range teams {
if err := teams[i].GetUnits(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetUnits", err)
return
}
apiTeams[i] = convert.ToTeam(teams[i])
}
ctx.JSON(http.StatusOK, apiTeams)
}
// IsTeam check if a team is assigned to a repository
func IsTeam(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/teams/{team} repository repoCheckTeam
// ---
// summary: Check if a team is assigned to a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: team
// in: path
// description: team name
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Team"
// "404":
// "$ref": "#/responses/notFound"
// "405":
// "$ref": "#/responses/error"
if !ctx.Repo.Owner.IsOrganization() {
ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
return
}
team := getTeamByParam(ctx)
if team == nil {
return
}
if team.HasRepository(ctx.Repo.Repository.ID) {
if err := team.GetUnits(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetUnits", err)
return
}
apiTeam := convert.ToTeam(team)
ctx.JSON(http.StatusOK, apiTeam)
return
}
ctx.NotFound()
}
// AddTeam add a team to a repository
func AddTeam(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/teams/{team} repository repoAddTeam
// ---
// summary: Add a team to a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: team
// in: path
// description: team name
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/validationError"
// "405":
// "$ref": "#/responses/error"
changeRepoTeam(ctx, true)
}
// DeleteTeam delete a team from a repository
func DeleteTeam(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/teams/{team} repository repoDeleteTeam
// ---
// summary: Delete a team from a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: team
// in: path
// description: team name
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "422":
// "$ref": "#/responses/validationError"
// "405":
// "$ref": "#/responses/error"
changeRepoTeam(ctx, false)
}
func changeRepoTeam(ctx *context.APIContext, add bool) {
if !ctx.Repo.Owner.IsOrganization() {
ctx.Error(http.StatusMethodNotAllowed, "noOrg", "repo is not owned by an organization")
}
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
ctx.Error(http.StatusForbidden, "noAdmin", "user is nor repo admin nor owner")
return
}
team := getTeamByParam(ctx)
if team == nil {
return
}
repoHasTeam := team.HasRepository(ctx.Repo.Repository.ID)
var err error
if add {
if repoHasTeam {
ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name))
return
}
err = team.AddRepository(ctx.Repo.Repository)
} else {
if !repoHasTeam {
ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name))
return
}
err = team.RemoveRepository(ctx.Repo.Repository.ID)
}
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusNoContent)
}
func getTeamByParam(ctx *context.APIContext) *models.Team {
team, err := models.GetTeam(ctx.Repo.Owner.ID, ctx.Params(":team"))
if err != nil {
if models.IsErrTeamNotExist(err) {
ctx.Error(http.StatusNotFound, "TeamNotExit", err)
return nil
}
ctx.InternalServerError(err)
return nil
}
return team
}

View file

@ -8803,6 +8803,173 @@
}
}
},
"/repos/{owner}/{repo}/teams": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "List a repository's teams",
"operationId": "repoListTeams",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/TeamList"
}
}
}
},
"/repos/{owner}/{repo}/teams/{team}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Check if a team is assigned to a repository",
"operationId": "repoCheckTeam",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "team name",
"name": "team",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/Team"
},
"404": {
"$ref": "#/responses/notFound"
},
"405": {
"$ref": "#/responses/error"
}
}
},
"put": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Add a team to a repository",
"operationId": "repoAddTeam",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "team name",
"name": "team",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"405": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Delete a team from a repository",
"operationId": "repoDeleteTeam",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "team name",
"name": "team",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"405": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
},
"/repos/{owner}/{repo}/times": {
"get": {
"produces": [