diff --git a/modules/structs/package.go b/modules/structs/package.go index e656e26cd0a..9f8dba1623f 100644 --- a/modules/structs/package.go +++ b/modules/structs/package.go @@ -46,4 +46,6 @@ type PackageFile struct { HashSHA256 string `json:"sha256"` // The SHA512 hash of the package file HashSHA512 string `json:"sha512"` + // The type specific properties locating the package file, e.g. the distribution and component of a Debian package + Properties map[string]string `json:"properties,omitempty"` } diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 15c3a62288b..7cfcfe9173b 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -227,7 +227,7 @@ func ListPackageFiles(ctx *context.APIContext) { apiPackageFiles := make([]*api.PackageFile, 0, len(ctx.Package.Descriptor.Files)) for _, pfd := range ctx.Package.Descriptor.Files { - apiPackageFiles = append(apiPackageFiles, convert.ToPackageFile(pfd)) + apiPackageFiles = append(apiPackageFiles, convert.ToPackageFile(ctx.Package.Descriptor.Package.Type, pfd)) } ctx.JSON(http.StatusOK, apiPackageFiles) diff --git a/services/convert/package.go b/services/convert/package.go index b24f3e93416..dea499cfc9b 100644 --- a/services/convert/package.go +++ b/services/convert/package.go @@ -5,11 +5,13 @@ package convert import ( "context" + "slices" "gitea.dev/models/packages" access_model "gitea.dev/models/perm/access" user_model "gitea.dev/models/user" api "gitea.dev/modules/structs" + packages_service "gitea.dev/services/packages" ) // ToPackage convert a packages.PackageDescriptor to api.Package @@ -40,7 +42,14 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m } // ToPackageFile converts packages.PackageFileDescriptor to api.PackageFile -func ToPackageFile(pfd *packages.PackageFileDescriptor) *api.PackageFile { +func ToPackageFile(packageType packages.Type, pfd *packages.PackageFileDescriptor) *api.PackageFile { + properties := make(map[string]string) + publicPropertyNames := packages_service.GetSpecManager().Get(packageType).PublicPropertyNames() + for _, pp := range pfd.Properties { + if slices.Contains(publicPropertyNames, pp.Name) { + properties[pp.Name] = pp.Value + } + } return &api.PackageFile{ ID: pfd.File.ID, Size: pfd.Blob.Size, @@ -49,5 +58,6 @@ func ToPackageFile(pfd *packages.PackageFileDescriptor) *api.PackageFile { HashSHA1: pfd.Blob.HashSHA1, HashSHA256: pfd.Blob.HashSHA256, HashSHA512: pfd.Blob.HashSHA512, + Properties: properties, } } diff --git a/services/packages/alpine/spec.go b/services/packages/alpine/spec.go new file mode 100644 index 00000000000..98a0ce566ef --- /dev/null +++ b/services/packages/alpine/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package alpine + +import ( + alpine_module "gitea.dev/modules/packages/alpine" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{alpine_module.PropertyBranch, alpine_module.PropertyRepository, alpine_module.PropertyArchitecture} +} diff --git a/services/packages/arch/spec.go b/services/packages/arch/spec.go new file mode 100644 index 00000000000..5fe4f1f331d --- /dev/null +++ b/services/packages/arch/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package arch + +import ( + arch_module "gitea.dev/modules/packages/arch" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{arch_module.PropertyRepository, arch_module.PropertyArchitecture} +} diff --git a/services/packages/conan/spec.go b/services/packages/conan/spec.go new file mode 100644 index 00000000000..6069e2c1c5e --- /dev/null +++ b/services/packages/conan/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package conan + +import ( + conan_module "gitea.dev/modules/packages/conan" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{conan_module.PropertyRecipeUser, conan_module.PropertyRecipeChannel, conan_module.PropertyRecipeRevision, conan_module.PropertyPackageReference, conan_module.PropertyPackageRevision} +} diff --git a/services/packages/conda/spec.go b/services/packages/conda/spec.go new file mode 100644 index 00000000000..dcf5c367197 --- /dev/null +++ b/services/packages/conda/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package conda + +import ( + conda_module "gitea.dev/modules/packages/conda" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{conda_module.PropertySubdir} +} diff --git a/services/packages/debian/spec.go b/services/packages/debian/spec.go index cab6d49e80b..afb043d4245 100644 --- a/services/packages/debian/spec.go +++ b/services/packages/debian/spec.go @@ -85,3 +85,7 @@ func (s Specialization) RenderSetupManual(ctx context.Context, pkg *packages_mod w.WriteHTML(``) return w.HTMLString() } + +func (s Specialization) PublicPropertyNames() []string { + return []string{debian_module.PropertyDistribution, debian_module.PropertyComponent, debian_module.PropertyArchitecture} +} diff --git a/services/packages/nuget/spec.go b/services/packages/nuget/spec.go new file mode 100644 index 00000000000..dcf2054ce1c --- /dev/null +++ b/services/packages/nuget/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package nuget + +import ( + nuget_module "gitea.dev/modules/packages/nuget" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{nuget_module.PropertySymbolID} +} diff --git a/services/packages/packages.go b/services/packages/packages.go index e102b52ea6d..9b8bc049a0c 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -35,6 +35,7 @@ var ( ) type Specialization interface { + PublicPropertyNames() []string // only properties locating the file, the others are internal and can be large OnBeforeRemovePackageAll(ctx context.Context, doer *user_model.User, pkg *packages_model.Package, pds []*packages_model.PackageDescriptor) error OnBeforeRemovePackageVersion(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor) error GetViewPackageVersionData(ctx context.Context, pd *packages_model.PackageDescriptor) (any, error) diff --git a/services/packages/pkgspec/manager.go b/services/packages/pkgspec/manager.go index 18fe5306a22..93dc45943eb 100644 --- a/services/packages/pkgspec/manager.go +++ b/services/packages/pkgspec/manager.go @@ -6,14 +6,28 @@ package pkgspec import ( packages_model "gitea.dev/models/packages" packages_service "gitea.dev/services/packages" + "gitea.dev/services/packages/alpine" + "gitea.dev/services/packages/arch" + "gitea.dev/services/packages/conan" + "gitea.dev/services/packages/conda" "gitea.dev/services/packages/debian" + "gitea.dev/services/packages/nuget" + "gitea.dev/services/packages/rpm" "gitea.dev/services/packages/terraform" + "gitea.dev/services/packages/vagrant" ) func InitManager() error { mgr := packages_service.GetSpecManager() + mgr.Add(packages_model.TypeAlpine, &alpine.Specialization{}) + mgr.Add(packages_model.TypeArch, &arch.Specialization{}) + mgr.Add(packages_model.TypeConan, &conan.Specialization{}) + mgr.Add(packages_model.TypeConda, &conda.Specialization{}) mgr.Add(packages_model.TypeDebian, &debian.Specialization{}) + mgr.Add(packages_model.TypeNuGet, &nuget.Specialization{}) + mgr.Add(packages_model.TypeRpm, &rpm.Specialization{}) mgr.Add(packages_model.TypeTerraformState, &terraform.Specialization{}) + mgr.Add(packages_model.TypeVagrant, &vagrant.Specialization{}) // TODO: add more in the future, refactor the existing code to use this approach return nil } diff --git a/services/packages/rpm/spec.go b/services/packages/rpm/spec.go new file mode 100644 index 00000000000..14ed7d26d70 --- /dev/null +++ b/services/packages/rpm/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rpm + +import ( + rpm_module "gitea.dev/modules/packages/rpm" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{rpm_module.PropertyGroup, rpm_module.PropertyArchitecture} +} diff --git a/services/packages/spec.go b/services/packages/spec.go index 19f5eec24dc..b95b23aa027 100644 --- a/services/packages/spec.go +++ b/services/packages/spec.go @@ -36,7 +36,14 @@ func (*SpecRenderUsageManual) RenderSetupManual(ctx context.Context, pkg *packag return "" } +type SpecPublicPropertyNames struct{} + +func (*SpecPublicPropertyNames) PublicPropertyNames() []string { + return nil +} + type specDefault struct { + SpecPublicPropertyNames SpecGetViewPackageVersionData SpecOnBeforeRemovePackageAll SpecOnBeforeRemovePackageVersion diff --git a/services/packages/terraform/spec.go b/services/packages/terraform/spec.go index 4f5d49f8fbf..25a6b4311cc 100644 --- a/services/packages/terraform/spec.go +++ b/services/packages/terraform/spec.go @@ -15,6 +15,7 @@ import ( ) type Specialization struct { + packages_service.SpecPublicPropertyNames packages_service.SpecRenderUsageManual } diff --git a/services/packages/vagrant/spec.go b/services/packages/vagrant/spec.go new file mode 100644 index 00000000000..bb229dacc97 --- /dev/null +++ b/services/packages/vagrant/spec.go @@ -0,0 +1,22 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package vagrant + +import ( + vagrant_module "gitea.dev/modules/packages/vagrant" + packages_service "gitea.dev/services/packages" +) + +type Specialization struct { + packages_service.SpecGetViewPackageVersionData + packages_service.SpecOnBeforeRemovePackageAll + packages_service.SpecOnBeforeRemovePackageVersion + packages_service.SpecRenderUsageManual +} + +var _ packages_service.Specialization = (*Specialization)(nil) + +func (s Specialization) PublicPropertyNames() []string { + return []string{vagrant_module.PropertyProvider} +} diff --git a/templates/swagger/v1-openapi3.generated.json b/templates/swagger/v1-openapi3.generated.json index 970ce5120f2..7454d108c5c 100644 --- a/templates/swagger/v1-openapi3.generated.json +++ b/templates/swagger/v1-openapi3.generated.json @@ -8506,6 +8506,14 @@ "type": "string", "x-go-name": "Name" }, + "properties": { + "additionalProperties": { + "type": "string" + }, + "description": "The type specific properties locating the package file, e.g. the distribution and component of a Debian package", + "type": "object", + "x-go-name": "Properties" + }, "sha1": { "description": "The SHA1 hash of the package file", "type": "string", diff --git a/templates/swagger/v1-swagger.generated.json b/templates/swagger/v1-swagger.generated.json index 3be78a74c8d..c30635a6921 100644 --- a/templates/swagger/v1-swagger.generated.json +++ b/templates/swagger/v1-swagger.generated.json @@ -30991,6 +30991,14 @@ "type": "string", "x-go-name": "Name" }, + "properties": { + "description": "The type specific properties locating the package file, e.g. the distribution and component of a Debian package", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Properties" + }, "sha1": { "description": "The SHA1 hash of the package file", "type": "string", diff --git a/tests/integration/api_packages_debian_test.go b/tests/integration/api_packages_debian_test.go index e4c7e178759..bb1d89ee65e 100644 --- a/tests/integration/api_packages_debian_test.go +++ b/tests/integration/api_packages_debian_test.go @@ -19,6 +19,7 @@ import ( user_model "gitea.dev/models/user" "gitea.dev/modules/base" debian_module "gitea.dev/modules/packages/debian" + api "gitea.dev/modules/structs" packages_cleanup_service "gitea.dev/services/packages/cleanup" "gitea.dev/tests" @@ -116,6 +117,19 @@ func TestPackageDebian(t *testing.T) { assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageVersion, pd.Version.Version) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/debian/%s/%s/files", user.Name, packageName, packageVersion)). + AddBasicAuth(user.Name) + apiFiles := DecodeJSON(t, MakeRequest(t, req, http.StatusOK), []*api.PackageFile{}) + var fileProperties []map[string]string + for _, apiFile := range apiFiles { + fileProperties = append(fileProperties, apiFile.Properties) + } + assert.Contains(t, fileProperties, map[string]string{ + debian_module.PropertyDistribution: distribution, + debian_module.PropertyComponent: component, + debian_module.PropertyArchitecture: architecture, + }) + pfs, err := packages.GetFilesByVersionID(t.Context(), pv.ID) assert.NoError(t, err) assert.NotEmpty(t, pfs)