2
0

feat(packages): add global package access support

Add is_global flag to packages allowing them to be accessible at root URLs without owner prefix. Include database migration, package settings UI, admin bulk operations, and automatic repository matching. This enables cleaner package URLs for organization-wide packages.
This commit is contained in:
2026-01-21 20:05:36 -05:00
parent 4ae82201dc
commit 1f512924de
12 changed files with 520 additions and 11 deletions

View File

@@ -61,6 +61,10 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
pkg := &Package{
Owner: ctx.ContextUser,
}
// Handle global packages (when owner is nil, i.e., username was "_")
isGlobalRequest := ctx.ContextUser == nil
var err error
pkg.AccessMode, err = determineAccessMode(ctx.Base, pkg, ctx.Doer)
if err != nil {
@@ -72,14 +76,47 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
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))
var pv *packages_model.PackageVersion
if isGlobalRequest {
// Look up global package by name (no owner)
p, err := packages_model.GetGlobalPackageByName(ctx, packages_model.Type(packageType), name)
if err != nil {
if err == packages_model.ErrPackageNotExist {
errCb(http.StatusNotFound, fmt.Errorf("GetGlobalPackageByName: %w", err))
} else {
errCb(http.StatusInternalServerError, fmt.Errorf("GetGlobalPackageByName: %w", err))
}
return pkg
}
// Get the actual owner for the package
owner, err := user_model.GetUserByID(ctx, p.OwnerID)
if err != nil {
errCb(http.StatusInternalServerError, fmt.Errorf("GetUserByID: %w", err))
return pkg
}
pkg.Owner = owner
// Get the version
pv, err = packages_model.GetVersionByNameAndVersion(ctx, p.OwnerID, 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))
}
return pkg
}
} else {
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))
}
return pkg
}
return pkg
}
pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
@@ -101,6 +138,14 @@ func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.A
return perm.AccessModeNone, nil
}
// Global packages (no owner) - allow read access to everyone, write only to admins
if pkg.Owner == nil {
if doer != nil && doer.IsAdmin {
return perm.AccessModeAdmin, nil
}
return perm.AccessModeRead, nil
}
// TODO: ActionUser permission check
accessMode := perm.AccessModeNone
if pkg.Owner.IsOrganization() {

View File

@@ -61,6 +61,11 @@ func UserAssignmentAPI() func(ctx *APIContext) {
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) {
username := ctx.PathParam("username")
// Special case: "_" means global packages (no specific owner)
if username == "_" {
return nil
}
if doer != nil && strings.EqualFold(doer.LowerName, username) {
contextUser = doer
} else {