* List, Check, Add & delete endpoints for repository teams * return units on single team responce too * Add Teststags/v1.15.0-dev
@@ -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 | |||||
} |
@@ -727,6 +727,12 @@ func Routes() *web.Route { | |||||
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). | Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). | ||||
Delete(reqAdmin(), repo.DeleteCollaborator) | Delete(reqAdmin(), repo.DeleteCollaborator) | ||||
}, reqToken()) | }, 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("/raw/*", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile) | ||||
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive) | m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive) | ||||
m.Combo("/forks").Get(repo.ListForks). | m.Combo("/forks").Get(repo.ListForks). | ||||
@@ -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 | |||||
} |
@@ -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": { | "/repos/{owner}/{repo}/times": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ | ||||