From ab817e42519676b0f29957b3d0d7cfdf7857f9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Thu, 14 Aug 2025 17:13:40 -0400 Subject: [PATCH] add api routes and functions to get a repository group's subgroups and repos --- models/shared/group/org_group.go | 11 ++++ routers/api/v1/api.go | 2 + routers/api/v1/group/group.go | 95 +++++++++++++++++++++++++++++++- templates/swagger/v1_json.tmpl | 67 ++++++++++++++++++++-- 4 files changed, 168 insertions(+), 7 deletions(-) diff --git a/models/shared/group/org_group.go b/models/shared/group/org_group.go index bdb326a32e..4ff39b1b53 100644 --- a/models/shared/group/org_group.go +++ b/models/shared/group/org_group.go @@ -1,6 +1,7 @@ package group import ( + repo_model "code.gitea.io/gitea/models/repo" "context" "code.gitea.io/gitea/models/db" @@ -55,3 +56,13 @@ func IsGroupMember(ctx context.Context, groupID int64, user *user_model.User) (b Table("team_user"). Exist() } + +func GetGroupRepos(ctx context.Context, groupID int64, doer *user_model.User) ([]*repo_model.Repository, error) { + sess := db.GetEngine(ctx) + repos := make([]*repo_model.Repository, 0) + return repos, sess.Table("repository"). + Where("group_id = ?", groupID). + And(builder.In("id", repo_model.AccessibleRepoIDsQuery(doer))). + OrderBy("group_sort_order"). + Find(&repos) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index ffade69d3f..b17505bbff 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1806,6 +1806,8 @@ func Routes() *web.Router { Delete(reqToken(), reqGroupMembership(perm.AccessModeAdmin, false), group.DeleteGroup) m.Post("/move", reqToken(), reqGroupMembership(perm.AccessModeWrite, false), bind(api.MoveGroupOption{}), group.MoveGroup) 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) }, checkTokenPublicOnly()) }) return m diff --git a/routers/api/v1/group/group.go b/routers/api/v1/group/group.go index af41784aea..cfe267813d 100644 --- a/routers/api/v1/group/group.go +++ b/routers/api/v1/group/group.go @@ -1,6 +1,8 @@ package group import ( + access_model "code.gitea.io/gitea/models/perm/access" + shared_group_model "code.gitea.io/gitea/models/shared/group" "fmt" "net/http" "strings" @@ -265,8 +267,6 @@ func GetGroup(ctx *context.APIContext) { // "$ref": "#/responses/Group" // "404": // "$ref": "#/responses/notFound" - // "422": - // "$ref": "#/responses/validationError" var ( err error group *group_model.Group @@ -318,3 +318,94 @@ func DeleteGroup(ctx *context.APIContext) { } ctx.Status(http.StatusNoContent) } + +func GetGroupRepos(ctx *context.APIContext) { + // swagger:operation GET /groups/{group_id}/repos repository-group groupGetRepos + // --- + // summary: gets the repos contained within a group + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the group containing the repositories + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/RepositoryList" + // "404": + // "$ref": "#/responses/notFound" + gid := ctx.PathParamInt64("group_id") + _, err := group_model.GetGroupByID(ctx, gid) + if err != nil { + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + } else { + ctx.APIErrorInternal(err) + } + return + } + + groupRepos, err := shared_group_model.GetGroupRepos(ctx, gid, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + repos := make([]*api.Repository, len(groupRepos)) + for i, repo := range groupRepos { + permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + repos[i] = convert.ToRepo(ctx, repo, permission) + } + ctx.SetTotalCountHeader(int64(len(repos))) + ctx.JSON(http.StatusOK, repos) +} + +func GetGroupSubGroups(ctx *context.APIContext) { + // swagger:operation GET /groups/{group_id}/subgroups repository-group groupGetSubGroups + // --- + // summary: gets the subgroups contained within a group + // produces: + // - application/json + // parameters: + // - name: group_id + // in: path + // description: id of the parent group + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/GroupList" + // "404": + // "$ref": "#/responses/notFound" + g, err := group_model.GetGroupByID(ctx, ctx.PathParamInt64("group_id")) + if err != nil { + if group_model.IsErrGroupNotExist(err) { + ctx.APIErrorNotFound() + } else { + ctx.APIErrorInternal(err) + } + return + } + err = g.LoadAccessibleSubgroups(ctx, false, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + groups := make([]*api.Group, len(g.Subgroups)) + for i, group := range g.Subgroups { + groups[i], err = convert.ToAPIGroup(ctx, group, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + } + ctx.SetTotalCountHeader(int64(len(groups))) + ctx.JSON(http.StatusOK, groups) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8189e06e12..6c4e833b22 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1324,8 +1324,8 @@ "tags": [ "repository-group" ], - "summary": "gets the repos contained within a group", - "operationId": "groupRepos", + "summary": "gets a group in an organization", + "operationId": "groupGet", "parameters": [ { "type": "integer", @@ -1342,9 +1342,6 @@ }, "404": { "$ref": "#/responses/notFound" - }, - "422": { - "$ref": "#/responses/validationError" } } }, @@ -1509,6 +1506,66 @@ } } }, + "/groups/{group_id}/repos": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "gets the repos contained within a group", + "operationId": "groupGetRepos", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the group containing the repositories", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/RepositoryList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/groups/{group_id}/subgroups": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository-group" + ], + "summary": "gets the subgroups contained within a group", + "operationId": "groupGetSubGroups", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the parent group", + "name": "group_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/GroupList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/label/templates": { "get": { "produces": [