mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-11 09:15:31 +02:00
feat: add api routes to add/update/remove teams to/from repo groups
This commit is contained in:
parent
d92dc9e79b
commit
7b47ece487
13
modules/structs/repo_group_team.go
Normal file
13
modules/structs/repo_group_team.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
// CreateOrUpdateRepoGroupTeamOption options for adding a team to a repo group
|
||||
type CreateOrUpdateRepoGroupTeamOption struct {
|
||||
// Whether the team can create repositories and subgroups in the group
|
||||
CanCreateIn *bool `json:"can_create_in"`
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
Permission *RepoWritePermission `json:"permission"`
|
||||
}
|
||||
@ -518,6 +518,25 @@ func reqGroupMembership(mode perm.AccessMode, needsCreatePerm bool) func(ctx *co
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
isOrgOwner := false
|
||||
isOrgAdmin := false
|
||||
|
||||
if ctx.Doer != nil {
|
||||
isOrgOwner, err = organization.IsOrganizationOwner(ctx, g.OwnerID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
isOrgAdmin, err = organization.IsOrganizationAdmin(ctx, g.OwnerID, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if isOrgOwner || isOrgAdmin {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
canAccess, err := g.CanAccessAtLevel(ctx, ctx.Doer, mode)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@ -541,7 +560,7 @@ func reqGroupMembership(mode perm.AccessMode, needsCreatePerm bool) func(ctx *co
|
||||
return
|
||||
}
|
||||
}
|
||||
if !canCreateIn {
|
||||
if !(canCreateIn || isOrgOwner || isOrgAdmin) {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("User[%d] does not have permission to create new items in group[%d]", ctx.Doer.ID, gid))
|
||||
return
|
||||
}
|
||||
@ -1841,6 +1860,14 @@ func Routes() *web.Router {
|
||||
m.Post("/new", reqToken(), reqGroupMembership(perm.AccessModeWrite, true), bind(api.NewGroupOption{}), group.NewSubGroup)
|
||||
m.Get("/subgroups", reqGroupMembership(perm.AccessModeRead, false), group.GetGroupSubGroups)
|
||||
m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqGroupMembership(perm.AccessModeRead, false), group.GetGroupRepos)
|
||||
m.Group("/teams", func() {
|
||||
m.Get("", group.ListTeams)
|
||||
m.Combo("/{team}").
|
||||
Get(group.IsTeam).
|
||||
Put(group.AddTeam, reqGroupMembership(perm.AccessModeAdmin, false)).
|
||||
Patch(group.EditTeam, reqGroupMembership(perm.AccessModeAdmin, false)).
|
||||
Delete(group.DeleteTeam, reqGroupMembership(perm.AccessModeAdmin, false))
|
||||
}, reqToken(), reqGroupMembership(perm.AccessModeRead, false))
|
||||
}, checkTokenPublicOnly())
|
||||
})
|
||||
return m
|
||||
|
||||
318
routers/api/v1/group/team.go
Normal file
318
routers/api/v1/group/team.go
Normal file
@ -0,0 +1,318 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
perm_model "code.gitea.io/gitea/models/perm"
|
||||
org_group_model "code.gitea.io/gitea/models/shared/group"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
)
|
||||
|
||||
// ListTeams list a repository group's teams
|
||||
func ListTeams(ctx *context.APIContext) {
|
||||
// swagger:operation GET /groups/{group_id}/teams repository-group repoGroupListTeams
|
||||
// ---
|
||||
// summary: List a repository group's teams
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/TeamList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
var (
|
||||
err error
|
||||
group *group_model.Group
|
||||
)
|
||||
group, err = group_model.GetGroupByID(ctx, ctx.PathParamInt64("group_id"))
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
teams, err := org_group_model.GetGroupTeams(ctx, group.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
apiTeams, err := convert.ToTeams(ctx, teams, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiTeams)
|
||||
}
|
||||
|
||||
// AddTeam add a team to a repository group
|
||||
func AddTeam(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /groups/{group_id}/teams/{team} repository-group repoGroupAddTeam
|
||||
// ---
|
||||
// summary: Add a team to a repository group
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: team
|
||||
// in: path
|
||||
// description: team name
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateOrUpdateRepoGroupTeamOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateOrUpdateRepoGroupTeamOption)
|
||||
|
||||
changeGroupTeam(ctx, form, true)
|
||||
}
|
||||
|
||||
// EditTeam update a team assigned to a repository group
|
||||
func EditTeam(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /groups/{group_id}/teams/{team} repository-group repoGroupEditTeam
|
||||
// ---
|
||||
// summary: Update a team assigned to a repository group
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: team
|
||||
// in: path
|
||||
// description: team name
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateOrUpdateRepoGroupTeamOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
form := web.GetForm(ctx).(*api.CreateOrUpdateRepoGroupTeamOption)
|
||||
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
group, err := group_model.GetGroupByID(ctx, gid)
|
||||
if err != nil {
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
team := getTeamFromGroup(ctx, group)
|
||||
gt, err := group_model.FindGroupTeamByTeamID(ctx, group.ID, team.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if gt == nil {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if form.CanCreateIn != nil {
|
||||
gt.CanCreateIn = *form.CanCreateIn
|
||||
}
|
||||
if form.Permission != nil {
|
||||
gt.AccessMode = perm_model.ParseAccessMode(string(*form.Permission))
|
||||
}
|
||||
err = group_service.UpdateGroupTeam(ctx, gt, form.UnitsMap)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteTeam delete a team from a repository group
|
||||
func DeleteTeam(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /groups/{group_id}/teams/{team} repository-group repoGroupDeleteTeam
|
||||
// ---
|
||||
// summary: Add a team to a repository group
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: team
|
||||
// in: path
|
||||
// description: team name
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
changeGroupTeam(ctx, nil, false)
|
||||
}
|
||||
|
||||
// IsTeam check if a team is assigned to a repository
|
||||
func IsTeam(ctx *context.APIContext) {
|
||||
// swagger:operation GET /groups/{group_id}/teams/{team} repository-group repoGroupCheckTeam
|
||||
// ---
|
||||
// summary: Check if a team is assigned to a repository group
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: team
|
||||
// in: path
|
||||
// description: team name
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Team"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
group, err := group_model.GetGroupByID(ctx, gid)
|
||||
if err != nil {
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
team := getTeamFromGroup(ctx, group)
|
||||
if team == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if group_model.HasTeamGroup(ctx, group.OwnerID, team.ID, gid) {
|
||||
apiTeam, err := convert.ToTeam(ctx, team)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiTeam)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
|
||||
func getTeamFromGroup(ctx *context.APIContext, group *group_model.Group) *org_model.Team {
|
||||
teamName := ctx.PathParam("team")
|
||||
|
||||
team, err := org_model.GetTeam(ctx, group.OwnerID, teamName)
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return nil
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil
|
||||
}
|
||||
return team
|
||||
}
|
||||
|
||||
func changeGroupTeam(ctx *context.APIContext, options *api.CreateOrUpdateRepoGroupTeamOption, add bool) {
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
group, err := group_model.GetGroupByID(ctx, gid)
|
||||
if err != nil {
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
err = group.LoadOwner(ctx)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
team := getTeamFromGroup(ctx, group)
|
||||
if team == nil {
|
||||
return
|
||||
}
|
||||
|
||||
groupHasTeam := group_model.HasTeamGroup(ctx, group.OwnerID, team.ID, gid)
|
||||
|
||||
if add {
|
||||
if groupHasTeam {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' is already added to group", team.Name))
|
||||
return
|
||||
}
|
||||
var accessModeArg *perm_model.AccessMode
|
||||
if options.Permission != nil {
|
||||
accessModeArg = new(perm_model.ParseAccessMode(string(*options.Permission)))
|
||||
}
|
||||
err = group_service.AddTeamToGroup(ctx, group, team.Name, options.UnitsMap, options.CanCreateIn, accessModeArg)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = group_service.DeleteTeamFromGroup(ctx, group, group.OwnerID, team.Name)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if _, err = db.GetEngine(ctx).Where("group_id = ?", gid).Delete(new(group_model.RepoGroupUnit)); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user