Files

253 lines
6.8 KiB
Go
Raw Permalink Normal View History

2022-03-30 10:42:47 +02:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2022-03-30 10:42:47 +02:00
package composer
import (
2022-12-31 12:49:37 +01:00
"errors"
2022-03-30 10:42:47 +02:00
"io"
"net/http"
"net/url"
"strconv"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/optional"
2022-03-30 10:42:47 +02:00
packages_module "code.gitea.io/gitea/modules/packages"
composer_module "code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/setting"
2022-07-27 03:59:10 +02:00
"code.gitea.io/gitea/modules/util"
2022-03-30 10:42:47 +02:00
"code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
2022-12-29 03:57:15 +01:00
"code.gitea.io/gitea/services/convert"
2022-03-30 10:42:47 +02:00
packages_service "code.gitea.io/gitea/services/packages"
)
2023-07-04 20:36:08 +02:00
func apiError(ctx *context.Context, status int, obj any) {
2025-07-17 00:25:49 +08:00
message := helper.ProcessErrorForUser(ctx, status, obj)
type Error struct {
Status int `json:"status"`
Message string `json:"message"`
}
ctx.JSON(status, struct {
Errors []Error `json:"errors"`
}{
Errors: []Error{
{Status: status, Message: message},
},
2022-03-30 10:42:47 +02:00
})
}
// ServiceIndex displays registry endpoints
func ServiceIndex(ctx *context.Context) {
resp := createServiceIndexResponse(setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/composer")
ctx.JSON(http.StatusOK, resp)
}
// SearchPackages searches packages, only "q" is supported
// https://packagist.org/apidoc#search-packages
func SearchPackages(ctx *context.Context) {
2025-06-18 03:48:09 +02:00
page := max(ctx.FormInt("page"), 1)
2022-03-30 10:42:47 +02:00
perPage := ctx.FormInt("per_page")
paginator := db.ListOptions{
Page: page,
PageSize: convert.ToCorrectPageSize(perPage),
}
opts := &packages_model.PackageSearchOptions{
2022-07-27 03:59:10 +02:00
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeComposer,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: optional.Some(false),
2022-07-27 03:59:10 +02:00
Paginator: &paginator,
2022-03-30 10:42:47 +02:00
}
if ctx.FormTrim("type") != "" {
opts.Properties = map[string]string{
composer_module.TypeProperty: ctx.FormTrim("type"),
}
}
pvs, total, err := packages_model.SearchLatestVersions(ctx, opts)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
nextLink := ""
if len(pvs) == paginator.PageSize {
u, err := url.Parse(setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/composer/search.json")
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
q := u.Query()
q.Set("q", ctx.FormTrim("q"))
q.Set("type", ctx.FormTrim("type"))
q.Set("page", strconv.Itoa(page+1))
if perPage != 0 {
q.Set("per_page", strconv.Itoa(perPage))
}
u.RawQuery = q.Encode()
nextLink = u.String()
}
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
resp := createSearchResultResponse(total, pds, nextLink)
ctx.JSON(http.StatusOK, resp)
}
// EnumeratePackages lists all package names
// https://packagist.org/apidoc#list-packages
func EnumeratePackages(ctx *context.Context) {
ps, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeComposer)
2022-03-30 10:42:47 +02:00
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
names := make([]string, 0, len(ps))
for _, p := range ps {
names = append(names, p.Name)
}
ctx.JSON(http.StatusOK, map[string][]string{
"packageNames": names,
})
}
// PackageMetadata returns the metadata for a single package
// https://packagist.org/apidoc#get-package-data
func PackageMetadata(ctx *context.Context) {
2024-06-19 06:32:45 +08:00
vendorName := ctx.PathParam("vendorname")
projectName := ctx.PathParam("projectname")
2022-03-30 10:42:47 +02:00
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeComposer, vendorName+"/"+projectName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
return
}
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
resp := createPackageMetadataResponse(
ctx,
2022-03-30 10:42:47 +02:00
setting.AppURL+"api/packages/"+ctx.Package.Owner.Name+"/composer",
pds,
)
ctx.JSON(http.StatusOK, resp)
}
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
2025-06-19 03:04:24 +08:00
s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
2022-03-30 10:42:47 +02:00
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeComposer,
2024-06-19 06:32:45 +08:00
Name: ctx.PathParam("package"),
Version: ctx.PathParam("version"),
2022-03-30 10:42:47 +02:00
},
&packages_service.PackageFileInfo{
2024-06-19 06:32:45 +08:00
Filename: ctx.PathParam("filename"),
2022-03-30 10:42:47 +02:00
},
ctx.Req.Method,
2022-03-30 10:42:47 +02:00
)
if err != nil {
if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
2022-03-30 10:42:47 +02:00
apiError(ctx, http.StatusNotFound, err)
return
}
apiError(ctx, http.StatusInternalServerError, err)
return
}
helper.ServePackageFile(ctx, s, u, pf)
2022-03-30 10:42:47 +02:00
}
// UploadPackage creates a new package
func UploadPackage(ctx *context.Context) {
2023-05-02 18:31:35 +02:00
buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
2022-03-30 10:42:47 +02:00
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer buf.Close()
cp, err := composer_module.ParsePackage(buf, ctx.FormTrim("version"))
2022-03-30 10:42:47 +02:00
if err != nil {
2022-12-31 12:49:37 +01:00
if errors.Is(err, util.ErrInvalidArgument) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
2022-03-30 10:42:47 +02:00
return
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if cp.Version == "" {
// the version should be either set in the "composer.json", or as a query parameter "?version=xxx"
apiError(ctx, http.StatusBadRequest, composer_module.ErrInvalidVersion)
return
2022-03-30 10:42:47 +02:00
}
_, _, err = packages_service.CreatePackageAndAddFile(
ctx,
2022-03-30 10:42:47 +02:00
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeComposer,
Name: cp.Name,
Version: cp.Version,
},
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: cp.Metadata,
2022-07-28 05:59:39 +02:00
VersionProperties: map[string]string{
2022-03-30 10:42:47 +02:00
composer_module.TypeProperty: cp.Type,
},
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: cp.Filename,
2022-03-30 10:42:47 +02:00
},
2022-11-09 07:34:27 +01:00
Creator: ctx.Doer,
Data: buf,
IsLead: true,
2022-03-30 10:42:47 +02:00
},
)
if err != nil {
2022-11-09 07:34:27 +01:00
switch err {
case packages_model.ErrDuplicatePackageVersion:
apiError(ctx, http.StatusConflict, err)
2022-11-09 07:34:27 +01:00
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
2022-03-30 10:42:47 +02:00
}
return
}
ctx.Status(http.StatusCreated)
}