2022-03-30 04:42:47 -04:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2022-03-30 04:42:47 -04:00
package packages
import (
"context"
"fmt"
2024-04-08 11:13:04 -04:00
"strconv"
2022-03-30 04:42:47 -04:00
"strings"
"code.gitea.io/gitea/models/db"
2024-04-08 11:13:04 -04:00
"code.gitea.io/gitea/modules/setting"
2022-12-31 06:49:37 -05:00
"code.gitea.io/gitea/modules/util"
2022-03-30 04:42:47 -04:00
"xorm.io/builder"
)
func init ( ) {
db . RegisterModel ( new ( Package ) )
}
var (
// ErrDuplicatePackage indicates a duplicated package error
2022-12-31 06:49:37 -05:00
ErrDuplicatePackage = util . NewAlreadyExistErrorf ( "package already exists" )
2022-03-30 04:42:47 -04:00
// ErrPackageNotExist indicates a package not exist error
2022-12-31 06:49:37 -05:00
ErrPackageNotExist = util . NewNotExistErrorf ( "package does not exist" )
2022-03-30 04:42:47 -04:00
)
// Type of a package
type Type string
// List of supported packages
const (
2023-05-12 13:27:50 -04:00
TypeAlpine Type = "alpine"
2023-02-05 05:12:31 -05:00
TypeCargo Type = "cargo"
2023-02-05 20:49:21 -05:00
TypeChef Type = "chef"
2022-03-30 04:42:47 -04:00
TypeComposer Type = "composer"
TypeConan Type = "conan"
2023-02-01 13:30:39 -05:00
TypeConda Type = "conda"
2022-03-30 04:42:47 -04:00
TypeContainer Type = "container"
2023-05-21 22:57:49 -04:00
TypeCran Type = "cran"
2023-05-02 12:31:35 -04:00
TypeDebian Type = "debian"
2022-03-30 04:42:47 -04:00
TypeGeneric Type = "generic"
2023-05-14 11:38:40 -04:00
TypeGo Type = "go"
2022-04-19 12:55:35 -04:00
TypeHelm Type = "helm"
2022-03-30 04:42:47 -04:00
TypeMaven Type = "maven"
2022-04-19 12:55:35 -04:00
TypeNpm Type = "npm"
TypeNuGet Type = "nuget"
2022-08-07 06:09:54 -04:00
TypePub Type = "pub"
2022-03-30 04:42:47 -04:00
TypePyPI Type = "pypi"
2023-05-05 16:33:37 -04:00
TypeRpm Type = "rpm"
2022-03-30 04:42:47 -04:00
TypeRubyGems Type = "rubygems"
2023-03-13 16:28:39 -04:00
TypeSwift Type = "swift"
2022-08-29 03:04:45 -04:00
TypeVagrant Type = "vagrant"
2022-03-30 04:42:47 -04:00
)
2022-11-20 09:08:38 -05:00
var TypeList = [ ] Type {
2023-05-12 13:27:50 -04:00
TypeAlpine ,
2023-02-05 05:12:31 -05:00
TypeCargo ,
2023-02-05 20:49:21 -05:00
TypeChef ,
2022-11-20 09:08:38 -05:00
TypeComposer ,
TypeConan ,
2023-02-01 13:30:39 -05:00
TypeConda ,
2022-11-20 09:08:38 -05:00
TypeContainer ,
2023-05-21 22:57:49 -04:00
TypeCran ,
2023-05-02 12:31:35 -04:00
TypeDebian ,
2022-11-20 09:08:38 -05:00
TypeGeneric ,
2023-05-14 11:38:40 -04:00
TypeGo ,
2022-11-20 09:08:38 -05:00
TypeHelm ,
TypeMaven ,
TypeNpm ,
TypeNuGet ,
TypePub ,
TypePyPI ,
2023-05-05 16:33:37 -04:00
TypeRpm ,
2022-11-20 09:08:38 -05:00
TypeRubyGems ,
2023-03-13 16:28:39 -04:00
TypeSwift ,
2022-11-20 09:08:38 -05:00
TypeVagrant ,
}
2022-03-30 04:42:47 -04:00
// Name gets the name of the package type
func ( pt Type ) Name ( ) string {
switch pt {
2023-05-12 13:27:50 -04:00
case TypeAlpine :
return "Alpine"
2023-02-05 05:12:31 -05:00
case TypeCargo :
return "Cargo"
2023-02-05 20:49:21 -05:00
case TypeChef :
return "Chef"
2022-03-30 04:42:47 -04:00
case TypeComposer :
return "Composer"
case TypeConan :
return "Conan"
2023-02-01 13:30:39 -05:00
case TypeConda :
return "Conda"
2022-03-30 04:42:47 -04:00
case TypeContainer :
return "Container"
2023-05-21 22:57:49 -04:00
case TypeCran :
return "CRAN"
2023-05-02 12:31:35 -04:00
case TypeDebian :
return "Debian"
2022-03-30 04:42:47 -04:00
case TypeGeneric :
return "Generic"
2023-05-14 11:38:40 -04:00
case TypeGo :
return "Go"
2022-04-19 12:55:35 -04:00
case TypeHelm :
return "Helm"
2022-03-30 04:42:47 -04:00
case TypeMaven :
return "Maven"
2022-04-19 12:55:35 -04:00
case TypeNpm :
return "npm"
case TypeNuGet :
return "NuGet"
2022-08-07 06:09:54 -04:00
case TypePub :
return "Pub"
2022-03-30 04:42:47 -04:00
case TypePyPI :
return "PyPI"
2023-05-05 16:33:37 -04:00
case TypeRpm :
return "RPM"
2022-03-30 04:42:47 -04:00
case TypeRubyGems :
return "RubyGems"
2023-03-13 16:28:39 -04:00
case TypeSwift :
return "Swift"
2022-08-29 03:04:45 -04:00
case TypeVagrant :
return "Vagrant"
2022-03-30 04:42:47 -04:00
}
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 {
2023-05-12 13:27:50 -04:00
case TypeAlpine :
return "gitea-alpine"
2023-02-05 05:12:31 -05:00
case TypeCargo :
return "gitea-cargo"
2023-02-05 20:49:21 -05:00
case TypeChef :
return "gitea-chef"
2022-03-30 04:42:47 -04:00
case TypeComposer :
return "gitea-composer"
case TypeConan :
return "gitea-conan"
2023-02-01 13:30:39 -05:00
case TypeConda :
return "gitea-conda"
2022-03-30 04:42:47 -04:00
case TypeContainer :
return "octicon-container"
2023-05-21 22:57:49 -04:00
case TypeCran :
return "gitea-cran"
2023-05-02 12:31:35 -04:00
case TypeDebian :
return "gitea-debian"
2022-03-30 04:42:47 -04:00
case TypeGeneric :
return "octicon-package"
2023-05-14 11:38:40 -04:00
case TypeGo :
return "gitea-go"
2022-04-19 12:55:35 -04:00
case TypeHelm :
return "gitea-helm"
2022-03-30 04:42:47 -04:00
case TypeMaven :
return "gitea-maven"
2022-04-19 12:55:35 -04:00
case TypeNpm :
return "gitea-npm"
case TypeNuGet :
return "gitea-nuget"
2022-08-07 06:09:54 -04:00
case TypePub :
return "gitea-pub"
2022-03-30 04:42:47 -04:00
case TypePyPI :
return "gitea-python"
2023-05-05 16:33:37 -04:00
case TypeRpm :
return "gitea-rpm"
2022-03-30 04:42:47 -04:00
case TypeRubyGems :
return "gitea-rubygems"
2023-03-13 16:28:39 -04:00
case TypeSwift :
return "gitea-swift"
2022-08-29 03:04:45 -04:00
case TypeVagrant :
return "gitea-vagrant"
2022-03-30 04:42:47 -04:00
}
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" `
2023-05-02 12:31:35 -04:00
IsInternal bool ` xorm:"NOT NULL DEFAULT false" `
2022-03-30 04:42:47 -04:00
}
// TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned
func TryInsertPackage ( ctx context . Context , p * Package ) ( * Package , error ) {
2024-04-08 11:13:04 -04:00
switch {
case setting . Database . Type . IsMySQL ( ) :
if _ , err := db . GetEngine ( ctx ) . Exec ( "INSERT INTO package (owner_id,`type`,lower_name,name,semver_compatible) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE 1=1" ,
p . OwnerID , p . Type , p . LowerName , p . Name , p . SemverCompatible ) ; err != nil {
return nil , err
}
case setting . Database . Type . IsPostgreSQL ( ) , setting . Database . Type . IsSQLite3 ( ) :
if _ , err := db . GetEngine ( ctx ) . Exec ( "INSERT INTO package (owner_id,`type`,lower_name,name,semver_compatible) VALUES (?,?,?,?,?) ON CONFLICT (owner_id,`type`,lower_name) DO UPDATE SET lower_name=lower_name" ,
p . OwnerID , p . Type , p . LowerName , p . Name , p . SemverCompatible ) ; err != nil {
return nil , err
}
case setting . Database . Type . IsMSSQL ( ) :
r := func ( s string ) string {
2024-04-11 00:10:50 -04:00
return strings . ReplaceAll ( s , "'" , "''" )
2024-04-08 11:13:04 -04:00
}
2024-04-11 00:10:50 -04:00
sql := fmt . Sprintf ( `
MERGE INTO package WITH ( HOLDLOCK ) AS target USING (
SELECT
% d AS owner_id ,
' % s ' AS [ type ] ,
' % s ' AS lower_name ,
' % s ' AS name ,
% s AS semver_compatible
) AS source (
owner_id , [ type ] , lower_name , name , semver_compatible
) ON (
target . owner_id = source . owner_id
AND target . [ type ] = source . [ type ]
AND target . lower_name = source . lower_name
) WHEN MATCHED
THEN UPDATE SET 1 = 1
WHEN NOT MATCHED
THEN INSERT (
owner_id , [ type ] , lower_name , name , semver_compatible
) VALUES (
% d , ' % s ' , ' % s ' , ' % s ' , % s
) ` ,
2024-04-08 11:13:04 -04:00
p . OwnerID , r ( string ( p . Type ) ) , r ( p . LowerName ) , r ( p . Name ) , strconv . FormatBool ( p . SemverCompatible ) ,
p . OwnerID , r ( string ( p . Type ) ) , r ( p . LowerName ) , r ( p . Name ) , strconv . FormatBool ( p . SemverCompatible ) ,
)
if _ , err := db . GetEngine ( ctx ) . Exec ( sql ) ; err != nil {
return nil , err
}
2022-03-30 04:42:47 -04:00
2024-04-08 11:13:04 -04:00
}
2022-03-30 04:42:47 -04:00
2024-04-08 11:13:04 -04:00
var existing Package
has , err := db . GetEngine ( ctx ) . Where ( builder . Eq {
2024-01-19 06:37:10 -05:00
"owner_id" : p . OwnerID ,
"type" : p . Type ,
"lower_name" : p . LowerName ,
2024-04-08 11:13:04 -04:00
} ) . Get ( & existing )
2022-03-30 04:42:47 -04:00
if err != nil {
return nil , err
}
if has {
2024-04-08 11:13:04 -04:00
return & existing , ErrDuplicatePackage
2022-03-30 04:42:47 -04:00
}
return p , nil
}
2022-07-27 23:59:39 -04:00
// DeletePackageByID deletes a package by id
func DeletePackageByID ( ctx context . Context , packageID int64 ) error {
_ , err := db . GetEngine ( ctx ) . ID ( packageID ) . Delete ( & Package { } )
return err
}
2022-03-30 04:42:47 -04:00
// 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
}
// 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 {
2023-05-02 12:31:35 -04:00
"package.owner_id" : ownerID ,
"package.type" : packageType ,
"package.lower_name" : strings . ToLower ( name ) ,
"package.is_internal" : false ,
2022-03-30 04:42:47 -04:00
}
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 {
2023-05-02 12:31:35 -04:00
"package.owner_id" : ownerID ,
"package.type" : packageType ,
"package.is_internal" : false ,
2022-03-30 04:42:47 -04:00
}
ps := make ( [ ] * Package , 0 , 10 )
return ps , db . GetEngine ( ctx ) .
Where ( cond ) .
Find ( & ps )
}
2022-07-27 23:59:39 -04:00
// FindUnreferencedPackages gets all packages without associated versions
func FindUnreferencedPackages ( ctx context . Context ) ( [ ] * Package , error ) {
2022-03-30 04:42:47 -04:00
in := builder .
2022-04-05 21:32:09 -04:00
Select ( "package.id" ) .
2022-03-30 04:42:47 -04:00
From ( "package" ) .
2022-04-05 21:32:09 -04:00
LeftJoin ( "package_version" , "package_version.package_id = package.id" ) .
2022-03-30 04:42:47 -04:00
Where ( builder . Expr ( "package_version.id IS NULL" ) )
2022-07-27 23:59:39 -04:00
ps := make ( [ ] * Package , 0 , 10 )
return ps , db . GetEngine ( ctx ) .
2022-04-05 21:32:09 -04:00
// 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" ) ) ) .
2022-07-27 23:59:39 -04:00
Find ( & ps )
2022-03-30 04:42:47 -04:00
}
2022-09-03 12:01:00 -04:00
// HasOwnerPackages tests if a user/org has accessible packages
2022-03-30 04:42:47 -04:00
func HasOwnerPackages ( ctx context . Context , ownerID int64 ) ( bool , error ) {
2022-09-03 12:01:00 -04:00
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 { } )
2022-03-30 04:42:47 -04:00
}
// 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 { } )
}