Files
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

492 lines
13 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package packages
import (
"errors"
"net/http"
"github.com/hanzoai/git/models/packages"
repo_model "github.com/hanzoai/git/models/repo"
"github.com/hanzoai/git/modules/optional"
api "github.com/hanzoai/git/modules/structs"
"github.com/hanzoai/git/modules/util"
"github.com/hanzoai/git/routers/api/v1/utils"
"github.com/hanzoai/git/services/context"
"github.com/hanzoai/git/services/convert"
packages_service "github.com/hanzoai/git/services/packages"
)
// ListPackages gets all packages of an owner
func ListPackages(ctx *context.APIContext) {
// swagger:operation GET /owners/{owner}/packages package listPackages
// ---
// summary: Gets all packages of an owner
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the packages
// 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
// - 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"
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages.Type(ctx.FormTrim("type")),
Name: packages.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)
}
// GetPackage gets a package
func GetPackage(ctx *context.APIContext) {
// swagger:operation GET /owners/{owner}/packages/{type}/{name}/{version} package getPackage
// ---
// summary: Gets 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: version
// in: path
// description: version of the package
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Package"
// "404":
// "$ref": "#/responses/notFound"
apiPackage, err := convert.ToPackage(ctx, ctx.Package.Descriptor, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, apiPackage)
}
// DeletePackage deletes a package
func DeletePackage(ctx *context.APIContext) {
// swagger:operation DELETE /owners/{owner}/packages/{type}/{name} package deletePackage
// ---
// summary: Delete a package
// 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:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
err := packages_service.RemovePackage(ctx, ctx.Doer, ctx.Package.Descriptor.Package)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// DeletePackageVersion deletes a package version
func DeletePackageVersion(ctx *context.APIContext) {
// swagger:operation DELETE /owners/{owner}/packages/{type}/{name}/{version} package deletePackageVersion
// ---
// summary: Delete a package version
// 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: version
// in: path
// description: version of the package
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
err := packages_service.RemovePackageVersion(ctx, ctx.Doer, ctx.Package.Descriptor.Version)
if err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// ListPackageFiles gets all files of a package
func ListPackageFiles(ctx *context.APIContext) {
// swagger:operation GET /owners/{owner}/packages/{type}/{name}/{version}/files package listPackageFiles
// ---
// summary: Gets all files 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: version
// in: path
// description: version of the package
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/PackageFileList"
// "404":
// "$ref": "#/responses/notFound"
apiPackageFiles := make([]*api.PackageFile, 0, len(ctx.Package.Descriptor.Files))
for _, pfd := range ctx.Package.Descriptor.Files {
apiPackageFiles = append(apiPackageFiles, convert.ToPackageFile(pfd))
}
ctx.JSON(http.StatusOK, apiPackageFiles)
}
// ListPackageVersions gets all versions of a package
func ListPackageVersions(ctx *context.APIContext) {
// swagger:operation GET /owners/{owner}/packages/{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(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 /owners/{owner}/packages/{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.APIErrorNotFound()
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
func LinkPackage(ctx *context.APIContext) {
// swagger:operation POST /owners/{owner}/packages/{type}/{name}/-/link/{repo_name} package linkPackage
// ---
// summary: Link a package to a repository
// 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: repo_name
// in: path
// description: name of the repository to link.
// type: string
// required: true
// responses:
// "201":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err.Error())
} else {
ctx.APIErrorInternal(err)
}
return
}
repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParam("repo_name"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err.Error())
} else {
ctx.APIErrorInternal(err)
}
return
}
err = packages_service.LinkToRepository(ctx, pkg, repo, ctx.Doer)
if err != nil {
switch {
case errors.Is(err, util.ErrInvalidArgument):
ctx.APIError(http.StatusBadRequest, err.Error())
case errors.Is(err, util.ErrPermissionDenied):
ctx.APIError(http.StatusForbidden, err.Error())
default:
ctx.APIErrorInternal(err)
}
return
}
ctx.Status(http.StatusCreated)
}
// UnlinkPackage sets a repository link for a package
func UnlinkPackage(ctx *context.APIContext) {
// swagger:operation POST /owners/{owner}/packages/{type}/{name}/-/unlink package unlinkPackage
// ---
// summary: Unlink a package from a repository
// 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:
// "201":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err.Error())
} else {
ctx.APIErrorInternal(err)
}
return
}
err = packages_service.UnlinkFromRepository(ctx, pkg, ctx.Doer)
if err != nil {
switch {
case errors.Is(err, util.ErrPermissionDenied):
ctx.APIError(http.StatusForbidden, err.Error())
case errors.Is(err, util.ErrInvalidArgument):
ctx.APIError(http.StatusBadRequest, err.Error())
default:
ctx.APIErrorInternal(err)
}
return
}
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
}