初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package alpine
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "gitea.dev/models/packages"
|
||||
alpine_module "gitea.dev/modules/packages/alpine"
|
||||
)
|
||||
|
||||
// GetBranches gets all available branches
|
||||
func GetBranches(ctx context.Context, ownerID int64) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeAlpine,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
alpine_module.PropertyBranch,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetRepositories gets all available repositories for the given branch
|
||||
func GetRepositories(ctx context.Context, ownerID int64, branch string) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeAlpine,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
alpine_module.PropertyRepository,
|
||||
&packages_model.DistinctPropertyDependency{
|
||||
Name: alpine_module.PropertyBranch,
|
||||
Value: branch,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// GetArchitectures gets all available architectures for the given repository
|
||||
func GetArchitectures(ctx context.Context, ownerID int64, repository string) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeAlpine,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
alpine_module.PropertyArchitecture,
|
||||
&packages_model.DistinctPropertyDependency{
|
||||
Name: alpine_module.PropertyRepository,
|
||||
Value: repository,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "gitea.dev/models/packages"
|
||||
arch_module "gitea.dev/modules/packages/arch"
|
||||
)
|
||||
|
||||
// GetRepositories gets all available repositories
|
||||
func GetRepositories(ctx context.Context, ownerID int64) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeArch,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
arch_module.PropertyRepository,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetArchitectures gets all available architectures for the given repository
|
||||
func GetArchitectures(ctx context.Context, ownerID int64, repository string) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeArch,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
arch_module.PropertyArchitecture,
|
||||
&packages_model.DistinctPropertyDependency{
|
||||
Name: arch_module.PropertyRepository,
|
||||
Value: repository,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package conan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
conan_module "gitea.dev/modules/packages/conan"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRecipeReferenceNotExist = util.NewNotExistErrorf("recipe reference does not exist")
|
||||
ErrPackageReferenceNotExist = util.NewNotExistErrorf("package reference does not exist")
|
||||
)
|
||||
|
||||
// RecipeExists checks if a recipe exists
|
||||
func RecipeExists(ctx context.Context, ownerID int64, ref *conan_module.RecipeReference) (bool, error) {
|
||||
revisions, err := GetRecipeRevisions(ctx, ownerID, ref)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(revisions) != 0, nil
|
||||
}
|
||||
|
||||
type PropertyValue struct {
|
||||
Value string
|
||||
CreatedUnix timeutil.TimeStamp
|
||||
}
|
||||
|
||||
func findPropertyValues(ctx context.Context, propertyName string, ownerID int64, name, version string, propertyFilter map[string]string) ([]*PropertyValue, error) {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
|
||||
|
||||
propsCondBlock := builder.NewCond()
|
||||
for name, value := range propertyFilter {
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{
|
||||
"package_property.name": name,
|
||||
"package_property.value": value,
|
||||
})
|
||||
}
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeConan,
|
||||
"package.owner_id": ownerID,
|
||||
"package.lower_name": strings.ToLower(name),
|
||||
"package_version.lower_version": strings.ToLower(version),
|
||||
"package_version.is_internal": false,
|
||||
strconv.Itoa(len(propertyFilter)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
}
|
||||
|
||||
in2 := builder.
|
||||
Select("package_file.id").
|
||||
From("package_file").
|
||||
InnerJoin("package_version", "package_version.id = package_file.version_id").
|
||||
InnerJoin("package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
query := builder.
|
||||
Select("package_property.value, MAX(package_file.created_unix) AS created_unix").
|
||||
From("package_property").
|
||||
InnerJoin("package_file", "package_file.id = package_property.ref_id").
|
||||
Where(builder.Eq{"package_property.name": propertyName}.And(builder.In("package_property.ref_id", in2))).
|
||||
GroupBy("package_property.value").
|
||||
OrderBy("created_unix DESC")
|
||||
|
||||
var values []*PropertyValue
|
||||
return values, db.GetEngine(ctx).SQL(query).Find(&values)
|
||||
}
|
||||
|
||||
// GetRecipeRevisions gets all revisions of a recipe
|
||||
func GetRecipeRevisions(ctx context.Context, ownerID int64, ref *conan_module.RecipeReference) ([]*PropertyValue, error) {
|
||||
values, err := findPropertyValues(
|
||||
ctx,
|
||||
conan_module.PropertyRecipeRevision,
|
||||
ownerID,
|
||||
ref.Name,
|
||||
ref.Version,
|
||||
map[string]string{
|
||||
conan_module.PropertyRecipeUser: ref.User,
|
||||
conan_module.PropertyRecipeChannel: ref.Channel,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetLastRecipeRevision gets the latest recipe revision
|
||||
func GetLastRecipeRevision(ctx context.Context, ownerID int64, ref *conan_module.RecipeReference) (*PropertyValue, error) {
|
||||
revisions, err := GetRecipeRevisions(ctx, ownerID, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(revisions) == 0 {
|
||||
return nil, ErrRecipeReferenceNotExist
|
||||
}
|
||||
return revisions[0], nil
|
||||
}
|
||||
|
||||
// GetPackageReferences gets all package references of a recipe
|
||||
func GetPackageReferences(ctx context.Context, ownerID int64, ref *conan_module.RecipeReference) ([]*PropertyValue, error) {
|
||||
values, err := findPropertyValues(
|
||||
ctx,
|
||||
conan_module.PropertyPackageReference,
|
||||
ownerID,
|
||||
ref.Name,
|
||||
ref.Version,
|
||||
map[string]string{
|
||||
conan_module.PropertyRecipeUser: ref.User,
|
||||
conan_module.PropertyRecipeChannel: ref.Channel,
|
||||
conan_module.PropertyRecipeRevision: ref.Revision,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetPackageRevisions gets all revision of a package
|
||||
func GetPackageRevisions(ctx context.Context, ownerID int64, ref *conan_module.PackageReference) ([]*PropertyValue, error) {
|
||||
values, err := findPropertyValues(
|
||||
ctx,
|
||||
conan_module.PropertyPackageRevision,
|
||||
ownerID,
|
||||
ref.Recipe.Name,
|
||||
ref.Recipe.Version,
|
||||
map[string]string{
|
||||
conan_module.PropertyRecipeUser: ref.Recipe.User,
|
||||
conan_module.PropertyRecipeChannel: ref.Recipe.Channel,
|
||||
conan_module.PropertyRecipeRevision: ref.Recipe.Revision,
|
||||
conan_module.PropertyPackageReference: ref.Reference,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetLastPackageRevision gets the latest package revision
|
||||
func GetLastPackageRevision(ctx context.Context, ownerID int64, ref *conan_module.PackageReference) (*PropertyValue, error) {
|
||||
revisions, err := GetPackageRevisions(ctx, ownerID, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(revisions) == 0 {
|
||||
return nil, ErrPackageReferenceNotExist
|
||||
}
|
||||
return revisions[0], nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package conan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
"gitea.dev/modules/container"
|
||||
conan_module "gitea.dev/modules/packages/conan"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// buildCondition creates a Like condition if a wildcard is present. Otherwise Eq is used.
|
||||
func buildCondition(name, value string) builder.Cond {
|
||||
if strings.Contains(value, "*") {
|
||||
return builder.Like{name, strings.ReplaceAll(strings.ReplaceAll(value, "_", "\\_"), "*", "%")}
|
||||
}
|
||||
return builder.Eq{name: value}
|
||||
}
|
||||
|
||||
type RecipeSearchOptions struct {
|
||||
OwnerID int64
|
||||
Name string
|
||||
Version string
|
||||
User string
|
||||
Channel string
|
||||
}
|
||||
|
||||
// SearchRecipes gets all recipes matching the search options
|
||||
func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package_file.is_lead": true,
|
||||
"package.type": packages.TypeConan,
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
if opts.Name != "" {
|
||||
cond = cond.And(buildCondition("package.lower_name", strings.ToLower(opts.Name)))
|
||||
}
|
||||
if opts.Version != "" {
|
||||
cond = cond.And(buildCondition("package_version.lower_version", strings.ToLower(opts.Version)))
|
||||
}
|
||||
if opts.User != "" || opts.Channel != "" {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
|
||||
|
||||
count := 0
|
||||
propsCondBlock := builder.NewCond()
|
||||
if opts.User != "" {
|
||||
count++
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{"package_property.name": conan_module.PropertyRecipeUser}.And(buildCondition("package_property.value", opts.User)))
|
||||
}
|
||||
if opts.Channel != "" {
|
||||
count++
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{"package_property.name": conan_module.PropertyRecipeChannel}.And(buildCondition("package_property.value", opts.Channel)))
|
||||
}
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
cond = cond.And(builder.Eq{
|
||||
strconv.Itoa(count): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
})
|
||||
}
|
||||
|
||||
query := builder.
|
||||
Select("package.name, package_version.version, package_file.id").
|
||||
From("package_file").
|
||||
InnerJoin("package_version", "package_version.id = package_file.version_id").
|
||||
InnerJoin("package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
results := make([]struct {
|
||||
Name string
|
||||
Version string
|
||||
ID int64
|
||||
}, 0, 5)
|
||||
err := db.GetEngine(ctx).SQL(query).Find(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := make(container.Set[string])
|
||||
for _, info := range results {
|
||||
recipe := fmt.Sprintf("%s/%s", info.Name, info.Version)
|
||||
|
||||
props, _ := packages.GetProperties(ctx, packages.PropertyTypeFile, info.ID)
|
||||
if len(props) > 0 {
|
||||
var (
|
||||
user = ""
|
||||
channel = ""
|
||||
)
|
||||
for _, prop := range props {
|
||||
if prop.Name == conan_module.PropertyRecipeUser {
|
||||
user = prop.Value
|
||||
}
|
||||
if prop.Name == conan_module.PropertyRecipeChannel {
|
||||
channel = prop.Value
|
||||
}
|
||||
}
|
||||
if user != "" && channel != "" {
|
||||
recipe = fmt.Sprintf("%s@%s/%s", recipe, user, channel)
|
||||
}
|
||||
}
|
||||
|
||||
unique.Add(recipe)
|
||||
}
|
||||
|
||||
recipes := make([]string, 0, len(unique))
|
||||
for recipe := range unique {
|
||||
recipes = append(recipes, recipe)
|
||||
}
|
||||
return recipes, nil
|
||||
}
|
||||
|
||||
// GetPackageInfo gets the Conaninfo for a package
|
||||
func GetPackageInfo(ctx context.Context, ownerID int64, ref *conan_module.PackageReference) (string, error) {
|
||||
values, err := findPropertyValues(
|
||||
ctx,
|
||||
conan_module.PropertyPackageInfo,
|
||||
ownerID,
|
||||
ref.Recipe.Name,
|
||||
ref.Recipe.Version,
|
||||
map[string]string{
|
||||
conan_module.PropertyRecipeUser: ref.Recipe.User,
|
||||
conan_module.PropertyRecipeChannel: ref.Recipe.Channel,
|
||||
conan_module.PropertyRecipeRevision: ref.Recipe.Revision,
|
||||
conan_module.PropertyPackageReference: ref.Reference,
|
||||
conan_module.PropertyPackageRevision: ref.Revision,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return "", ErrPackageReferenceNotExist
|
||||
}
|
||||
|
||||
return values[0].Value, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package conda
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
conda_module "gitea.dev/modules/packages/conda"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type FileSearchOptions struct {
|
||||
OwnerID int64
|
||||
Channel string
|
||||
Subdir string
|
||||
Filename string
|
||||
}
|
||||
|
||||
// SearchFiles gets all files matching the search options
|
||||
func SearchFiles(ctx context.Context, opts *FileSearchOptions) ([]*packages.PackageFile, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeConda,
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
if opts.Filename != "" {
|
||||
cond = cond.And(builder.Eq{
|
||||
"package_file.lower_name": strings.ToLower(opts.Filename),
|
||||
})
|
||||
}
|
||||
|
||||
var versionPropsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypePackage,
|
||||
"package_property.name": conda_module.PropertyChannel,
|
||||
"package_property.value": opts.Channel,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(versionPropsCond).From("package_property")))
|
||||
|
||||
var filePropsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
"package_property.name": conda_module.PropertySubdir,
|
||||
"package_property.value": opts.Subdir,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(filePropsCond).From("package_property")))
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
Select("package_file.*").
|
||||
Table("package_file").
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond)
|
||||
|
||||
pfs := make([]*packages.PackageFile, 0, 10)
|
||||
return pfs, sess.Find(&pfs)
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
user_model "gitea.dev/models/user"
|
||||
container_module "gitea.dev/modules/packages/container"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
var ErrContainerBlobNotExist = util.NewNotExistErrorf("container blob does not exist")
|
||||
|
||||
type BlobSearchOptions struct {
|
||||
OwnerID int64
|
||||
Image string
|
||||
Digest string
|
||||
Tag string
|
||||
IsManifest bool
|
||||
OnlyLead bool
|
||||
Repository string
|
||||
}
|
||||
|
||||
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeContainer,
|
||||
}
|
||||
|
||||
if opts.OwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.Image != "" {
|
||||
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Image)})
|
||||
}
|
||||
if opts.Tag != "" {
|
||||
cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)})
|
||||
}
|
||||
if opts.IsManifest {
|
||||
cond = cond.And(builder.Eq{"package_file.lower_name": container_module.ManifestFilename})
|
||||
}
|
||||
if opts.OnlyLead {
|
||||
cond = cond.And(builder.Eq{"package_file.is_lead": true})
|
||||
}
|
||||
if opts.Digest != "" {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
"package_property.name": container_module.PropertyDigest,
|
||||
"package_property.value": opts.Digest,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||
}
|
||||
if opts.Repository != "" {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypePackage,
|
||||
"package_property.name": container_module.PropertyRepository,
|
||||
"package_property.value": opts.Repository,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetContainerBlob gets the container blob matching the blob search options
|
||||
// If multiple matching blobs are found (manifests with the same digest) the first (according to the database) is selected.
|
||||
func GetContainerBlob(ctx context.Context, opts *BlobSearchOptions) (*packages.PackageFileDescriptor, error) {
|
||||
pfds, err := getContainerBlobsLimit(ctx, opts, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(pfds) == 0 {
|
||||
return nil, ErrContainerBlobNotExist
|
||||
}
|
||||
return pfds[0], nil
|
||||
}
|
||||
|
||||
// GetContainerBlobs gets the container blobs matching the blob search options
|
||||
func GetContainerBlobs(ctx context.Context, opts *BlobSearchOptions) ([]*packages.PackageFileDescriptor, error) {
|
||||
return getContainerBlobsLimit(ctx, opts, 0)
|
||||
}
|
||||
|
||||
func getContainerBlobsLimit(ctx context.Context, opts *BlobSearchOptions, limit int) ([]*packages.PackageFileDescriptor, error) {
|
||||
pfs := make([]*packages.PackageFile, 0, limit)
|
||||
sess := db.GetEngine(ctx).
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.toConds())
|
||||
|
||||
if limit > 0 {
|
||||
sess = sess.Limit(limit)
|
||||
}
|
||||
|
||||
if err := sess.Find(&pfs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return packages.GetPackageFileDescriptors(ctx, pfs)
|
||||
}
|
||||
|
||||
// GetManifestVersions gets all package versions representing the matching manifest
|
||||
func GetManifestVersions(ctx context.Context, opts *BlobSearchOptions) ([]*packages.PackageVersion, error) {
|
||||
cond := opts.toConds().And(builder.Eq{"package_version.is_internal": false})
|
||||
|
||||
pvs := make([]*packages.PackageVersion, 0, 10)
|
||||
return pvs, db.GetEngine(ctx).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Join("INNER", "package_file", "package_file.version_id = package_version.id").
|
||||
Where(cond).
|
||||
Find(&pvs)
|
||||
}
|
||||
|
||||
// GetImageTags gets a sorted list of the tags of an image
|
||||
// The result is suitable for the api call.
|
||||
func GetImageTags(ctx context.Context, ownerID int64, image string, n int, last string) ([]string, error) {
|
||||
// Short circuit: n == 0 should return an empty list
|
||||
if n == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeContainer,
|
||||
"package.owner_id": ownerID,
|
||||
"package.lower_name": strings.ToLower(image),
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeVersion,
|
||||
"package_property.name": container_module.PropertyManifestTagged,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.In("package_version.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||
|
||||
if last != "" {
|
||||
cond = cond.And(builder.Gt{"package_version.lower_version": strings.ToLower(last)})
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Select("package_version.lower_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond).
|
||||
Asc("package_version.lower_version")
|
||||
|
||||
var tags []string
|
||||
if n > 0 {
|
||||
sess = sess.Limit(n)
|
||||
|
||||
tags = make([]string, 0, n)
|
||||
} else {
|
||||
tags = make([]string, 0, 10)
|
||||
}
|
||||
|
||||
return tags, sess.Find(&tags)
|
||||
}
|
||||
|
||||
type ImageTagsSearchOptions struct {
|
||||
PackageID int64
|
||||
Query string
|
||||
IsTagged bool
|
||||
Sort packages.VersionSort
|
||||
db.Paginator
|
||||
}
|
||||
|
||||
func (opts *ImageTagsSearchOptions) toConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeContainer,
|
||||
"package.id": opts.PackageID,
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
if opts.Query != "" {
|
||||
cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Query)})
|
||||
}
|
||||
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeVersion,
|
||||
"package_property.name": container_module.PropertyManifestTagged,
|
||||
}
|
||||
|
||||
in := builder.In("package_version.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property"))
|
||||
|
||||
if opts.IsTagged {
|
||||
cond = cond.And(in)
|
||||
} else {
|
||||
cond = cond.And(builder.Not{in})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts *ImageTagsSearchOptions) configureOrderBy(e db.Engine) {
|
||||
switch opts.Sort {
|
||||
case packages.SortVersionDesc:
|
||||
e.Desc("package_version.version")
|
||||
case packages.SortVersionAsc:
|
||||
e.Asc("package_version.version")
|
||||
case packages.SortCreatedAsc:
|
||||
e.Asc("package_version.created_unix")
|
||||
default:
|
||||
e.Desc("package_version.created_unix")
|
||||
}
|
||||
|
||||
// Sort by id for stable order with duplicates in the other field
|
||||
e.Asc("package_version.id")
|
||||
}
|
||||
|
||||
// SearchImageTags gets a sorted list of the tags of an image
|
||||
func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*packages.PackageVersion, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.toConds())
|
||||
|
||||
opts.configureOrderBy(sess)
|
||||
|
||||
if opts.Paginator != nil {
|
||||
db.SetSessionPagination(sess, opts)
|
||||
}
|
||||
|
||||
pvs := make([]*packages.PackageVersion, 0, 10)
|
||||
count, err := sess.FindAndCount(&pvs)
|
||||
return pvs, count, err
|
||||
}
|
||||
|
||||
// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
|
||||
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package_version.is_internal": true,
|
||||
"package_version.lower_version": container_module.UploadVersion,
|
||||
"package.type": packages.TypeContainer,
|
||||
}
|
||||
cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()})
|
||||
|
||||
var pfs []*packages.PackageFile
|
||||
return pfs, db.GetEngine(ctx).
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond).
|
||||
Find(&pfs)
|
||||
}
|
||||
|
||||
// GetRepositories gets a sorted list of all repositories
|
||||
func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeContainer,
|
||||
"package_property.ref_type": packages.PropertyTypePackage,
|
||||
"package_property.name": container_module.PropertyRepository,
|
||||
}
|
||||
|
||||
cond = cond.And(builder.Exists(
|
||||
builder.
|
||||
Select("package_version.id").
|
||||
Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
|
||||
From("package_version"),
|
||||
))
|
||||
|
||||
if last != "" {
|
||||
cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
|
||||
}
|
||||
|
||||
if actor.IsGhost() {
|
||||
actor = nil
|
||||
}
|
||||
|
||||
cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package").
|
||||
Select("package_property.value").
|
||||
Join("INNER", "user", "`user`.id = package.owner_id").
|
||||
Join("INNER", "package_property", "package_property.ref_id = package.id").
|
||||
Where(cond).
|
||||
Asc("package_property.value").
|
||||
Limit(n)
|
||||
|
||||
repositories := make([]string, 0, n)
|
||||
return repositories, sess.Find(&repositories)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cran
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
cran_module "gitea.dev/modules/packages/cran"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type SearchOptions struct {
|
||||
OwnerID int64
|
||||
FileType string
|
||||
Platform string
|
||||
RVersion string
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (opts *SearchOptions) toConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.type": packages.TypeCran,
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
if opts.Filename != "" {
|
||||
cond = cond.And(builder.Eq{"package_file.lower_name": strings.ToLower(opts.Filename)})
|
||||
}
|
||||
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
|
||||
|
||||
count := 1
|
||||
propsCondBlock := builder.Eq{"package_property.name": cran_module.PropertyType}.And(builder.Eq{"package_property.value": opts.FileType})
|
||||
|
||||
if opts.Platform != "" {
|
||||
count += 2
|
||||
propsCondBlock = propsCondBlock.
|
||||
Or(builder.Eq{"package_property.name": cran_module.PropertyPlatform}.And(builder.Eq{"package_property.value": opts.Platform})).
|
||||
Or(builder.Eq{"package_property.name": cran_module.PropertyRVersion}.And(builder.Eq{"package_property.value": opts.RVersion}))
|
||||
}
|
||||
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
cond = cond.And(builder.Eq{
|
||||
strconv.Itoa(count): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
})
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
func SearchLatestVersions(ctx context.Context, opts *SearchOptions) ([]*packages.PackageVersion, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Select("package_version.*").
|
||||
Join("LEFT", "package_version pv2", builder.Expr("package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false)).
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Join("INNER", "package_file", "package_file.version_id = package_version.id").
|
||||
Where(opts.toConds().And(builder.Expr("pv2.id IS NULL"))).
|
||||
Asc("package.name")
|
||||
|
||||
pvs := make([]*packages.PackageVersion, 0, 10)
|
||||
return pvs, sess.Find(&pvs)
|
||||
}
|
||||
|
||||
func SearchFile(ctx context.Context, opts *SearchOptions) (*packages.PackageFile, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Select("package_file.*").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Join("INNER", "package_file", "package_file.version_id = package_version.id").
|
||||
Where(opts.toConds())
|
||||
|
||||
pf := &packages.PackageFile{}
|
||||
if has, err := sess.Get(pf); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, packages.ErrPackageFileNotExist
|
||||
}
|
||||
return pf, nil
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package debian
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/packages"
|
||||
debian_module "gitea.dev/modules/packages/debian"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type PackageSearchOptions struct {
|
||||
OwnerID int64
|
||||
Distribution string
|
||||
Component string
|
||||
Architecture string
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) toCond() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package_file.is_lead": true,
|
||||
"package.type": packages.TypeDebian,
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package.is_internal": false,
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
|
||||
props := make(map[string]string)
|
||||
if opts.Distribution != "" {
|
||||
props[debian_module.PropertyDistribution] = opts.Distribution
|
||||
}
|
||||
if opts.Component != "" {
|
||||
props[debian_module.PropertyComponent] = opts.Component
|
||||
}
|
||||
if opts.Architecture != "" {
|
||||
props[debian_module.PropertyArchitecture] = opts.Architecture
|
||||
}
|
||||
|
||||
if len(props) > 0 {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": packages.PropertyTypeFile,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
|
||||
|
||||
propsCondBlock := builder.NewCond()
|
||||
for name, value := range props {
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{
|
||||
"package_property.name": name,
|
||||
"package_property.value": value,
|
||||
})
|
||||
}
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
cond = cond.And(builder.Eq{
|
||||
strconv.Itoa(len(props)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// ExistPackages tests if there are packages matching the search options
|
||||
func ExistPackages(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_file").
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.toCond()).
|
||||
Exist(new(packages.PackageFile))
|
||||
}
|
||||
|
||||
// SearchPackages gets the packages matching the search options
|
||||
func SearchPackages(ctx context.Context, opts *PackageSearchOptions) ([]*packages.PackageFileDescriptor, error) {
|
||||
var pkgFiles []*packages.PackageFile
|
||||
err := db.GetEngine(ctx).
|
||||
Table("package_file").
|
||||
Select("package_file.*").
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.toCond()).
|
||||
Asc("package.lower_name", "package_version.created_unix").Find(&pkgFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfds := make([]*packages.PackageFileDescriptor, 0, len(pkgFiles))
|
||||
for _, pf := range pkgFiles {
|
||||
pfd, err := packages.GetPackageFileDescriptor(ctx, pf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfds = append(pfds, pfd)
|
||||
}
|
||||
return pfds, nil
|
||||
}
|
||||
|
||||
// GetDistributions gets all available distributions
|
||||
func GetDistributions(ctx context.Context, ownerID int64) ([]string, error) {
|
||||
return packages.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages.TypeDebian,
|
||||
ownerID,
|
||||
packages.PropertyTypeFile,
|
||||
debian_module.PropertyDistribution,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// GetComponents gets all available components for the given distribution
|
||||
func GetComponents(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
|
||||
return packages.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages.TypeDebian,
|
||||
ownerID,
|
||||
packages.PropertyTypeFile,
|
||||
debian_module.PropertyComponent,
|
||||
&packages.DistinctPropertyDependency{
|
||||
Name: debian_module.PropertyDistribution,
|
||||
Value: distribution,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// GetArchitectures gets all available architectures for the given distribution
|
||||
func GetArchitectures(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
|
||||
return packages.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages.TypeDebian,
|
||||
ownerID,
|
||||
packages.PropertyTypeFile,
|
||||
debian_module.PropertyArchitecture,
|
||||
&packages.DistinctPropertyDependency{
|
||||
Name: debian_module.PropertyDistribution,
|
||||
Value: distribution,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/cache"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/packages/alpine"
|
||||
"gitea.dev/modules/packages/arch"
|
||||
"gitea.dev/modules/packages/cargo"
|
||||
"gitea.dev/modules/packages/chef"
|
||||
"gitea.dev/modules/packages/composer"
|
||||
"gitea.dev/modules/packages/conan"
|
||||
"gitea.dev/modules/packages/conda"
|
||||
"gitea.dev/modules/packages/container"
|
||||
"gitea.dev/modules/packages/cran"
|
||||
"gitea.dev/modules/packages/debian"
|
||||
"gitea.dev/modules/packages/helm"
|
||||
"gitea.dev/modules/packages/maven"
|
||||
"gitea.dev/modules/packages/npm"
|
||||
"gitea.dev/modules/packages/nuget"
|
||||
"gitea.dev/modules/packages/pub"
|
||||
"gitea.dev/modules/packages/pypi"
|
||||
"gitea.dev/modules/packages/rpm"
|
||||
"gitea.dev/modules/packages/rubygems"
|
||||
"gitea.dev/modules/packages/swift"
|
||||
"gitea.dev/modules/packages/vagrant"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// PackagePropertyList is a list of package properties
|
||||
type PackagePropertyList []*PackageProperty
|
||||
|
||||
// GetByName gets the first property value with the specific name
|
||||
func (l PackagePropertyList) GetByName(name string) string {
|
||||
for _, pp := range l {
|
||||
if pp.Name == name {
|
||||
return pp.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PackageDescriptor describes a package
|
||||
type PackageDescriptor struct {
|
||||
// basic package info
|
||||
Package *Package
|
||||
Owner *user_model.User
|
||||
|
||||
// package version info
|
||||
Repository *repo_model.Repository
|
||||
Version *PackageVersion
|
||||
SemVer *version.Version
|
||||
Creator *user_model.User
|
||||
PackageProperties PackagePropertyList
|
||||
VersionProperties PackagePropertyList
|
||||
Metadata any
|
||||
Files []*PackageFileDescriptor
|
||||
}
|
||||
|
||||
// PackageFileDescriptor describes a package file
|
||||
type PackageFileDescriptor struct {
|
||||
File *PackageFile
|
||||
Blob *PackageBlob
|
||||
Properties PackagePropertyList
|
||||
}
|
||||
|
||||
// PackageWebLink returns the relative package web link
|
||||
func (pd *PackageDescriptor) PackageWebLink() string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// PackageSettingsLink returns the relative package settings link
|
||||
func (pd *PackageDescriptor) PackageSettingsLink() string {
|
||||
return fmt.Sprintf("%s/-/packages/settings/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// VersionWebLink returns the relative package version web link
|
||||
func (pd *PackageDescriptor) VersionWebLink() string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// PackageHTMLURL returns the absolute package HTML URL
|
||||
func (pd *PackageDescriptor) PackageHTMLURL(ctx context.Context) string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(ctx), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// VersionHTMLURL returns the absolute package version HTML URL
|
||||
func (pd *PackageDescriptor) VersionHTMLURL(ctx context.Context) string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(ctx), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// CalculateBlobSize returns the total blobs size in bytes
|
||||
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
|
||||
size := int64(0)
|
||||
for _, f := range pd.Files {
|
||||
size += f.Blob.Size
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// GetPackageDescriptor gets the package description for a version
|
||||
func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) {
|
||||
return GetPackageDescriptorWithCache(ctx, pv, cache.NewEphemeralCache())
|
||||
}
|
||||
|
||||
func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) {
|
||||
p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := cache.GetWithEphemeralCache(ctx, c, "user", p.OwnerID, user_model.GetUserByID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var repository *repo_model.Repository
|
||||
if p.RepoID > 0 {
|
||||
repository, err = cache.GetWithEphemeralCache(ctx, c, "repo", p.RepoID, repo_model.GetRepositoryByID)
|
||||
if err != nil && !repo_model.IsErrRepoNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
creator, err := cache.GetWithEphemeralCache(ctx, c, "user", pv.CreatorID, user_model.GetUserByID)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
creator = user_model.NewGhostUser()
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var semVer *version.Version
|
||||
if p.SemverCompatible {
|
||||
semVer, err = version.NewVersion(pv.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfs, err := GetFilesByVersionID(ctx, pv.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pfds := make([]*PackageFileDescriptor, 0, len(pfs))
|
||||
for _, pf := range pfs {
|
||||
pfd, err := getPackageFileDescriptor(ctx, pf, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfds = append(pfds, pfd)
|
||||
}
|
||||
|
||||
var metadata any
|
||||
switch p.Type {
|
||||
case TypeAlpine:
|
||||
metadata = &alpine.VersionMetadata{}
|
||||
case TypeArch:
|
||||
metadata = &arch.VersionMetadata{}
|
||||
case TypeCargo:
|
||||
metadata = &cargo.Metadata{}
|
||||
case TypeChef:
|
||||
metadata = &chef.Metadata{}
|
||||
case TypeComposer:
|
||||
metadata = &composer.Metadata{}
|
||||
case TypeConan:
|
||||
metadata = &conan.Metadata{}
|
||||
case TypeConda:
|
||||
metadata = &conda.VersionMetadata{}
|
||||
case TypeContainer:
|
||||
metadata = &container.Metadata{}
|
||||
case TypeCran:
|
||||
metadata = &cran.Metadata{}
|
||||
case TypeDebian:
|
||||
metadata = &debian.Metadata{}
|
||||
case TypeGeneric:
|
||||
// generic packages have no metadata
|
||||
case TypeGo:
|
||||
// go packages have no metadata
|
||||
case TypeHelm:
|
||||
metadata = &helm.Metadata{}
|
||||
case TypeNuGet:
|
||||
metadata = &nuget.Metadata{}
|
||||
case TypeNpm:
|
||||
metadata = &npm.Metadata{}
|
||||
case TypeMaven:
|
||||
metadata = &maven.Metadata{}
|
||||
case TypePub:
|
||||
metadata = &pub.Metadata{}
|
||||
case TypePyPI:
|
||||
metadata = &pypi.Metadata{}
|
||||
case TypeRpm:
|
||||
metadata = &rpm.VersionMetadata{}
|
||||
case TypeRubyGems:
|
||||
metadata = &rubygems.Metadata{}
|
||||
case TypeSwift:
|
||||
metadata = &swift.Metadata{}
|
||||
case TypeTerraformState:
|
||||
// terraform packages have no metadata
|
||||
case TypeVagrant:
|
||||
metadata = &vagrant.Metadata{}
|
||||
default:
|
||||
panic("unknown package type: " + string(p.Type))
|
||||
}
|
||||
if metadata != nil {
|
||||
if err := json.Unmarshal([]byte(pv.MetadataJSON), &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &PackageDescriptor{
|
||||
Package: p,
|
||||
Owner: o,
|
||||
Repository: repository,
|
||||
Version: pv,
|
||||
SemVer: semVer,
|
||||
Creator: creator,
|
||||
PackageProperties: PackagePropertyList(pps),
|
||||
VersionProperties: PackagePropertyList(pvps),
|
||||
Metadata: metadata,
|
||||
Files: pfds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPackageFileDescriptor gets a package file descriptor for a package file
|
||||
func GetPackageFileDescriptor(ctx context.Context, pf *PackageFile) (*PackageFileDescriptor, error) {
|
||||
return getPackageFileDescriptor(ctx, pf, cache.NewEphemeralCache())
|
||||
}
|
||||
|
||||
func getPackageFileDescriptor(ctx context.Context, pf *PackageFile, c *cache.EphemeralCache) (*PackageFileDescriptor, error) {
|
||||
pb, err := cache.GetWithEphemeralCache(ctx, c, "package_file_blob", pf.BlobID, GetBlobByID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfps, err := GetProperties(ctx, PropertyTypeFile, pf.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PackageFileDescriptor{
|
||||
pf,
|
||||
pb,
|
||||
PackagePropertyList(pfps),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPackageFileDescriptors gets the package file descriptors for the package files
|
||||
func GetPackageFileDescriptors(ctx context.Context, pfs []*PackageFile) ([]*PackageFileDescriptor, error) {
|
||||
pfds := make([]*PackageFileDescriptor, 0, len(pfs))
|
||||
for _, pf := range pfs {
|
||||
pfd, err := GetPackageFileDescriptor(ctx, pf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfds = append(pfds, pfd)
|
||||
}
|
||||
return pfds, nil
|
||||
}
|
||||
|
||||
// GetPackageDescriptors gets the package descriptions for the versions
|
||||
func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*PackageDescriptor, error) {
|
||||
return getPackageDescriptors(ctx, pvs, cache.NewEphemeralCache())
|
||||
}
|
||||
|
||||
// GetAllPackageDescriptors gets all package descriptors for a package
|
||||
func GetAllPackageDescriptors(ctx context.Context, p *Package) ([]*PackageDescriptor, error) {
|
||||
pvs := make([]*PackageVersion, 0, 10)
|
||||
if err := db.GetEngine(ctx).Where("package_id = ?", p.ID).Find(&pvs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getPackageDescriptors(ctx, pvs, cache.NewEphemeralCache())
|
||||
}
|
||||
|
||||
func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) {
|
||||
pds := make([]*PackageDescriptor, 0, len(pvs))
|
||||
for _, pv := range pvs {
|
||||
pd, err := GetPackageDescriptorWithCache(ctx, pv, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pds = append(pds, pd)
|
||||
}
|
||||
return pds, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nuget
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
packages_model "gitea.dev/models/packages"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
func SearchVersions(ctx context.Context, opts *packages_model.PackageSearchOptions) ([]*packages_model.PackageVersion, int64, error) {
|
||||
cond := toConds(opts)
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
total, err := e.
|
||||
Where(cond).
|
||||
Count(&packages_model.Package{})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
inner := builder.
|
||||
Dialect(db.BuilderDialect()). // builder needs the sql dialect to build the Limit() below
|
||||
Select("*").
|
||||
From("package").
|
||||
Where(cond).
|
||||
OrderBy("package.name ASC")
|
||||
if opts.Paginator != nil {
|
||||
skip, take := opts.Paginator.GetSkipTake()
|
||||
inner = inner.Limit(take, skip)
|
||||
}
|
||||
|
||||
sess := e.
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", inner, "package.id = package_version.package_id")
|
||||
|
||||
pvs := make([]*packages_model.PackageVersion, 0, 10)
|
||||
return pvs, total, sess.Find(&pvs)
|
||||
}
|
||||
|
||||
// CountPackages counts all packages matching the search options
|
||||
func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(toConds(opts)).
|
||||
Count(&packages_model.Package{})
|
||||
}
|
||||
|
||||
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.is_internal": opts.IsInternal.Value(),
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package.type": packages_model.TypeNuGet,
|
||||
}
|
||||
if opts.Name.Value != "" {
|
||||
if opts.Name.ExactMatch {
|
||||
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
|
||||
} else {
|
||||
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Package))
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrDuplicatePackage indicates a duplicated package error
|
||||
ErrDuplicatePackage = util.NewAlreadyExistErrorf("package already exists")
|
||||
// ErrPackageNotExist indicates a package not exist error
|
||||
ErrPackageNotExist = util.NewNotExistErrorf("package does not exist")
|
||||
)
|
||||
|
||||
// Type of a package
|
||||
type Type string
|
||||
|
||||
// List of supported packages
|
||||
const (
|
||||
TypeAlpine Type = "alpine"
|
||||
TypeArch Type = "arch"
|
||||
TypeCargo Type = "cargo"
|
||||
TypeChef Type = "chef"
|
||||
TypeComposer Type = "composer"
|
||||
TypeConan Type = "conan"
|
||||
TypeConda Type = "conda"
|
||||
TypeContainer Type = "container"
|
||||
TypeCran Type = "cran"
|
||||
TypeDebian Type = "debian"
|
||||
TypeGeneric Type = "generic"
|
||||
TypeGo Type = "go"
|
||||
TypeHelm Type = "helm"
|
||||
TypeMaven Type = "maven"
|
||||
TypeNpm Type = "npm"
|
||||
TypeNuGet Type = "nuget"
|
||||
TypePub Type = "pub"
|
||||
TypePyPI Type = "pypi"
|
||||
TypeRpm Type = "rpm"
|
||||
TypeRubyGems Type = "rubygems"
|
||||
TypeSwift Type = "swift"
|
||||
TypeTerraformState Type = "terraform"
|
||||
TypeVagrant Type = "vagrant"
|
||||
)
|
||||
|
||||
var TypeList = []Type{
|
||||
TypeAlpine,
|
||||
TypeArch,
|
||||
TypeCargo,
|
||||
TypeChef,
|
||||
TypeComposer,
|
||||
TypeConan,
|
||||
TypeConda,
|
||||
TypeContainer,
|
||||
TypeCran,
|
||||
TypeDebian,
|
||||
TypeGeneric,
|
||||
TypeGo,
|
||||
TypeHelm,
|
||||
TypeMaven,
|
||||
TypeNpm,
|
||||
TypeNuGet,
|
||||
TypePub,
|
||||
TypePyPI,
|
||||
TypeRpm,
|
||||
TypeRubyGems,
|
||||
TypeSwift,
|
||||
TypeTerraformState,
|
||||
TypeVagrant,
|
||||
}
|
||||
|
||||
// Name gets the name of the package type
|
||||
func (pt Type) Name() string {
|
||||
switch pt {
|
||||
case TypeAlpine:
|
||||
return "Alpine"
|
||||
case TypeArch:
|
||||
return "Arch"
|
||||
case TypeCargo:
|
||||
return "Cargo"
|
||||
case TypeChef:
|
||||
return "Chef"
|
||||
case TypeComposer:
|
||||
return "Composer"
|
||||
case TypeConan:
|
||||
return "Conan"
|
||||
case TypeConda:
|
||||
return "Conda"
|
||||
case TypeContainer:
|
||||
return "Container"
|
||||
case TypeCran:
|
||||
return "CRAN"
|
||||
case TypeDebian:
|
||||
return "Debian"
|
||||
case TypeGeneric:
|
||||
return "Generic"
|
||||
case TypeGo:
|
||||
return "Go"
|
||||
case TypeHelm:
|
||||
return "Helm"
|
||||
case TypeMaven:
|
||||
return "Maven"
|
||||
case TypeNpm:
|
||||
return "npm"
|
||||
case TypeNuGet:
|
||||
return "NuGet"
|
||||
case TypePub:
|
||||
return "Pub"
|
||||
case TypePyPI:
|
||||
return "PyPI"
|
||||
case TypeRpm:
|
||||
return "RPM"
|
||||
case TypeRubyGems:
|
||||
return "RubyGems"
|
||||
case TypeSwift:
|
||||
return "Swift"
|
||||
case TypeTerraformState:
|
||||
return "Terraform State"
|
||||
case TypeVagrant:
|
||||
return "Vagrant"
|
||||
}
|
||||
panic("unknown package type: " + string(pt))
|
||||
}
|
||||
|
||||
// SVGName gets the name of the package type svg image
|
||||
func (pt Type) SVGName() string {
|
||||
switch pt {
|
||||
case TypeAlpine:
|
||||
return "gitea-alpine"
|
||||
case TypeArch:
|
||||
return "gitea-arch"
|
||||
case TypeCargo:
|
||||
return "gitea-cargo"
|
||||
case TypeChef:
|
||||
return "gitea-chef"
|
||||
case TypeComposer:
|
||||
return "gitea-composer"
|
||||
case TypeConan:
|
||||
return "gitea-conan"
|
||||
case TypeConda:
|
||||
return "gitea-conda"
|
||||
case TypeContainer:
|
||||
return "octicon-container"
|
||||
case TypeCran:
|
||||
return "gitea-cran"
|
||||
case TypeDebian:
|
||||
return "gitea-debian"
|
||||
case TypeGeneric:
|
||||
return "octicon-package"
|
||||
case TypeGo:
|
||||
return "gitea-go"
|
||||
case TypeHelm:
|
||||
return "gitea-helm"
|
||||
case TypeMaven:
|
||||
return "gitea-maven"
|
||||
case TypeNpm:
|
||||
return "gitea-npm"
|
||||
case TypeNuGet:
|
||||
return "gitea-nuget"
|
||||
case TypePub:
|
||||
return "gitea-pub"
|
||||
case TypePyPI:
|
||||
return "gitea-python"
|
||||
case TypeRpm:
|
||||
return "gitea-rpm"
|
||||
case TypeRubyGems:
|
||||
return "gitea-rubygems"
|
||||
case TypeSwift:
|
||||
return "gitea-swift"
|
||||
case TypeTerraformState:
|
||||
return "gitea-terraform"
|
||||
case TypeVagrant:
|
||||
return "gitea-vagrant"
|
||||
}
|
||||
panic("unknown package type: " + string(pt))
|
||||
}
|
||||
|
||||
// Package represents a package
|
||||
type Package struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
Type Type `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"NOT NULL"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
SemverCompatible bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsInternal bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
// TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned
|
||||
func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
existing := &Package{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
"owner_id": p.OwnerID,
|
||||
"type": p.Type,
|
||||
"lower_name": p.LowerName,
|
||||
}).Get(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return existing, ErrDuplicatePackage
|
||||
}
|
||||
if _, err = e.Insert(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// DeletePackageByID deletes a package by id
|
||||
func DeletePackageByID(ctx context.Context, packageID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRepositoryLink sets the linked repository
|
||||
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
|
||||
return err
|
||||
}
|
||||
|
||||
func UnlinkRepository(ctx context.Context, packageID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: 0})
|
||||
return err
|
||||
}
|
||||
|
||||
// UnlinkRepositoryFromAllPackages unlinks every package from the repository
|
||||
func UnlinkRepositoryFromAllPackages(ctx context.Context, repoID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Cols("repo_id").Update(&Package{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPackageByID gets a package by id
|
||||
func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
|
||||
p := &Package{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(packageID).Get(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageNotExist
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// UpdatePackageNameByID updates the package's name, it is only for internal usage, for example: rename some legacy packages
|
||||
func UpdatePackageNameByID(ctx context.Context, ownerID int64, packageType Type, packageID int64, name string) error {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.id": packageID,
|
||||
"package.owner_id": ownerID,
|
||||
"package.type": packageType,
|
||||
"package.is_internal": false,
|
||||
}
|
||||
_, err := db.GetEngine(ctx).Where(cond).Update(&Package{Name: name, LowerName: strings.ToLower(name)})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPackageByName gets a package by name
|
||||
func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.owner_id": ownerID,
|
||||
"package.type": packageType,
|
||||
"package.lower_name": strings.ToLower(name),
|
||||
"package.is_internal": false,
|
||||
}
|
||||
|
||||
p := &Package{}
|
||||
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where(cond).
|
||||
Get(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageNotExist
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetPackagesByType gets all packages of a specific type
|
||||
func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]*Package, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.owner_id": ownerID,
|
||||
"package.type": packageType,
|
||||
"package.is_internal": false,
|
||||
}
|
||||
|
||||
ps := make([]*Package, 0, 10)
|
||||
return ps, db.GetEngine(ctx).
|
||||
Where(cond).
|
||||
Find(&ps)
|
||||
}
|
||||
|
||||
// FindUnreferencedPackages gets all packages without associated versions
|
||||
func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
|
||||
in := builder.
|
||||
Select("package.id").
|
||||
From("package").
|
||||
LeftJoin("package_version", "package_version.package_id = package.id").
|
||||
Where(builder.Expr("package_version.id IS NULL"))
|
||||
|
||||
ps := make([]*Package, 0, 10)
|
||||
return ps, db.GetEngine(ctx).
|
||||
// double select workaround for MySQL
|
||||
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
|
||||
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
|
||||
Find(&ps)
|
||||
}
|
||||
|
||||
// ErrUserOwnPackages notifies that the user (still) owns the packages.
|
||||
type ErrUserOwnPackages struct {
|
||||
UID int64
|
||||
}
|
||||
|
||||
// IsErrUserOwnPackages checks if an error is an ErrUserOwnPackages.
|
||||
func IsErrUserOwnPackages(err error) bool {
|
||||
_, ok := err.(ErrUserOwnPackages)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserOwnPackages) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// HasOwnerPackages tests if a user/org has accessible packages
|
||||
func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(builder.Eq{
|
||||
"package_version.is_internal": false,
|
||||
"package.owner_id": ownerID,
|
||||
}).
|
||||
Exist(&PackageVersion{})
|
||||
}
|
||||
|
||||
// HasRepositoryPackages tests if a repository has packages
|
||||
func HasRepositoryPackages(ctx context.Context, repositoryID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Where("repo_id = ?", repositoryID).Exist(&Package{})
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrPackageBlobNotExist indicates a package blob not exist error
|
||||
var ErrPackageBlobNotExist = util.NewNotExistErrorf("package blob does not exist")
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageBlob))
|
||||
}
|
||||
|
||||
// PackageBlob represents a package blob
|
||||
type PackageBlob struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Size int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
HashMD5 string `xorm:"hash_md5 char(32) UNIQUE(md5) INDEX NOT NULL"`
|
||||
HashSHA1 string `xorm:"hash_sha1 char(40) UNIQUE(sha1) INDEX NOT NULL"`
|
||||
HashSHA256 string `xorm:"hash_sha256 char(64) UNIQUE(sha256) INDEX NOT NULL"`
|
||||
HashSHA512 string `xorm:"hash_sha512 char(128) UNIQUE(sha512) INDEX NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
// GetOrInsertBlob inserts a blob. If the blob exists already the existing blob is returned
|
||||
func GetOrInsertBlob(ctx context.Context, pb *PackageBlob) (*PackageBlob, bool, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
existing := &PackageBlob{}
|
||||
|
||||
hashCond := builder.Eq{
|
||||
"size": pb.Size,
|
||||
"hash_md5": pb.HashMD5,
|
||||
"hash_sha1": pb.HashSHA1,
|
||||
"hash_sha256": pb.HashSHA256,
|
||||
"hash_sha512": pb.HashSHA512,
|
||||
}
|
||||
|
||||
has, err := e.Where(hashCond).Get(existing)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if has {
|
||||
return existing, true, nil
|
||||
}
|
||||
if _, err = e.Insert(pb); err != nil {
|
||||
// Handle race condition: another request may have inserted the same blob
|
||||
// between our SELECT and INSERT. Retry the SELECT to get the existing blob.
|
||||
if has, _ = e.Where(hashCond).Get(existing); has {
|
||||
return existing, true, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
return pb, false, nil
|
||||
}
|
||||
|
||||
// GetBlobByID gets a blob by id
|
||||
func GetBlobByID(ctx context.Context, blobID int64) (*PackageBlob, error) {
|
||||
pb := &PackageBlob{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(blobID).Get(pb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageBlobNotExist
|
||||
}
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// ExistPackageBlobWithSHA returns if a package blob exists with the provided sha
|
||||
func ExistPackageBlobWithSHA(ctx context.Context, blobSha256 string) (bool, error) {
|
||||
return db.GetEngine(ctx).Exist(&PackageBlob{
|
||||
HashSHA256: blobSha256,
|
||||
})
|
||||
}
|
||||
|
||||
// FindExpiredUnreferencedBlobs gets all blobs without associated files older than the specific duration
|
||||
func FindExpiredUnreferencedBlobs(ctx context.Context, olderThan time.Duration) ([]*PackageBlob, error) {
|
||||
pbs := make([]*PackageBlob, 0, 10)
|
||||
return pbs, db.GetEngine(ctx).
|
||||
Table("package_blob").
|
||||
Join("LEFT", "package_file", "package_file.blob_id = package_blob.id").
|
||||
Where("package_file.id IS NULL AND package_blob.created_unix < ?", time.Now().Add(-olderThan).Unix()).
|
||||
Find(&pbs)
|
||||
}
|
||||
|
||||
// DeleteBlobByID deletes a blob by id
|
||||
func DeleteBlobByID(ctx context.Context, blobID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(blobID).Delete(&PackageBlob{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTotalBlobSize returns the total blobs size in bytes
|
||||
func GetTotalBlobSize(ctx context.Context) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
SumInt(&PackageBlob{}, "size")
|
||||
}
|
||||
|
||||
// GetTotalUnreferencedBlobSize returns the total size of all unreferenced blobs in bytes
|
||||
func GetTotalUnreferencedBlobSize(ctx context.Context) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_blob").
|
||||
Join("LEFT", "package_file", "package_file.blob_id = package_blob.id").
|
||||
Where("package_file.id IS NULL").
|
||||
SumInt(&PackageBlob{}, "size")
|
||||
}
|
||||
|
||||
// IsBlobAccessibleForUser tests if the user has access to the blob
|
||||
func IsBlobAccessibleForUser(ctx context.Context, blobID int64, user *user_model.User) (bool, error) {
|
||||
if user.IsAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
maxTeamAuthorize := builder.
|
||||
Select("max(team.authorize)").
|
||||
From("team").
|
||||
InnerJoin("team_user", "team_user.team_id = team.id").
|
||||
Where(builder.Eq{"team_user.uid": user.ID}.And(builder.Expr("team_user.org_id = `user`.id")))
|
||||
|
||||
maxTeamUnitAccessMode := builder.
|
||||
Select("max(team_unit.access_mode)").
|
||||
From("team").
|
||||
InnerJoin("team_user", "team_user.team_id = team.id").
|
||||
InnerJoin("team_unit", "team_unit.team_id = team.id").
|
||||
Where(builder.Eq{"team_user.uid": user.ID, "team_unit.type": unit.TypePackages}.And(builder.Expr("team_user.org_id = `user`.id")))
|
||||
|
||||
cond := builder.Eq{"package_blob.id": blobID}.And(
|
||||
// owner = user
|
||||
builder.Eq{"`user`.id": user.ID}.
|
||||
// user can see owner
|
||||
Or(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}.Or(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})).
|
||||
// owner is an organization and user has access to it
|
||||
Or(builder.Eq{"`user`.type": user_model.UserTypeOrganization}.
|
||||
And(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamAuthorize}.Or(builder.Lte{strconv.Itoa(int(perm.AccessModeRead)): maxTeamUnitAccessMode}))),
|
||||
)
|
||||
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_blob").
|
||||
Join("INNER", "package_file", "package_file.blob_id = package_blob.id").
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Join("INNER", "user", "`user`.id = package.owner_id").
|
||||
Where(cond).
|
||||
Exist(&PackageBlob{})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func TestGetOrInsertBlobConcurrent(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testBlob := PackageBlob{
|
||||
Size: 123,
|
||||
HashMD5: "md5",
|
||||
HashSHA1: "sha1",
|
||||
HashSHA256: "sha256",
|
||||
HashSHA512: "sha512",
|
||||
}
|
||||
|
||||
const numGoroutines = 3
|
||||
var wg errgroup.Group
|
||||
results := make([]*PackageBlob, numGoroutines)
|
||||
existed := make([]bool, numGoroutines)
|
||||
for idx := range numGoroutines {
|
||||
wg.Go(func() error {
|
||||
blob := testBlob // Create a copy of the test blob for each goroutine
|
||||
var err error
|
||||
results[idx], existed[idx], err = GetOrInsertBlob(t.Context(), &blob)
|
||||
return err
|
||||
})
|
||||
}
|
||||
require.NoError(t, wg.Wait())
|
||||
|
||||
// then: all GetOrInsertBlob succeeds with the same blob ID, and only one indicates it did not exist before
|
||||
existedCount := 0
|
||||
assert.NotNil(t, results[0])
|
||||
for i := range numGoroutines {
|
||||
assert.Equal(t, results[0].ID, results[i].ID)
|
||||
if existed[i] {
|
||||
existedCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, numGoroutines-1, existedCount)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
// ErrPackageBlobUploadNotExist indicates a package blob upload not exist error
|
||||
var ErrPackageBlobUploadNotExist = util.NewNotExistErrorf("package blob upload does not exist")
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageBlobUpload))
|
||||
}
|
||||
|
||||
// PackageBlobUpload represents a package blob upload
|
||||
type PackageBlobUpload struct {
|
||||
ID string `xorm:"pk"`
|
||||
BytesReceived int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
HashStateBytes []byte `xorm:"BLOB"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
// CreateBlobUpload inserts a blob upload
|
||||
func CreateBlobUpload(ctx context.Context) (*PackageBlobUpload, error) {
|
||||
id := util.CryptoRandomString(25)
|
||||
|
||||
pbu := &PackageBlobUpload{
|
||||
ID: strings.ToLower(id),
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Insert(pbu)
|
||||
return pbu, err
|
||||
}
|
||||
|
||||
// GetBlobUploadByID gets a blob upload by id
|
||||
func GetBlobUploadByID(ctx context.Context, id string) (*PackageBlobUpload, error) {
|
||||
pbu := &PackageBlobUpload{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(pbu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageBlobUploadNotExist
|
||||
}
|
||||
return pbu, nil
|
||||
}
|
||||
|
||||
// UpdateBlobUpload updates the blob upload
|
||||
func UpdateBlobUpload(ctx context.Context, pbu *PackageBlobUpload) error {
|
||||
_, err := db.GetEngine(ctx).ID(pbu.ID).Update(pbu)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteBlobUploadByID deletes the blob upload
|
||||
func DeleteBlobUploadByID(ctx context.Context, id string) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(&PackageBlobUpload{})
|
||||
return err
|
||||
}
|
||||
|
||||
// FindExpiredBlobUploads gets all expired blob uploads
|
||||
func FindExpiredBlobUploads(ctx context.Context, olderThan time.Duration) ([]*PackageBlobUpload, error) {
|
||||
pbus := make([]*PackageBlobUpload, 0, 10)
|
||||
return pbus, db.GetEngine(ctx).
|
||||
Where("updated_unix < ?", time.Now().Add(-olderThan).Unix()).
|
||||
Find(&pbus)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
var ErrPackageCleanupRuleNotExist = util.NewNotExistErrorf("package blob does not exist")
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageCleanupRule))
|
||||
}
|
||||
|
||||
// PackageCleanupRule represents a rule which describes when to clean up package versions
|
||||
type PackageCleanupRule struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Enabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT 0"`
|
||||
Type Type `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
KeepCount int `xorm:"NOT NULL DEFAULT 0"`
|
||||
KeepPattern string `xorm:"NOT NULL DEFAULT ''"`
|
||||
KeepPatternMatcher *regexp.Regexp `xorm:"-"`
|
||||
RemoveDays int `xorm:"NOT NULL DEFAULT 0"`
|
||||
RemovePattern string `xorm:"NOT NULL DEFAULT ''"`
|
||||
RemovePatternMatcher *regexp.Regexp `xorm:"-"`
|
||||
MatchFullName bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL DEFAULT 0"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
func (pcr *PackageCleanupRule) CompiledPattern() error {
|
||||
if pcr.KeepPatternMatcher != nil || pcr.RemovePatternMatcher != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if pcr.KeepPattern != "" {
|
||||
var err error
|
||||
pcr.KeepPatternMatcher, err = regexp.Compile(fmt.Sprintf(`(?i)\A%s\z`, pcr.KeepPattern))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pcr.RemovePattern != "" {
|
||||
var err error
|
||||
pcr.RemovePatternMatcher, err = regexp.Compile(fmt.Sprintf(`(?i)\A%s\z`, pcr.RemovePattern))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InsertCleanupRule(ctx context.Context, pcr *PackageCleanupRule) (*PackageCleanupRule, error) {
|
||||
return pcr, db.Insert(ctx, pcr)
|
||||
}
|
||||
|
||||
func GetCleanupRuleByID(ctx context.Context, id int64) (*PackageCleanupRule, error) {
|
||||
pcr := &PackageCleanupRule{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(pcr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageCleanupRuleNotExist
|
||||
}
|
||||
return pcr, nil
|
||||
}
|
||||
|
||||
func UpdateCleanupRule(ctx context.Context, pcr *PackageCleanupRule) error {
|
||||
_, err := db.GetEngine(ctx).ID(pcr.ID).AllCols().Update(pcr)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetCleanupRulesByOwner(ctx context.Context, ownerID int64) ([]*PackageCleanupRule, error) {
|
||||
pcrs := make([]*PackageCleanupRule, 0, 10)
|
||||
return pcrs, db.GetEngine(ctx).Where("owner_id = ?", ownerID).Find(&pcrs)
|
||||
}
|
||||
|
||||
func DeleteCleanupRuleByID(ctx context.Context, ruleID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(ruleID).Delete(&PackageCleanupRule{})
|
||||
return err
|
||||
}
|
||||
|
||||
func HasOwnerCleanupRuleForPackageType(ctx context.Context, ownerID int64, packageType Type) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where("owner_id = ? AND type = ?", ownerID, packageType).
|
||||
Exist(&PackageCleanupRule{})
|
||||
}
|
||||
|
||||
func IterateEnabledCleanupRules(ctx context.Context, callback func(context.Context, *PackageCleanupRule) error) error {
|
||||
return db.Iterate(
|
||||
ctx,
|
||||
builder.Eq{"enabled": true},
|
||||
callback,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageFile))
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrDuplicatePackageFile indicates a duplicated package file error
|
||||
ErrDuplicatePackageFile = util.NewAlreadyExistErrorf("package file already exists")
|
||||
// ErrPackageFileNotExist indicates a package file not exist error
|
||||
ErrPackageFileNotExist = util.NewNotExistErrorf("package file does not exist")
|
||||
)
|
||||
|
||||
// EmptyFileKey is a named constant for an empty file key
|
||||
const EmptyFileKey = ""
|
||||
|
||||
// PackageFile represents a package file
|
||||
type PackageFile struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
VersionID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
BlobID int64 `xorm:"INDEX NOT NULL"`
|
||||
Name string `xorm:"NOT NULL"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
CompositeKey string `xorm:"UNIQUE(s) INDEX"`
|
||||
IsLead bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
// TryInsertFile inserts a file. If the file exists already ErrDuplicatePackageFile is returned
|
||||
func TryInsertFile(ctx context.Context, pf *PackageFile) (*PackageFile, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
existing := &PackageFile{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
"version_id": pf.VersionID,
|
||||
"lower_name": pf.LowerName,
|
||||
"composite_key": pf.CompositeKey,
|
||||
}).Get(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return existing, ErrDuplicatePackageFile
|
||||
}
|
||||
if _, err = e.Insert(pf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pf, nil
|
||||
}
|
||||
|
||||
// GetFilesByVersionID gets all files of a version
|
||||
func GetFilesByVersionID(ctx context.Context, versionID int64) ([]*PackageFile, error) {
|
||||
pfs := make([]*PackageFile, 0, 10)
|
||||
return pfs, db.GetEngine(ctx).Where("version_id = ?", versionID).OrderBy("lower_name, created_unix, id").Find(&pfs)
|
||||
}
|
||||
|
||||
// GetFileForVersionByID gets a file of a version by id
|
||||
func GetFileForVersionByID(ctx context.Context, versionID, fileID int64) (*PackageFile, error) {
|
||||
pf := &PackageFile{
|
||||
VersionID: versionID,
|
||||
}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(fileID).Get(pf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageFileNotExist
|
||||
}
|
||||
return pf, nil
|
||||
}
|
||||
|
||||
// GetFileForVersionByName gets a file of a version by name
|
||||
func GetFileForVersionByName(ctx context.Context, versionID int64, name, key string) (*PackageFile, error) {
|
||||
if name == "" {
|
||||
return nil, ErrPackageFileNotExist
|
||||
}
|
||||
|
||||
pf := &PackageFile{}
|
||||
|
||||
has, err := db.GetEngine(ctx).Where(builder.Eq{
|
||||
"version_id": versionID,
|
||||
"lower_name": strings.ToLower(name),
|
||||
"composite_key": key,
|
||||
}).Get(pf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageFileNotExist
|
||||
}
|
||||
return pf, nil
|
||||
}
|
||||
|
||||
// DeleteFileByID deletes a file
|
||||
func DeleteFileByID(ctx context.Context, fileID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(fileID).Delete(&PackageFile{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFilesByPackageID deletes all files of a specific package
|
||||
// Versions must not be deleted prior to this call
|
||||
func DeleteFilesByPackageID(ctx context.Context, packageID int64) error {
|
||||
deleteStmt := builder.Delete(builder.In("version_id", builder.Select("package_version.id").From("package_version").Where(builder.Eq{"package_id": packageID}))).From("package_file")
|
||||
_, err := db.GetEngine(ctx).Exec(deleteStmt)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFilesByVersionID deletes all files of a specific version
|
||||
func DeleteFilesByVersionID(ctx context.Context, versionID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("version_id = ?", versionID).Delete(&PackageFile{})
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateFile(ctx context.Context, pf *PackageFile, cols []string) error {
|
||||
_, err := db.GetEngine(ctx).ID(pf.ID).Cols(cols...).Update(pf)
|
||||
return err
|
||||
}
|
||||
|
||||
// PackageFileSearchOptions are options for SearchXXX methods
|
||||
type PackageFileSearchOptions struct {
|
||||
OwnerID int64
|
||||
PackageType Type
|
||||
VersionID int64
|
||||
Query string
|
||||
CompositeKey string
|
||||
Properties map[string]string
|
||||
OlderThan time.Duration
|
||||
HashAlgorithm string
|
||||
Hash string
|
||||
db.Paginator
|
||||
}
|
||||
|
||||
func (opts *PackageFileSearchOptions) toConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
|
||||
if opts.VersionID != 0 {
|
||||
cond = cond.And(builder.Eq{"package_file.version_id": opts.VersionID})
|
||||
} else if opts.OwnerID != 0 || (opts.PackageType != "" && opts.PackageType != "all") {
|
||||
var versionCond builder.Cond = builder.Eq{
|
||||
"package_version.is_internal": false,
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
versionCond = versionCond.And(builder.Eq{"package.owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.PackageType != "" && opts.PackageType != "all" {
|
||||
versionCond = versionCond.And(builder.Eq{"package.type": opts.PackageType})
|
||||
}
|
||||
|
||||
in := builder.
|
||||
Select("package_version.id").
|
||||
From("package_version").
|
||||
InnerJoin("package", "package.id = package_version.package_id").
|
||||
Where(versionCond)
|
||||
|
||||
cond = cond.And(builder.In("package_file.version_id", in))
|
||||
}
|
||||
if opts.CompositeKey != "" {
|
||||
cond = cond.And(builder.Eq{"package_file.composite_key": opts.CompositeKey})
|
||||
}
|
||||
if opts.Query != "" {
|
||||
cond = cond.And(builder.Like{"package_file.lower_name", strings.ToLower(opts.Query)})
|
||||
}
|
||||
|
||||
if len(opts.Properties) != 0 {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": PropertyTypeFile,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
|
||||
|
||||
propsCondBlock := builder.NewCond()
|
||||
for name, value := range opts.Properties {
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{
|
||||
"package_property.name": name,
|
||||
"package_property.value": value,
|
||||
})
|
||||
}
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
cond = cond.And(builder.Eq{
|
||||
strconv.Itoa(len(opts.Properties)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
})
|
||||
}
|
||||
|
||||
if opts.OlderThan != 0 {
|
||||
cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-opts.OlderThan).Unix()})
|
||||
}
|
||||
|
||||
if opts.Hash != "" {
|
||||
var field string
|
||||
switch strings.ToLower(opts.HashAlgorithm) {
|
||||
case "md5":
|
||||
field = "package_blob.hash_md5"
|
||||
case "sha1":
|
||||
field = "package_blob.hash_sha1"
|
||||
case "sha256":
|
||||
field = "package_blob.hash_sha256"
|
||||
case "sha512":
|
||||
fallthrough
|
||||
default: // default to SHA512 if not specified or unknown
|
||||
field = "package_blob.hash_sha512"
|
||||
}
|
||||
innerCond := builder.
|
||||
Expr("package_blob.id = package_file.blob_id").
|
||||
And(builder.Eq{field: opts.Hash})
|
||||
cond = cond.And(builder.Exists(builder.Select("package_blob.id").From("package_blob").Where(innerCond)))
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
// SearchFiles gets all files of packages matching the search options
|
||||
func SearchFiles(ctx context.Context, opts *PackageFileSearchOptions) ([]*PackageFile, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where(opts.toConds())
|
||||
|
||||
if opts.Paginator != nil {
|
||||
db.SetSessionPagination(sess, opts)
|
||||
}
|
||||
|
||||
pfs := make([]*PackageFile, 0, 10)
|
||||
count, err := sess.FindAndCount(&pfs)
|
||||
return pfs, count, err
|
||||
}
|
||||
|
||||
// HasFiles tests if there are files of packages matching the search options
|
||||
func HasFiles(ctx context.Context, opts *PackageFileSearchOptions) (bool, error) {
|
||||
return db.Exist[PackageFile](ctx, opts.toConds())
|
||||
}
|
||||
|
||||
// CalculateFileSize sums up all blob sizes matching the search options.
|
||||
// It does NOT respect the deduplication of blobs.
|
||||
func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Table("package_file").
|
||||
Where(opts.toConds()).
|
||||
Join("INNER", "package_blob", "package_blob.id = package_file.blob_id").
|
||||
SumInt(new(PackageBlob), "size")
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageProperty))
|
||||
}
|
||||
|
||||
type PropertyType int64
|
||||
|
||||
const (
|
||||
// PropertyTypeVersion means the reference is a package version
|
||||
PropertyTypeVersion PropertyType = iota // 0
|
||||
// PropertyTypeFile means the reference is a package file
|
||||
PropertyTypeFile // 1
|
||||
// PropertyTypePackage means the reference is a package
|
||||
PropertyTypePackage // 2
|
||||
)
|
||||
|
||||
// PackageProperty represents a property of a package, version or file
|
||||
type PackageProperty struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RefType PropertyType `xorm:"INDEX NOT NULL"`
|
||||
RefID int64 `xorm:"INDEX NOT NULL"`
|
||||
Name string `xorm:"INDEX NOT NULL"`
|
||||
Value string `xorm:"LONGTEXT NOT NULL"`
|
||||
}
|
||||
|
||||
// InsertProperty creates a property
|
||||
func InsertProperty(ctx context.Context, refType PropertyType, refID int64, name, value string) (*PackageProperty, error) {
|
||||
pp := &PackageProperty{
|
||||
RefType: refType,
|
||||
RefID: refID,
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Insert(pp)
|
||||
return pp, err
|
||||
}
|
||||
|
||||
// GetProperties gets all properties
|
||||
func GetProperties(ctx context.Context, refType PropertyType, refID int64) ([]*PackageProperty, error) {
|
||||
pps := make([]*PackageProperty, 0, 10)
|
||||
return pps, db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ?", refType, refID).OrderBy("id").Find(&pps)
|
||||
}
|
||||
|
||||
// GetPropertiesByName gets all properties with a specific name
|
||||
func GetPropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) ([]*PackageProperty, error) {
|
||||
pps := make([]*PackageProperty, 0, 10)
|
||||
return pps, db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).OrderBy("id").Find(&pps)
|
||||
}
|
||||
|
||||
// UpdateProperty updates a property
|
||||
func UpdateProperty(ctx context.Context, pp *PackageProperty) error {
|
||||
_, err := db.GetEngine(ctx).ID(pp.ID).Update(pp)
|
||||
return err
|
||||
}
|
||||
|
||||
func InsertOrUpdateProperty(ctx context.Context, refType PropertyType, refID int64, name, value string) error {
|
||||
pp := PackageProperty{RefType: refType, RefID: refID, Name: name}
|
||||
ok, err := db.GetEngine(ctx).Get(&pp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
_, err = db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", refType, refID, name).Cols("value").Update(&PackageProperty{Value: value})
|
||||
return err
|
||||
}
|
||||
_, err = InsertProperty(ctx, refType, refID, name, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAllProperties deletes all properties of a ref
|
||||
func DeleteAllProperties(ctx context.Context, refType PropertyType, refID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ?", refType, refID).Delete(&PackageProperty{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePropertiesByPackageID deletes properties of a typed linked to the package
|
||||
// Use to avoid for loops in mass deletion of properties
|
||||
func DeletePropertiesByPackageID(ctx context.Context, refType PropertyType, packageID int64) error {
|
||||
var deleteStmt *builder.Builder
|
||||
|
||||
switch refType {
|
||||
case PropertyTypeFile:
|
||||
deleteStmt = builder.Delete(
|
||||
// Delete all properties that are attached to a file and are in ids from a subquery
|
||||
// which returns ids from the package_file table joined on package_version to link it with package id
|
||||
builder.Eq{"ref_type": PropertyTypeFile}, builder.In("ref_id",
|
||||
builder.Select("package_file.id").From("package_file").
|
||||
LeftJoin("package_version", "package_file.version_id = package_version.id").
|
||||
Where(builder.Eq{"package_version.package_id": packageID}))).From("package_property")
|
||||
case PropertyTypeVersion:
|
||||
// Delete all properties that are attached to a version and are in ids from subquery to the package_version filtered by package id
|
||||
deleteStmt = builder.Delete(
|
||||
builder.Eq{"ref_type": PropertyTypeVersion}, builder.In("ref_id",
|
||||
builder.Select("package_version.id").From("package_version").
|
||||
Where(builder.Eq{"package_version.package_id": packageID}))).From("package_property")
|
||||
case PropertyTypePackage:
|
||||
// Delete all properties that are attached to a package and their reference links to the given package ID
|
||||
deleteStmt = builder.Delete(
|
||||
builder.Eq{"ref_type": PropertyTypePackage}, builder.Eq{"ref_id": packageID}).
|
||||
From("package_property")
|
||||
default:
|
||||
return errors.New("invalid ref type")
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Exec(deleteStmt)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFilePropertiesByVersionID deletes all file properties linked to specific version
|
||||
func DeleteFilePropertiesByVersionID(ctx context.Context, versionID int64) error {
|
||||
deleteStmt := builder.Delete(builder.Eq{"ref_type": PropertyTypeFile}, builder.In("ref_id", builder.Select("id").From("package_file").Where(builder.Eq{"version_id": versionID}))).From("package_property")
|
||||
_, err := db.GetEngine(ctx).Exec(deleteStmt)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePropertyByID deletes a property
|
||||
func DeletePropertyByID(ctx context.Context, propertyID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePropertiesByName deletes properties by name
|
||||
func DeletePropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
|
||||
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
|
||||
return err
|
||||
}
|
||||
|
||||
type DistinctPropertyDependency struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// GetDistinctPropertyValues returns all distinct property values for a given type.
|
||||
// Optional: Search only in dependence of another property.
|
||||
func GetDistinctPropertyValues(ctx context.Context, packageType Type, ownerID int64, refType PropertyType, propertyName string, dep *DistinctPropertyDependency) ([]string, error) {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": refType,
|
||||
"package_property.name": propertyName,
|
||||
"package.type": packageType,
|
||||
"package.owner_id": ownerID,
|
||||
}
|
||||
if dep != nil {
|
||||
innerCond := builder.
|
||||
Expr("pp.ref_id = package_property.ref_id").
|
||||
And(builder.Eq{
|
||||
"pp.ref_type": refType,
|
||||
"pp.name": dep.Name,
|
||||
"pp.value": dep.Value,
|
||||
})
|
||||
cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond)))
|
||||
}
|
||||
|
||||
values := make([]string, 0, 5)
|
||||
return values, db.GetEngine(ctx).
|
||||
Table("package_property").
|
||||
Distinct("package_property.value").
|
||||
Join("INNER", "package_file", "package_file.id = package_property.ref_id").
|
||||
Join("INNER", "package_version", "package_version.id = package_file.version_id").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(cond).
|
||||
Find(&values)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packages_model "gitea.dev/models/packages"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
|
||||
_ "gitea.dev/models"
|
||||
_ "gitea.dev/models/actions"
|
||||
_ "gitea.dev/models/activities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
||||
|
||||
func TestHasOwnerPackages(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
p, err := packages_model.TryInsertPackage(t.Context(), &packages_model.Package{
|
||||
OwnerID: owner.ID,
|
||||
LowerName: "package",
|
||||
})
|
||||
assert.NotNil(t, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package without package versions gets automatically cleaned up and should return false
|
||||
has, err := packages_model.HasOwnerPackages(t.Context(), owner.ID)
|
||||
assert.False(t, has)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pv, err := packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
LowerVersion: "internal",
|
||||
IsInternal: true,
|
||||
})
|
||||
assert.NotNil(t, pv)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package with an internal package version gets automatically cleaned up and should return false
|
||||
has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID)
|
||||
assert.False(t, has)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pv, err = packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
LowerVersion: "normal",
|
||||
IsInternal: false,
|
||||
})
|
||||
assert.NotNil(t, pv)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// A package with a normal package version should return true
|
||||
has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID)
|
||||
assert.True(t, has)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrDuplicatePackageVersion indicates a duplicated package version error
|
||||
var ErrDuplicatePackageVersion = util.NewAlreadyExistErrorf("package version already exists")
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(PackageVersion))
|
||||
}
|
||||
|
||||
// PackageVersion represents a package version
|
||||
type PackageVersion struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PackageID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
CreatorID int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
Version string `xorm:"NOT NULL"`
|
||||
LowerVersion string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
|
||||
IsInternal bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
MetadataJSON string `xorm:"metadata_json LONGTEXT"`
|
||||
DownloadCount int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
// IsPrerelease checks if the version is a prerelease version according to semantic versioning
|
||||
func (pv *PackageVersion) IsPrerelease() bool {
|
||||
if pv == nil || pv.Version == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(pv.Version, "-")
|
||||
}
|
||||
|
||||
// GetOrInsertVersion inserts a version. If the same version exist already ErrDuplicatePackageVersion is returned
|
||||
func GetOrInsertVersion(ctx context.Context, pv *PackageVersion) (*PackageVersion, error) {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
existing := &PackageVersion{}
|
||||
|
||||
has, err := e.Where(builder.Eq{
|
||||
"package_id": pv.PackageID,
|
||||
"lower_version": pv.LowerVersion,
|
||||
}).Get(existing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return existing, ErrDuplicatePackageVersion
|
||||
}
|
||||
if _, err = e.Insert(pv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
// UpdateVersion updates a version
|
||||
func UpdateVersion(ctx context.Context, pv *PackageVersion) error {
|
||||
_, err := db.GetEngine(ctx).ID(pv.ID).Update(pv)
|
||||
return err
|
||||
}
|
||||
|
||||
// IncrementDownloadCounter increments the download counter of a version
|
||||
func IncrementDownloadCounter(ctx context.Context, versionID int64) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `package_version` SET `download_count` = `download_count` + 1 WHERE `id` = ?", versionID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetVersionByID gets a version by id
|
||||
func GetVersionByID(ctx context.Context, versionID int64) (*PackageVersion, error) {
|
||||
pv := &PackageVersion{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(versionID).Get(pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrPackageNotExist
|
||||
}
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
// GetVersionByNameAndVersion gets a version by name and version number
|
||||
func GetVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string) (*PackageVersion, error) {
|
||||
return getVersionByNameAndVersion(ctx, ownerID, packageType, name, version, false)
|
||||
}
|
||||
|
||||
// GetInternalVersionByNameAndVersion gets a version by name and version number
|
||||
func GetInternalVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string) (*PackageVersion, error) {
|
||||
return getVersionByNameAndVersion(ctx, ownerID, packageType, name, version, true)
|
||||
}
|
||||
|
||||
func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string, isInternal bool) (*PackageVersion, error) {
|
||||
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
Type: packageType,
|
||||
Name: SearchValue{
|
||||
ExactMatch: true,
|
||||
Value: name,
|
||||
},
|
||||
Version: SearchValue{
|
||||
ExactMatch: true,
|
||||
Value: version,
|
||||
},
|
||||
IsInternal: optional.Some(isInternal),
|
||||
Paginator: db.NewAbsoluteListOptions(0, 1),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pvs) == 0 {
|
||||
return nil, ErrPackageNotExist
|
||||
}
|
||||
return pvs[0], nil
|
||||
}
|
||||
|
||||
// GetVersionsByPackageType gets all versions of a specific type
|
||||
func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) {
|
||||
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
Type: packageType,
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
return pvs, err
|
||||
}
|
||||
|
||||
// GetVersionsByPackageName gets all versions of a specific package
|
||||
func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Type, name string) ([]*PackageVersion, error) {
|
||||
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
Type: packageType,
|
||||
Name: SearchValue{
|
||||
ExactMatch: true,
|
||||
Value: name,
|
||||
},
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
return pvs, err
|
||||
}
|
||||
|
||||
// DeleteVersionByID deletes a version by id
|
||||
func DeleteVersionByID(ctx context.Context, versionID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(versionID).Delete(&PackageVersion{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteVersionsByPackageID deletes all versions of a specific package
|
||||
func DeleteVersionsByPackageID(ctx context.Context, packageID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where(builder.Eq{"package_id": packageID}).Delete(&PackageVersion{})
|
||||
return err
|
||||
}
|
||||
|
||||
// HasVersionFileReferences checks if there are associated files
|
||||
func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&PackageFile{
|
||||
VersionID: versionID,
|
||||
})
|
||||
}
|
||||
|
||||
// SearchValue describes a value to search
|
||||
// If ExactMatch is true, the field must match the value otherwise a LIKE search is performed.
|
||||
type SearchValue struct {
|
||||
Value string
|
||||
ExactMatch bool
|
||||
}
|
||||
|
||||
type VersionSort = string
|
||||
|
||||
const (
|
||||
SortNameAsc VersionSort = "name_asc"
|
||||
SortNameDesc VersionSort = "name_desc"
|
||||
SortVersionAsc VersionSort = "version_asc"
|
||||
SortVersionDesc VersionSort = "version_desc"
|
||||
SortCreatedAsc VersionSort = "created_asc"
|
||||
SortCreatedDesc VersionSort = "created_desc"
|
||||
)
|
||||
|
||||
// PackageSearchOptions are options for SearchXXX methods
|
||||
// All fields optional and are not used if they have their default value (nil, "", 0)
|
||||
type PackageSearchOptions struct {
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
Type Type
|
||||
PackageID int64
|
||||
Name SearchValue // only results with the specific name are found
|
||||
Version SearchValue // only results with the specific version are found
|
||||
Properties map[string]string // only results are found which contain all listed version properties with the specific value
|
||||
IsInternal optional.Option[bool]
|
||||
HasFileWithName string // only results are found which are associated with a file with the specific name
|
||||
HasFiles optional.Option[bool] // only results are found which have associated files
|
||||
Sort VersionSort
|
||||
Paginator db.Paginator
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.IsInternal.Has() {
|
||||
cond = builder.Eq{
|
||||
"package_version.is_internal": opts.IsInternal.Value(),
|
||||
}
|
||||
}
|
||||
|
||||
if opts.OwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"package.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.Type != "" && opts.Type != "all" {
|
||||
cond = cond.And(builder.Eq{"package.type": opts.Type})
|
||||
}
|
||||
if opts.PackageID != 0 {
|
||||
cond = cond.And(builder.Eq{"package.id": opts.PackageID})
|
||||
}
|
||||
if opts.Name.Value != "" {
|
||||
if opts.Name.ExactMatch {
|
||||
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
|
||||
} else {
|
||||
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
|
||||
}
|
||||
}
|
||||
if opts.Version.Value != "" {
|
||||
if opts.Version.ExactMatch {
|
||||
cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Version.Value)})
|
||||
} else {
|
||||
cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Version.Value)})
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Properties) != 0 {
|
||||
var propsCond builder.Cond = builder.Eq{
|
||||
"package_property.ref_type": PropertyTypeVersion,
|
||||
}
|
||||
propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_version.id"))
|
||||
|
||||
propsCondBlock := builder.NewCond()
|
||||
for name, value := range opts.Properties {
|
||||
propsCondBlock = propsCondBlock.Or(builder.Eq{
|
||||
"package_property.name": name,
|
||||
"package_property.value": value,
|
||||
})
|
||||
}
|
||||
propsCond = propsCond.And(propsCondBlock)
|
||||
|
||||
cond = cond.And(builder.Eq{
|
||||
strconv.Itoa(len(opts.Properties)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
|
||||
})
|
||||
}
|
||||
|
||||
if opts.HasFileWithName != "" {
|
||||
fileCond := builder.Expr("package_file.version_id = package_version.id").And(builder.Eq{"package_file.lower_name": strings.ToLower(opts.HasFileWithName)})
|
||||
|
||||
cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
|
||||
}
|
||||
|
||||
if opts.HasFiles.Has() {
|
||||
filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
|
||||
|
||||
if !opts.HasFiles.Value() {
|
||||
filesCond = builder.Not{filesCond}
|
||||
}
|
||||
|
||||
cond = cond.And(filesCond)
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
||||
switch opts.Sort {
|
||||
case SortNameAsc:
|
||||
e.Asc("package.name")
|
||||
case SortNameDesc:
|
||||
e.Desc("package.name")
|
||||
case SortVersionDesc:
|
||||
e.Desc("package_version.version")
|
||||
case SortVersionAsc:
|
||||
e.Asc("package_version.version")
|
||||
case SortCreatedAsc:
|
||||
e.Asc("package_version.created_unix")
|
||||
default:
|
||||
e.Desc("package_version.created_unix")
|
||||
}
|
||||
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||
}
|
||||
|
||||
func searchVersionsBySession(sess db.Session, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
opts.configureOrderBy(sess)
|
||||
pvs := make([]*PackageVersion, 0, 10)
|
||||
if opts.Paginator != nil {
|
||||
db.SetSessionPagination(sess, opts.Paginator)
|
||||
count, err := sess.FindAndCount(&pvs)
|
||||
return pvs, count, err
|
||||
}
|
||||
err := sess.Find(&pvs)
|
||||
return pvs, int64(len(pvs)), err
|
||||
}
|
||||
|
||||
// SearchVersions gets all versions of packages matching the search options
|
||||
func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Select("package_version.*").
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(opts.ToConds())
|
||||
return searchVersionsBySession(sess, opts)
|
||||
}
|
||||
|
||||
// SearchLatestVersions gets the latest version of every package matching the search options
|
||||
func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
|
||||
in := builder.
|
||||
Select("MAX(package_version.id)").
|
||||
From("package_version").
|
||||
InnerJoin("package", "package.id = package_version.package_id").
|
||||
Where(opts.ToConds()).
|
||||
GroupBy("package_version.package_id")
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
Select("package_version.*").
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Where(builder.In("package_version.id", in))
|
||||
|
||||
return searchVersionsBySession(sess, opts)
|
||||
}
|
||||
|
||||
// ExistVersion checks if a version matching the search options exist
|
||||
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Exist(new(PackageVersion))
|
||||
}
|
||||
|
||||
// CountVersions counts all versions of packages matching the search options
|
||||
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).
|
||||
Where(opts.ToConds()).
|
||||
Table("package_version").
|
||||
Join("INNER", "package", "package.id = package_version.package_id").
|
||||
Count(new(PackageVersion))
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
packages_model "gitea.dev/models/packages"
|
||||
rpm_module "gitea.dev/modules/packages/rpm"
|
||||
)
|
||||
|
||||
// GetGroups gets all available groups
|
||||
func GetGroups(ctx context.Context, ownerID int64) ([]string, error) {
|
||||
return packages_model.GetDistinctPropertyValues(
|
||||
ctx,
|
||||
packages_model.TypeRpm,
|
||||
ownerID,
|
||||
packages_model.PropertyTypeFile,
|
||||
rpm_module.PropertyGroup,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user