mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-08 21:58:24 +02:00
Merge 55c66356c1645ddb869bd9fc712a759922950526 into 30c07c20e94551141cc1873ab14bdd4c104bba94
This commit is contained in:
commit
a4e5d5666e
@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
@ -77,6 +78,11 @@ 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))
|
||||
@ -267,6 +273,15 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa
|
||||
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 {
|
||||
|
||||
@ -115,6 +115,20 @@ func DeleteFileByID(ctx context.Context, fileID int64) error {
|
||||
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
|
||||
|
||||
@ -5,6 +5,7 @@ package packages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
||||
@ -86,6 +87,46 @@ func DeleteAllProperties(ctx context.Context, refType PropertyType, refID int64)
|
||||
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{})
|
||||
|
||||
@ -157,6 +157,12 @@ func DeleteVersionByID(ctx context.Context, versionID int64) error {
|
||||
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{
|
||||
|
||||
@ -3615,6 +3615,7 @@
|
||||
"packages.settings.delete": "Delete package",
|
||||
"packages.settings.delete.description": "Deleting a package is permanent and cannot be undone.",
|
||||
"packages.settings.delete.notice": "You are about to delete %s (%s). This operation is irreversible, are you sure?",
|
||||
"packages.settings.delete.notice.package": "You are about to delete %s and all its versions. This operation is irreversible, are you sure?",
|
||||
"packages.settings.delete.success": "The package has been deleted.",
|
||||
"packages.settings.delete.error": "Failed to delete the package.",
|
||||
"packages.owner.settings.cargo.title": "Cargo Registry Index",
|
||||
|
||||
@ -1578,10 +1578,11 @@ func Routes() *web.Router {
|
||||
m.Group("/packages/{username}", func() {
|
||||
m.Group("/{type}/{name}", func() {
|
||||
m.Get("/", packages.ListPackageVersions)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
||||
|
||||
m.Group("/{version}", func() {
|
||||
m.Get("", packages.GetPackage)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackageVersion)
|
||||
m.Get("/files", packages.ListPackageFiles)
|
||||
})
|
||||
|
||||
|
||||
@ -118,7 +118,7 @@ func GetPackage(ctx *context.APIContext) {
|
||||
|
||||
// DeletePackage deletes a package
|
||||
func DeletePackage(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name}/{version} package deletePackage
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name} package deletePackage
|
||||
// ---
|
||||
// summary: Delete a package
|
||||
// parameters:
|
||||
@ -137,6 +137,41 @@ func DeletePackage(ctx *context.APIContext) {
|
||||
// description: name of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := packages_service.RemovePackage(ctx, ctx.Doer, ctx.Package.Descriptor.Package)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeletePackageVersion deletes a package version
|
||||
func DeletePackageVersion(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name}/{version} package deletePackageVersion
|
||||
// ---
|
||||
// summary: Delete a package version
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: type
|
||||
// in: path
|
||||
// description: type of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: name
|
||||
// in: path
|
||||
// description: name of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: version
|
||||
// in: path
|
||||
// description: version of the package
|
||||
|
||||
@ -491,18 +491,37 @@ func packageSettingsPostActionLink(ctx *context.Context, form *forms.PackageSett
|
||||
}
|
||||
|
||||
func packageSettingsPostActionDelete(ctx *context.Context) {
|
||||
err := packages_service.RemovePackageVersion(ctx, ctx.Doer, ctx.Package.Descriptor.Version)
|
||||
if err != nil {
|
||||
pd := ctx.Package.Descriptor
|
||||
|
||||
if err := packages_service.RemovePackage(ctx, ctx.Doer, pd.Package); err != nil {
|
||||
log.Error("Error deleting package: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("packages.settings.delete.error"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages")
|
||||
}
|
||||
|
||||
// PackageVersionDelete deletes a package version
|
||||
func PackageVersionDelete(ctx *context.Context) {
|
||||
pd := ctx.Package.Descriptor
|
||||
if pd.Version == nil {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := packages_service.RemovePackageVersion(ctx, ctx.Doer, pd.Version); err != nil {
|
||||
log.Error("Error deleting package version: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("packages.settings.delete.error"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
|
||||
}
|
||||
|
||||
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
|
||||
// redirect to the package if there are still versions available
|
||||
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: optional.Some(false)}); has {
|
||||
redirectURL = ctx.Package.Descriptor.PackageWebLink()
|
||||
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: pd.Package.ID, IsInternal: optional.Some(false)}); has {
|
||||
redirectURL = pd.PackageWebLink()
|
||||
}
|
||||
|
||||
ctx.Redirect(redirectURL)
|
||||
@ -512,7 +531,7 @@ func packageSettingsPostActionDelete(ctx *context.Context) {
|
||||
func DownloadPackageFile(ctx *context.Context) {
|
||||
pf, err := packages_model.GetFileForVersionByID(ctx, ctx.Package.Descriptor.Version.ID, ctx.PathParamInt64("fileid"))
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageFileNotExist {
|
||||
if errors.Is(err, packages_model.ErrPackageFileNotExist) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.ServerError("GetFileForVersionByID", err)
|
||||
|
||||
@ -1071,14 +1071,15 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("/versions", user.ListPackageVersions)
|
||||
m.Group("/{version}", func() {
|
||||
m.Get("", user.ViewPackageVersion)
|
||||
m.Post("", reqPackageAccess(perm.AccessModeWrite), user.PackageVersionDelete)
|
||||
m.Get("/{version_sub}", user.ViewPackageVersion)
|
||||
m.Get("/files/{fileid}", user.DownloadPackageFile)
|
||||
m.Group("/settings", func() {
|
||||
m.Get("", user.PackageSettings)
|
||||
m.Post("", web.Bind(forms.PackageSettingForm{}), user.PackageSettingsPost)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
})
|
||||
})
|
||||
m.Group("/packages-settings/{type}/{name}", func() {
|
||||
m.Get("", user.PackageSettings)
|
||||
m.Post("", web.Bind(forms.PackageSettingForm{}), user.PackageSettingsPost)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
}, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@ -70,22 +71,39 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
|
||||
|
||||
packageType := ctx.PathParam("type")
|
||||
name := ctx.PathParam("name")
|
||||
version := ctx.PathParam("version")
|
||||
if packageType != "" && name != "" && version != "" {
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
if packageType != "" && name != "" {
|
||||
version := ctx.PathParam("version")
|
||||
if version != "" {
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version)
|
||||
if err != nil {
|
||||
if errors.Is(err, packages_model.ErrPackageNotExist) {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageDescriptor: %w", err))
|
||||
return pkg
|
||||
pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageDescriptor: %w", err))
|
||||
return pkg
|
||||
}
|
||||
} else {
|
||||
p, err := packages_model.GetPackageByName(ctx, pkg.Owner.ID, packages_model.Type(packageType), name)
|
||||
if err != nil {
|
||||
if errors.Is(err, packages_model.ErrPackageNotExist) {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetPackageByName: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageByName: %w", err))
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
pkg.Descriptor = &packages_model.PackageDescriptor{
|
||||
Package: p,
|
||||
Owner: pkg.Owner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -532,16 +532,11 @@ func DeletePackageVersionAndReferences(ctx context.Context, pv *packages_model.P
|
||||
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
|
||||
if err != nil {
|
||||
if err := packages_model.DeleteFilePropertiesByVersionID(ctx, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pf := range pfs {
|
||||
if err := DeletePackageFile(ctx, pf); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := packages_model.DeleteFilesByVersionID(ctx, pv.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return packages_model.DeleteVersionByID(ctx, pv.ID)
|
||||
@ -629,6 +624,45 @@ func OpenBlobForDownload(ctx context.Context, pf *packages_model.PackageFile, pb
|
||||
return s, u, pf, nil
|
||||
}
|
||||
|
||||
// RemovePackage deletes the package and all its versions
|
||||
func RemovePackage(ctx context.Context, doer *user_model.User, p *packages_model.Package) error {
|
||||
pds, err := packages_model.GetAllPackageDescriptors(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
err := packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypePackage, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypeFile, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeletePropertiesByPackageID(ctx, packages_model.PropertyTypeVersion, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeleteFilesByPackageID(ctx, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = packages_model.DeleteVersionsByPackageID(ctx, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return packages_model.DeletePackageByID(ctx, p.ID)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pd := range pds {
|
||||
notify_service.PackageDelete(ctx, doer, pd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllPackages for User
|
||||
func RemoveAllPackages(ctx context.Context, userID int64) (int, error) {
|
||||
count := 0
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
{{template "user/overview/header" .}}
|
||||
{{end}}
|
||||
{{template "base/alert" .}}
|
||||
<p><a href="{{.PackageDescriptor.VersionWebLink}}">{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</a> / <strong>{{ctx.Locale.Tr "repo.settings"}}</strong></p>
|
||||
<p><a href="{{.PackageDescriptor.PackageWebLink}}">{{.PackageDescriptor.Package.Name}}</a> / <strong>{{ctx.Locale.Tr "repo.settings"}}</strong></p>
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "packages.settings.link"}}
|
||||
</h4>
|
||||
@ -45,7 +45,7 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message tw-break-anywhere">
|
||||
{{ctx.Locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
||||
{{ctx.Locale.Tr "packages.settings.delete.notice.package" .PackageDescriptor.Package.Name}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
|
||||
@ -99,7 +99,24 @@
|
||||
<div class="item">{{svg "octicon-issue-opened"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
|
||||
{{end}}
|
||||
{{if .CanWritePackages}}
|
||||
<div class="item">{{svg "octicon-tools"}} <a href="{{$packageVersionLink}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
<div class="item">{{svg "octicon-tools"}} <a href="{{.PackageDescriptor.PackageSettingsLink}}">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
<div class="item">
|
||||
{{svg "octicon-trash"}}
|
||||
<a class="show-modal" data-modal="#delete-package-version-modal">{{ctx.Locale.Tr "packages.settings.delete"}}</a>
|
||||
<div class="ui tiny modal" id="delete-package-version-modal">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "packages.settings.delete"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message tw-break-anywhere">
|
||||
{{ctx.Locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
||||
</div>
|
||||
<form class="ui form" action="{{$packageVersionLink}}" method="post">
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
42
templates/swagger/v1_json.tmpl
generated
42
templates/swagger/v1_json.tmpl
generated
@ -3912,6 +3912,44 @@
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"package"
|
||||
],
|
||||
"summary": "Delete a package",
|
||||
"operationId": "deletePackage",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the package",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "type of the package",
|
||||
"name": "type",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the package",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/responses/empty"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/packages/{owner}/{type}/{name}/-/latest": {
|
||||
@ -4097,8 +4135,8 @@
|
||||
"tags": [
|
||||
"package"
|
||||
],
|
||||
"summary": "Delete a package",
|
||||
"operationId": "deletePackage",
|
||||
"summary": "Delete a package version",
|
||||
"operationId": "deletePackageVersion",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
||||
@ -85,6 +85,50 @@ func TestPackageAPI(t *testing.T) {
|
||||
assert.Equal(t, user.Name, p.Creator.UserName)
|
||||
})
|
||||
|
||||
t.Run("DeleteEntirePackage", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
packageName := "test-package-entire-delete"
|
||||
for _, version := range []string{"1.0.1", "1.0.2"} {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)).
|
||||
AddTokenAuth(tokenWritePackage)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("DeletePackageVersion", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
packageName := "test-package-version-delete"
|
||||
for _, version := range []string{"1.0.1", "1.0.2"} {
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version)
|
||||
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)).
|
||||
AddTokenAuth(tokenWritePackage)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, packageName)).
|
||||
AddTokenAuth(tokenReadPackage)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("ListPackageVersions", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
|
||||
118
tests/integration/packages_service_test.go
Normal file
118
tests/integration/packages_service_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRemovePackage(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
// 1. Setup: Create two packages with properties at all levels
|
||||
createPackage := func(name string) (*packages_model.Package, *packages_model.PackageVersion, *packages_model.PackageFile) {
|
||||
data, _ := packages_module.CreateHashedBufferFromReader(bytes.NewReader([]byte{1}))
|
||||
pv, pf, err := packages_service.CreatePackageOrAddFileToExisting(t.Context(), &packages_service.PackageCreationInfo{
|
||||
PackageInfo: packages_service.PackageInfo{
|
||||
Owner: user,
|
||||
PackageType: packages_model.TypeGeneric,
|
||||
Name: name,
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Creator: user,
|
||||
PackageProperties: map[string]string{"pkg_prop": "val"},
|
||||
VersionProperties: map[string]string{"ver_prop": "val"},
|
||||
}, &packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{Filename: "file.bin"},
|
||||
Creator: user,
|
||||
Data: data,
|
||||
Properties: map[string]string{"file_prop": "val"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := packages_model.GetPackageByID(t.Context(), pv.PackageID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return p, pv, pf
|
||||
}
|
||||
|
||||
p1, pv1, pf1 := createPackage("package-1")
|
||||
p2, pv2, pf2 := createPackage("package-2")
|
||||
|
||||
// Verify properties exist before deletion
|
||||
checkProps := func(p *packages_model.Package, pv *packages_model.PackageVersion, pf *packages_model.PackageFile, shouldExist bool) {
|
||||
pps, err := packages_model.GetProperties(t.Context(), packages_model.PropertyTypePackage, p.ID)
|
||||
require.NoError(t, err)
|
||||
if shouldExist {
|
||||
assert.NotEmpty(t, pps)
|
||||
} else {
|
||||
assert.Empty(t, pps)
|
||||
}
|
||||
|
||||
pps, err = packages_model.GetProperties(t.Context(), packages_model.PropertyTypeVersion, pv.ID)
|
||||
require.NoError(t, err)
|
||||
if shouldExist {
|
||||
assert.NotEmpty(t, pps)
|
||||
} else {
|
||||
assert.Empty(t, pps)
|
||||
}
|
||||
|
||||
pps, err = packages_model.GetProperties(t.Context(), packages_model.PropertyTypeFile, pf.ID)
|
||||
require.NoError(t, err)
|
||||
if shouldExist {
|
||||
assert.NotEmpty(t, pps)
|
||||
} else {
|
||||
assert.Empty(t, pps)
|
||||
}
|
||||
}
|
||||
|
||||
checkProps(p1, pv1, pf1, true)
|
||||
checkProps(p2, pv2, pf2, true)
|
||||
|
||||
// 2. Act: Remove package 1
|
||||
err := packages_service.RemovePackage(t.Context(), user, p1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 3. Assert: Package 1 is gone, Package 2 is untouched
|
||||
|
||||
// Check P1
|
||||
_, err = packages_model.GetPackageByID(t.Context(), p1.ID)
|
||||
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
|
||||
|
||||
_, err = packages_model.GetVersionByID(t.Context(), pv1.ID)
|
||||
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
|
||||
|
||||
_, err = packages_model.GetFileForVersionByID(t.Context(), pv1.ID, pf1.ID)
|
||||
assert.ErrorIs(t, err, packages_model.ErrPackageFileNotExist)
|
||||
|
||||
checkProps(p1, pv1, pf1, false)
|
||||
|
||||
// Check P2
|
||||
p2_after, err := packages_model.GetPackageByID(t.Context(), p2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p2_after)
|
||||
|
||||
pv2_after, err := packages_model.GetVersionByID(t.Context(), pv2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pv2_after)
|
||||
|
||||
pf2_after, err := packages_model.GetFileForVersionByID(t.Context(), pv2.ID, pf2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pf2_after)
|
||||
|
||||
checkProps(p2, pv2, pf2, true)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user