mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-04 05:45:23 +02:00
add api routes and functions for repository groups
This commit is contained in:
parent
401e633f01
commit
314be8ad26
@ -158,6 +158,8 @@ type CreateRepoOption struct {
|
||||
// ObjectFormatName of the underlying git repository, empty string for default (sha1)
|
||||
// enum: ["sha1","sha256"]
|
||||
ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"`
|
||||
// GroupID of the group which will contain this repository. ignored if the repo owner is not an organization.
|
||||
GroupID int64 `json:"group_id"`
|
||||
}
|
||||
|
||||
// EditRepoOption options when editing a repository's properties
|
||||
|
||||
@ -27,7 +27,7 @@ type NewGroupOption struct {
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
}
|
||||
|
||||
// MoveGroupOption - options for changing a group's parent and sort order
|
||||
// MoveGroupOption - options for changing a group or repo's parent and sort order
|
||||
// swagger:model
|
||||
type MoveGroupOption struct {
|
||||
// the new parent group. can be 0 to specify no parent
|
||||
|
||||
@ -68,11 +68,15 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
group_model "code.gitea.io/gitea/models/group"
|
||||
shared_group_model "code.gitea.io/gitea/models/shared/group"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@ -485,6 +489,60 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// reqGroupMembership user should be organization owner,
|
||||
// a member of a team with access to the group, or site admin
|
||||
func reqGroupMembership(mode perm.AccessMode, needsCreatePerm bool) func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if ctx.IsUserSiteAdmin() {
|
||||
return
|
||||
}
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
g, err := group_model.GetGroupByID(ctx, gid)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
err = g.LoadOwner(ctx)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
var canAccess bool
|
||||
if ctx.IsSigned {
|
||||
canAccess, err = g.CanAccessAtLevel(ctx, ctx.Doer.ID, mode)
|
||||
} else {
|
||||
canAccess, err = g.CanAccessAtLevel(ctx, 0, mode)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
igm, err := shared_group_model.IsGroupMember(ctx, gid, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !igm && !canAccess {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if needsCreatePerm {
|
||||
canCreateIn := false
|
||||
if ctx.IsSigned {
|
||||
canCreateIn, err = g.CanCreateIn(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !canCreateIn {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("User[%d] does not have permission to create new items in group[%d]", ctx.Doer.ID, gid))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqTeamMembership user should be an team member, or a site admin
|
||||
func reqTeamMembership() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
@ -1154,6 +1212,7 @@ func Routes() *web.Router {
|
||||
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
||||
Delete(reqToken(), reqOwner(), repo.Delete).
|
||||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
|
||||
m.Post("/groups/move", reqToken(), bind(api.EditGroupOption{}), reqOrgMembership(), reqGroupMembership(perm.AccessModeWrite, false), repo.MoveRepoToGroup)
|
||||
m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
|
||||
m.Group("/transfer", func() {
|
||||
m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
||||
@ -1661,6 +1720,10 @@ func Routes() *web.Router {
|
||||
m.Delete("", org.UnblockUser)
|
||||
})
|
||||
}, reqToken(), reqOrgOwnership())
|
||||
m.Group("/groups", func() {
|
||||
m.Post("/new", reqToken(), reqGroupMembership(perm.AccessModeWrite, true), group.NewGroup)
|
||||
m.Post("/{group_id}/move", reqToken(), reqGroupMembership(perm.AccessModeWrite, false), group.MoveGroup)
|
||||
})
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||
m.Group("/teams/{teamid}", func() {
|
||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||
@ -1741,7 +1804,15 @@ func Routes() *web.Router {
|
||||
m.Get("/search", repo.TopicSearch)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
}, sudo())
|
||||
|
||||
m.Group("/groups", func() {
|
||||
m.Group("/{group_id}", func() {
|
||||
m.Combo("").
|
||||
Get(reqGroupMembership(perm.AccessModeRead, false), group.GetGroup).
|
||||
Patch(reqToken(), reqGroupMembership(perm.AccessModeWrite, false), bind(api.EditGroupOption{}), group.EditGroup).
|
||||
Delete(reqToken(), reqGroupMembership(perm.AccessModeAdmin, false), group.DeleteGroup)
|
||||
m.Post("/new", reqToken(), reqGroupMembership(perm.AccessModeWrite, true), bind(api.NewGroupOption{}), group.NewSubGroup)
|
||||
})
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
|
||||
329
routers/api/v1/group/group.go
Normal file
329
routers/api/v1/group/group.go
Normal file
@ -0,0 +1,329 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
group_model "code.gitea.io/gitea/models/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"
|
||||
)
|
||||
|
||||
func createCommonGroup(ctx *context.APIContext, parentGroupID int64) (*api.Group, error) {
|
||||
form := web.GetForm(ctx).(*api.NewGroupOption)
|
||||
group := &group_model.Group{
|
||||
Name: form.Name,
|
||||
Description: form.Description,
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
LowerName: strings.ToLower(form.Name),
|
||||
Visibility: form.Visibility,
|
||||
ParentGroupID: parentGroupID,
|
||||
}
|
||||
if err := group_service.NewGroup(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convert.ToAPIGroup(ctx, group, ctx.Doer)
|
||||
}
|
||||
|
||||
// NewGroup create a new root-level group in an organization
|
||||
func NewGroup(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/groups/new repository-group groupNew
|
||||
// ---
|
||||
// summary: create a root-level repository group for an organization
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateGroupOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Group"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
ag, err := createCommonGroup(ctx, 0)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, ag)
|
||||
}
|
||||
|
||||
// NewSubGroup create a new subgroup inside a group
|
||||
func NewSubGroup(ctx *context.APIContext) {
|
||||
// swagger:operation POST /groups/{group_id}/new repository-group groupNewSubGroup
|
||||
// ---
|
||||
// summary: create a subgroup inside a group
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group to create a subgroup in
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateGroupOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Group"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
var (
|
||||
group *api.Group
|
||||
err error
|
||||
)
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
group, err = createCommonGroup(ctx, gid)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, group)
|
||||
}
|
||||
|
||||
// MoveGroup - move a group to a different group in the same organization, or to the root level if
|
||||
func MoveGroup(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/groups/{group_id}/move repository-group groupMove
|
||||
// ---
|
||||
// summary: move a group to a different parent group
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group to move
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/MoveGroupOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Group"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
form := web.GetForm(ctx).(*api.MoveGroupOption)
|
||||
id := ctx.PathParamInt64("group_id")
|
||||
var err error
|
||||
npos := -1
|
||||
if form.NewPos != nil {
|
||||
npos = *form.NewPos
|
||||
}
|
||||
err = group_service.MoveGroupItem(ctx, group_service.MoveGroupOptions{
|
||||
form.NewParent, id, true, npos,
|
||||
}, 3)
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
var (
|
||||
ng *group_model.Group
|
||||
apiGroup *api.Group
|
||||
)
|
||||
ng, err = group_model.GetGroupByID(ctx, id)
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
apiGroup, err = convert.ToAPIGroup(ctx, ng, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiGroup)
|
||||
}
|
||||
|
||||
// EditGroup - update a group in an organization
|
||||
func EditGroup(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /orgs/{org}/groups/{group_id} repository-group groupEdit
|
||||
// ---
|
||||
// summary: edits a group in an organization. only fields that are set will be changed.
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group to edit
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditGroupOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Group"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
var (
|
||||
err error
|
||||
group *group_model.Group
|
||||
)
|
||||
form := web.GetForm(ctx).(*api.EditGroupOption)
|
||||
gid := ctx.PathParamInt64("group_id")
|
||||
group, err = group_model.GetGroupByID(ctx, gid)
|
||||
if group_model.IsErrGroupNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if form.Visibility != nil {
|
||||
group.Visibility = *form.Visibility
|
||||
}
|
||||
if form.Description != nil {
|
||||
group.Description = *form.Description
|
||||
}
|
||||
if form.Name != nil {
|
||||
group.Name = *form.Name
|
||||
}
|
||||
err = group_model.UpdateGroup(ctx, group)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
var newAPIGroup *api.Group
|
||||
newAPIGroup, err = convert.ToAPIGroup(ctx, group, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, newAPIGroup)
|
||||
}
|
||||
|
||||
func GetGroup(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/groups/{group_id} repository-group groupGet
|
||||
// ---
|
||||
// summary: gets a group in an organization
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group to retrieve
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditGroupOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Group"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
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 group.OwnerID != ctx.Org.Organization.ID {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
apiGroup, err := convert.ToAPIGroup(ctx, group, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiGroup)
|
||||
}
|
||||
|
||||
func DeleteGroup(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/groups/{group_id} repositoryGroup groupDelete
|
||||
// ---
|
||||
// summary: Delete a repository group
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the group to delete
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: group_id
|
||||
// in: path
|
||||
// description: id of the group to delete
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
err := group_service.DeleteGroup(ctx, ctx.PathParamInt64("group_id"))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@ -36,6 +36,7 @@ import (
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
group_service "code.gitea.io/gitea/services/group"
|
||||
"code.gitea.io/gitea/services/issue"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
@ -263,6 +264,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
|
||||
IsTemplate: opt.Template,
|
||||
ObjectFormatName: opt.ObjectFormatName,
|
||||
GroupID: opt.GroupID,
|
||||
})
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||
@ -1264,3 +1266,51 @@ func ListRepoActivityFeeds(ctx *context.APIContext) {
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
|
||||
func MoveRepoToGroup(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repo/{owner}/{repo}/move
|
||||
// ---
|
||||
// summary: move a repository to another group
|
||||
// consumes:
|
||||
// - application/json
|
||||
// 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: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/MoveGroupOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
form := web.GetForm(ctx).(*api.MoveGroupOption)
|
||||
npos := -1
|
||||
if form.NewPos != nil {
|
||||
npos = *form.NewPos
|
||||
}
|
||||
err := group_service.MoveGroupItem(ctx, group_service.MoveGroupOptions{
|
||||
IsGroup: false, NewPos: npos,
|
||||
ItemID: ctx.Repo.Repository.ID,
|
||||
NewParent: form.NewParent,
|
||||
}, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user