forgejo/models/packages/package.go
Julian Schlarb 21151ea5ce
Some checks are pending
/ release (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
fix: maven use groupId:artifactId for package name concatenation (#6352)
Second part of #6327 to fix the Maven package naming. This pull request includes:
* Changing the group and artifact IDs from being separated by `-` to `:` as suggested by [Maven](https://maven.apache.org/pom.html#Maven_Coordinates).
* Making Maven package names case-sensitive
* Migrating the database to:
  * Handle collisions of package names (e.g., groupId: foo- with artifactId: bar and groupId: foo with artifactId: -bar) by moving them into their own packages.
  * Fix the missing group ID issue (#6329).
  * Update  lower_name to match the name value for maven pkgs to make it case-sensetive.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6352
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Julian Schlarb <julian.schlarb@denktmit.de>
Co-committed-by: Julian Schlarb <julian.schlarb@denktmit.de>
2025-06-01 09:02:29 +02:00

371 lines
8.7 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package packages
import (
"context"
"fmt"
"strings"
"forgejo.org/models/db"
"forgejo.org/modules/util"
"xorm.io/builder"
"xorm.io/xorm"
)
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"
TypeAlt Type = "alt"
TypeRubyGems Type = "rubygems"
TypeSwift Type = "swift"
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,
TypeAlt,
TypeRubyGems,
TypeSwift,
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 TypeAlt:
return "Alt"
case TypeRubyGems:
return "RubyGems"
case TypeSwift:
return "Swift"
case TypeVagrant:
return "Vagrant"
}
panic(fmt.Sprintf("unknown package type: %s", 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 TypeAlt:
return "gitea-alt"
case TypeRubyGems:
return "gitea-rubygems"
case TypeSwift:
return "gitea-swift"
case TypeVagrant:
return "gitea-vagrant"
}
panic(fmt.Sprintf("unknown package type: %s", 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"`
}
func ResolvePackageName(name string, t Type) string {
switch t {
case TypeMaven:
return name
default:
return strings.ToLower(name)
}
}
// 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 {
n, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
if n == 0 && err == nil {
return ErrPackageNotExist
}
return err
}
// SetRepositoryLink sets the linked repository
func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
n, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
if n == 0 && err == nil {
return ErrPackageNotExist
}
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
}
// 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": ResolvePackageName(name, packageType),
"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) ([]int64, error) {
var pIDs []int64
if err := db.GetEngine(ctx).
Select("package.id").
Table("package").
Join("LEFT", "package_version", "package_version.package_id = package.id").
Where("package_version.id IS NULL").
Find(&pIDs); err != nil {
return nil, err
}
return pIDs, nil
}
func getPackages(ctx context.Context) *xorm.Session {
return db.GetEngine(ctx).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Where("package_version.is_internal = ?", false)
}
func getOwnerPackages(ctx context.Context, ownerID int64) *xorm.Session {
return getPackages(ctx).
Where("package.owner_id = ?", ownerID)
}
// HasOwnerPackages tests if a user/org has accessible packages
func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
return getOwnerPackages(ctx, ownerID).
Exist(&Package{})
}
// CountOwnerPackages counts user/org accessible packages
func CountOwnerPackages(ctx context.Context, ownerID int64) (int64, error) {
return getOwnerPackages(ctx, ownerID).
Distinct("package.id").
Count(&Package{})
}
func getRepositoryPackages(ctx context.Context, repositoryID int64) *xorm.Session {
return getPackages(ctx).
Where("package.repo_id = ?", repositoryID)
}
// HasRepositoryPackages tests if a repository has packages
func HasRepositoryPackages(ctx context.Context, repositoryID int64) (bool, error) {
return getRepositoryPackages(ctx, repositoryID).
Exist(&PackageVersion{})
}
// CountRepositoryPackages counts packages of a repository
func CountRepositoryPackages(ctx context.Context, repositoryID int64) (int64, error) {
return getRepositoryPackages(ctx, repositoryID).
Distinct("package.id").
Count(&Package{})
}