* 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). | |||
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). |
@@ -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": { | |||
"get": { | |||
"produces": [ |