mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-24 08:01:42 +00:00
feat(api): list all packages for site administrators (#38968)
Add `GET /admin/packages` so site administrators can review packages
across every owner without querying each owner separately.
It returns the same package version representation as `GET
/packages/{owner}` and supports `page`, `limit`, `type`, and `q`
filters.
---
Assisted by Codet(DeepSeek)
---------
Signed-off-by: bircni <bircni@icloud.com>
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
87
routers/api/v1/admin/packages.go
Normal file
87
routers/api/v1/admin/packages.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
packages_model "gitea.dev/models/packages"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// ListPackages lists every package version on the instance.
|
||||
func ListPackages(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/packages admin adminListPackages
|
||||
// ---
|
||||
// summary: List all packages
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: type
|
||||
// in: query
|
||||
// description: package type filter
|
||||
// type: string
|
||||
// enum: [alpine, cargo, chef, composer, conan, conda, container, cran, debian, generic, go, helm, maven, npm, nuget, pub, pypi, rpm, rubygems, swift, terraform, vagrant]
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: name filter
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PackageList"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
apiPackages, count, err := searchPackages(ctx, &packages_model.PackageSearchOptions{
|
||||
Type: packages_model.Type(ctx.FormTrim("type")),
|
||||
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
|
||||
IsInternal: optional.Some(false),
|
||||
Paginator: &listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiPackages)
|
||||
}
|
||||
|
||||
func searchPackages(ctx *context.APIContext, opts *packages_model.PackageSearchOptions) ([]*api.Package, int64, error) {
|
||||
pvs, count, err := packages_model.SearchVersions(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
apiPackages := make([]*api.Package, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
apiPackages = append(apiPackages, apiPackage)
|
||||
}
|
||||
|
||||
return apiPackages, count, nil
|
||||
}
|
||||
@@ -1888,6 +1888,7 @@ func Routes() *web.Router {
|
||||
m.Post("/{task}", admin.PostCronTask)
|
||||
})
|
||||
m.Get("/orgs", admin.GetAllOrgs)
|
||||
m.Get("/packages", admin.ListPackages)
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.SearchUsers)
|
||||
m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
|
||||
|
||||
75
templates/swagger/v1-openapi3.generated.json
generated
75
templates/swagger/v1-openapi3.generated.json
generated
@@ -11685,6 +11685,81 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/admin/packages": {
|
||||
"get": {
|
||||
"operationId": "adminListPackages",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "page number of results to return (1-based)",
|
||||
"in": "query",
|
||||
"name": "page",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "page size of results",
|
||||
"in": "query",
|
||||
"name": "limit",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "package type filter",
|
||||
"in": "query",
|
||||
"name": "type",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"alpine",
|
||||
"cargo",
|
||||
"chef",
|
||||
"composer",
|
||||
"conan",
|
||||
"conda",
|
||||
"container",
|
||||
"cran",
|
||||
"debian",
|
||||
"generic",
|
||||
"go",
|
||||
"helm",
|
||||
"maven",
|
||||
"npm",
|
||||
"nuget",
|
||||
"pub",
|
||||
"pypi",
|
||||
"rpm",
|
||||
"rubygems",
|
||||
"swift",
|
||||
"terraform",
|
||||
"vagrant"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "name filter",
|
||||
"in": "query",
|
||||
"name": "q",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/PackageList"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
}
|
||||
},
|
||||
"summary": "List all packages",
|
||||
"tags": [
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/admin/unadopted": {
|
||||
"get": {
|
||||
"operationId": "adminUnadoptedList",
|
||||
|
||||
70
templates/swagger/v1-swagger.generated.json
generated
70
templates/swagger/v1-swagger.generated.json
generated
@@ -628,6 +628,76 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/packages": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "List all packages",
|
||||
"operationId": "adminListPackages",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"alpine",
|
||||
"cargo",
|
||||
"chef",
|
||||
"composer",
|
||||
"conan",
|
||||
"conda",
|
||||
"container",
|
||||
"cran",
|
||||
"debian",
|
||||
"generic",
|
||||
"go",
|
||||
"helm",
|
||||
"maven",
|
||||
"npm",
|
||||
"nuget",
|
||||
"pub",
|
||||
"pypi",
|
||||
"rpm",
|
||||
"rubygems",
|
||||
"swift",
|
||||
"terraform",
|
||||
"vagrant"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "package type filter",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name filter",
|
||||
"name": "q",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/PackageList"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/unadopted": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
||||
44
tests/integration/api_admin_packages_test.go
Normal file
44
tests/integration/api_admin_packages_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIAdminListPackages(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
packageName := "admin-test-package"
|
||||
packageVersion := "1.0.0"
|
||||
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user4.Name, packageName, packageVersion)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).AddBasicAuth(user4.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
adminToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin)
|
||||
req = NewRequest(t, "GET", "/api/v1/admin/packages").AddTokenAuth(adminToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
apiPackages := DecodeJSON(t, resp, []*api.Package{})
|
||||
actual := map[string]any{}
|
||||
for _, p := range apiPackages {
|
||||
actual[p.Name] = map[string]any{"ownerName": p.Owner.UserName, "type": p.Type, "version": p.Version}
|
||||
}
|
||||
assert.Equal(t, map[string]any{
|
||||
packageName: map[string]any{"ownerName": user4.Name, "type": "generic", "version": packageVersion},
|
||||
}, actual)
|
||||
}
|
||||
Reference in New Issue
Block a user