mirror of
https://github.com/go-gitea/gitea.git
synced 2025-05-01 13:15:20 +00:00
Add package version api endpoints (#34173)
Fixes #33544 Adds two new api endpoints to list a versions of a package and to get the latest version of a package by API. ⚠️ BREAKING ⚠️ the `size` field for this endpoint changes from `Size` to `size`.
This commit is contained in:
parent
34349c085c
commit
bec9233c29
@ -24,7 +24,7 @@ type Package struct {
|
|||||||
// PackageFile represents a package file
|
// PackageFile represents a package file
|
||||||
type PackageFile struct {
|
type PackageFile struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Size int64
|
Size int64 `json:"size"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
HashMD5 string `json:"md5"`
|
HashMD5 string `json:"md5"`
|
||||||
HashSHA1 string `json:"sha1"`
|
HashSHA1 string `json:"sha1"`
|
||||||
|
@ -1544,14 +1544,19 @@ func Routes() *web.Router {
|
|||||||
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
|
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
|
||||||
m.Group("/packages/{username}", func() {
|
m.Group("/packages/{username}", func() {
|
||||||
m.Group("/{type}/{name}", func() {
|
m.Group("/{type}/{name}", func() {
|
||||||
|
m.Get("/", packages.ListPackageVersions)
|
||||||
|
|
||||||
m.Group("/{version}", func() {
|
m.Group("/{version}", func() {
|
||||||
m.Get("", packages.GetPackage)
|
m.Get("", packages.GetPackage)
|
||||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
||||||
m.Get("/files", packages.ListPackageFiles)
|
m.Get("/files", packages.ListPackageFiles)
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Post("/-/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage)
|
m.Group("/-", func() {
|
||||||
m.Post("/-/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage)
|
m.Get("/latest", packages.GetLatestPackageVersion)
|
||||||
|
m.Post("/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage)
|
||||||
|
m.Post("/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Get("/", packages.ListPackages)
|
m.Get("/", packages.ListPackages)
|
||||||
|
@ -56,13 +56,10 @@ func ListPackages(ctx *context.APIContext) {
|
|||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
packageType := ctx.FormTrim("type")
|
apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{
|
||||||
query := ctx.FormTrim("q")
|
|
||||||
|
|
||||||
pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{
|
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
Type: packages.Type(packageType),
|
Type: packages.Type(ctx.FormTrim("type")),
|
||||||
Name: packages.SearchValue{Value: query},
|
Name: packages.SearchValue{Value: ctx.FormTrim("q")},
|
||||||
IsInternal: optional.Some(false),
|
IsInternal: optional.Some(false),
|
||||||
Paginator: &listOptions,
|
Paginator: &listOptions,
|
||||||
})
|
})
|
||||||
@ -71,22 +68,6 @@ func ListPackages(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pds, err := packages.GetPackageDescriptors(ctx, pvs)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
apiPackages := make([]*api.Package, 0, len(pds))
|
|
||||||
for _, pd := range pds {
|
|
||||||
apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiPackages = append(apiPackages, apiPackage)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetLinkHeader(int(count), listOptions.PageSize)
|
ctx.SetLinkHeader(int(count), listOptions.PageSize)
|
||||||
ctx.SetTotalCountHeader(count)
|
ctx.SetTotalCountHeader(count)
|
||||||
ctx.JSON(http.StatusOK, apiPackages)
|
ctx.JSON(http.StatusOK, apiPackages)
|
||||||
@ -217,6 +198,121 @@ func ListPackageFiles(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, apiPackageFiles)
|
ctx.JSON(http.StatusOK, apiPackageFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListPackageVersions gets all versions of a package
|
||||||
|
func ListPackageVersions(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /packages/{owner}/{type}/{name} package listPackageVersions
|
||||||
|
// ---
|
||||||
|
// summary: Gets all versions of a package
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: type
|
||||||
|
// in: path
|
||||||
|
// description: type of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - 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
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/PackageList"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
|
apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{
|
||||||
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
|
Type: packages.Type(ctx.PathParam("type")),
|
||||||
|
Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true},
|
||||||
|
IsInternal: optional.Some(false),
|
||||||
|
Paginator: &listOptions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int(count), listOptions.PageSize)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiPackages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestPackageVersion gets the latest version of a package
|
||||||
|
func GetLatestPackageVersion(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /packages/{owner}/{type}/{name}/-/latest package getLatestPackageVersion
|
||||||
|
// ---
|
||||||
|
// summary: Gets the latest version of a package
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: type
|
||||||
|
// in: path
|
||||||
|
// description: type of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the package
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/Package"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
pvs, _, err := packages.SearchLatestVersions(ctx, &packages.PackageSearchOptions{
|
||||||
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
|
Type: packages.Type(ctx.PathParam("type")),
|
||||||
|
Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true},
|
||||||
|
IsInternal: optional.Some(false),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pvs) == 0 {
|
||||||
|
ctx.APIError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pd, err := packages.GetPackageDescriptor(ctx, pvs[0])
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiPackage)
|
||||||
|
}
|
||||||
|
|
||||||
// LinkPackage sets a repository link for a package
|
// LinkPackage sets a repository link for a package
|
||||||
func LinkPackage(ctx *context.APIContext) {
|
func LinkPackage(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /packages/{owner}/{type}/{name}/-/link/{repo_name} package linkPackage
|
// swagger:operation POST /packages/{owner}/{type}/{name}/-/link/{repo_name} package linkPackage
|
||||||
@ -335,3 +431,26 @@ func UnlinkPackage(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func searchPackages(ctx *context.APIContext, opts *packages.PackageSearchOptions) ([]*api.Package, int64, error) {
|
||||||
|
pvs, count, err := packages.SearchVersions(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pds, err := packages.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
|
||||||
|
}
|
||||||
|
107
templates/swagger/v1_json.tmpl
generated
107
templates/swagger/v1_json.tmpl
generated
@ -3339,6 +3339,104 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/packages/{owner}/{type}/{name}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"package"
|
||||||
|
],
|
||||||
|
"summary": "Gets all versions of a package",
|
||||||
|
"operationId": "listPackageVersions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the package",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "type of the package",
|
||||||
|
"name": "type",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the package",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/PackageList"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/packages/{owner}/{type}/{name}/-/latest": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"package"
|
||||||
|
],
|
||||||
|
"summary": "Gets the latest version of a package",
|
||||||
|
"operationId": "getLatestPackageVersion",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the package",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "type of the package",
|
||||||
|
"name": "type",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the package",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/Package"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/packages/{owner}/{type}/{name}/-/link/{repo_name}": {
|
"/packages/{owner}/{type}/{name}/-/link/{repo_name}": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -24386,10 +24484,6 @@
|
|||||||
"description": "PackageFile represents a package file",
|
"description": "PackageFile represents a package file",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"Size": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
@ -24414,6 +24508,11 @@
|
|||||||
"sha512": {
|
"sha512": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "HashSHA512"
|
"x-go-name": "HashSHA512"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Size"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -83,6 +83,38 @@ func TestPackageAPI(t *testing.T) {
|
|||||||
assert.Equal(t, packageVersion, p.Version)
|
assert.Equal(t, packageVersion, p.Version)
|
||||||
assert.NotNil(t, p.Creator)
|
assert.NotNil(t, p.Creator)
|
||||||
assert.Equal(t, user.Name, p.Creator.UserName)
|
assert.Equal(t, user.Name, p.Creator.UserName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ListPackageVersions", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)).
|
||||||
|
AddTokenAuth(tokenReadPackage)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var apiPackages []*api.Package
|
||||||
|
DecodeJSON(t, resp, &apiPackages)
|
||||||
|
|
||||||
|
assert.Len(t, apiPackages, 1)
|
||||||
|
assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type)
|
||||||
|
assert.Equal(t, packageName, apiPackages[0].Name)
|
||||||
|
assert.Equal(t, packageVersion, apiPackages[0].Version)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LatestPackageVersion", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/latest", user.Name, packageName)).
|
||||||
|
AddTokenAuth(tokenReadPackage)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var apiPackage *api.Package
|
||||||
|
DecodeJSON(t, resp, &apiPackage)
|
||||||
|
|
||||||
|
assert.Equal(t, string(packages_model.TypeGeneric), apiPackage.Type)
|
||||||
|
assert.Equal(t, packageName, apiPackage.Name)
|
||||||
|
assert.Equal(t, packageVersion, apiPackage.Version)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("RepositoryLink", func(t *testing.T) {
|
t.Run("RepositoryLink", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
@ -136,7 +168,7 @@ func TestPackageAPI(t *testing.T) {
|
|||||||
|
|
||||||
// force link to a repository the currently logged-in user doesn't have access to
|
// force link to a repository the currently logged-in user doesn't have access to
|
||||||
privateRepoID := int64(6)
|
privateRepoID := int64(6)
|
||||||
assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, privateRepoID))
|
assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, ap1.ID, privateRepoID))
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage)
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage)
|
||||||
resp = MakeRequest(t, req, http.StatusOK)
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
@ -147,7 +179,6 @@ func TestPackageAPI(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID))
|
assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID))
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ListPackageFiles", func(t *testing.T) {
|
t.Run("ListPackageFiles", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
Loading…
Reference in New Issue
Block a user