code.gitea.io/gitea@v1.22.3/routers/web/user/package.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package user 5 6 import ( 7 "net/http" 8 "net/url" 9 10 "code.gitea.io/gitea/models/db" 11 org_model "code.gitea.io/gitea/models/organization" 12 packages_model "code.gitea.io/gitea/models/packages" 13 container_model "code.gitea.io/gitea/models/packages/container" 14 "code.gitea.io/gitea/models/perm" 15 access_model "code.gitea.io/gitea/models/perm/access" 16 repo_model "code.gitea.io/gitea/models/repo" 17 "code.gitea.io/gitea/modules/base" 18 "code.gitea.io/gitea/modules/container" 19 "code.gitea.io/gitea/modules/httplib" 20 "code.gitea.io/gitea/modules/log" 21 "code.gitea.io/gitea/modules/optional" 22 alpine_module "code.gitea.io/gitea/modules/packages/alpine" 23 debian_module "code.gitea.io/gitea/modules/packages/debian" 24 rpm_module "code.gitea.io/gitea/modules/packages/rpm" 25 "code.gitea.io/gitea/modules/setting" 26 "code.gitea.io/gitea/modules/util" 27 "code.gitea.io/gitea/modules/web" 28 packages_helper "code.gitea.io/gitea/routers/api/packages/helper" 29 shared_user "code.gitea.io/gitea/routers/web/shared/user" 30 "code.gitea.io/gitea/services/context" 31 "code.gitea.io/gitea/services/forms" 32 packages_service "code.gitea.io/gitea/services/packages" 33 ) 34 35 const ( 36 tplPackagesList base.TplName = "user/overview/packages" 37 tplPackagesView base.TplName = "package/view" 38 tplPackageVersionList base.TplName = "user/overview/package_versions" 39 tplPackagesSettings base.TplName = "package/settings" 40 ) 41 42 // ListPackages displays a list of all packages of the context user 43 func ListPackages(ctx *context.Context) { 44 shared_user.PrepareContextForProfileBigAvatar(ctx) 45 page := ctx.FormInt("page") 46 if page <= 1 { 47 page = 1 48 } 49 query := ctx.FormTrim("q") 50 packageType := ctx.FormTrim("type") 51 52 pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ 53 Paginator: &db.ListOptions{ 54 PageSize: setting.UI.PackagesPagingNum, 55 Page: page, 56 }, 57 OwnerID: ctx.ContextUser.ID, 58 Type: packages_model.Type(packageType), 59 Name: packages_model.SearchValue{Value: query}, 60 IsInternal: optional.Some(false), 61 }) 62 if err != nil { 63 ctx.ServerError("SearchLatestVersions", err) 64 return 65 } 66 67 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 68 if err != nil { 69 ctx.ServerError("GetPackageDescriptors", err) 70 return 71 } 72 73 repositoryAccessMap := make(map[int64]bool) 74 for _, pd := range pds { 75 if pd.Repository == nil { 76 continue 77 } 78 if _, has := repositoryAccessMap[pd.Repository.ID]; has { 79 continue 80 } 81 82 permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer) 83 if err != nil { 84 ctx.ServerError("GetUserRepoPermission", err) 85 return 86 } 87 repositoryAccessMap[pd.Repository.ID] = permission.HasAnyUnitAccess() 88 } 89 90 hasPackages, err := packages_model.HasOwnerPackages(ctx, ctx.ContextUser.ID) 91 if err != nil { 92 ctx.ServerError("HasOwnerPackages", err) 93 return 94 } 95 96 shared_user.RenderUserHeader(ctx) 97 98 ctx.Data["Title"] = ctx.Tr("packages.title") 99 ctx.Data["IsPackagesPage"] = true 100 ctx.Data["Query"] = query 101 ctx.Data["PackageType"] = packageType 102 ctx.Data["AvailableTypes"] = packages_model.TypeList 103 ctx.Data["HasPackages"] = hasPackages 104 ctx.Data["PackageDescriptors"] = pds 105 ctx.Data["Total"] = total 106 ctx.Data["RepositoryAccessMap"] = repositoryAccessMap 107 108 err = shared_user.LoadHeaderCount(ctx) 109 if err != nil { 110 ctx.ServerError("LoadHeaderCount", err) 111 return 112 } 113 114 // TODO: context/org -> HandleOrgAssignment() can not be used 115 if ctx.ContextUser.IsOrganization() { 116 org := org_model.OrgFromUser(ctx.ContextUser) 117 ctx.Data["Org"] = org 118 ctx.Data["OrgLink"] = ctx.ContextUser.OrganisationLink() 119 120 if ctx.Doer != nil { 121 ctx.Data["IsOrganizationMember"], _ = org_model.IsOrganizationMember(ctx, org.ID, ctx.Doer.ID) 122 ctx.Data["IsOrganizationOwner"], _ = org_model.IsOrganizationOwner(ctx, org.ID, ctx.Doer.ID) 123 } else { 124 ctx.Data["IsOrganizationMember"] = false 125 ctx.Data["IsOrganizationOwner"] = false 126 } 127 } 128 129 pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) 130 pager.AddParamString("q", query) 131 pager.AddParamString("type", packageType) 132 ctx.Data["Page"] = pager 133 134 ctx.HTML(http.StatusOK, tplPackagesList) 135 } 136 137 // RedirectToLastVersion redirects to the latest package version 138 func RedirectToLastVersion(ctx *context.Context) { 139 p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.Params("type")), ctx.Params("name")) 140 if err != nil { 141 if err == packages_model.ErrPackageNotExist { 142 ctx.NotFound("GetPackageByName", err) 143 } else { 144 ctx.ServerError("GetPackageByName", err) 145 } 146 return 147 } 148 149 pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ 150 PackageID: p.ID, 151 IsInternal: optional.Some(false), 152 }) 153 if err != nil { 154 ctx.ServerError("GetPackageByName", err) 155 return 156 } 157 if len(pvs) == 0 { 158 ctx.NotFound("", err) 159 return 160 } 161 162 pd, err := packages_model.GetPackageDescriptor(ctx, pvs[0]) 163 if err != nil { 164 ctx.ServerError("GetPackageDescriptor", err) 165 return 166 } 167 168 ctx.Redirect(pd.VersionWebLink()) 169 } 170 171 // ViewPackageVersion displays a single package version 172 func ViewPackageVersion(ctx *context.Context) { 173 pd := ctx.Package.Descriptor 174 175 shared_user.RenderUserHeader(ctx) 176 177 ctx.Data["Title"] = pd.Package.Name 178 ctx.Data["IsPackagesPage"] = true 179 ctx.Data["PackageDescriptor"] = pd 180 181 switch pd.Package.Type { 182 case packages_model.TypeContainer: 183 registryAppURL, err := url.Parse(httplib.GuessCurrentAppURL(ctx)) 184 if err != nil { 185 registryAppURL, _ = url.Parse(setting.AppURL) 186 } 187 ctx.Data["RegistryHost"] = registryAppURL.Host 188 case packages_model.TypeAlpine: 189 branches := make(container.Set[string]) 190 repositories := make(container.Set[string]) 191 architectures := make(container.Set[string]) 192 193 for _, f := range pd.Files { 194 for _, pp := range f.Properties { 195 switch pp.Name { 196 case alpine_module.PropertyBranch: 197 branches.Add(pp.Value) 198 case alpine_module.PropertyRepository: 199 repositories.Add(pp.Value) 200 case alpine_module.PropertyArchitecture: 201 architectures.Add(pp.Value) 202 } 203 } 204 } 205 206 ctx.Data["Branches"] = util.Sorted(branches.Values()) 207 ctx.Data["Repositories"] = util.Sorted(repositories.Values()) 208 ctx.Data["Architectures"] = util.Sorted(architectures.Values()) 209 case packages_model.TypeDebian: 210 distributions := make(container.Set[string]) 211 components := make(container.Set[string]) 212 architectures := make(container.Set[string]) 213 214 for _, f := range pd.Files { 215 for _, pp := range f.Properties { 216 switch pp.Name { 217 case debian_module.PropertyDistribution: 218 distributions.Add(pp.Value) 219 case debian_module.PropertyComponent: 220 components.Add(pp.Value) 221 case debian_module.PropertyArchitecture: 222 architectures.Add(pp.Value) 223 } 224 } 225 } 226 227 ctx.Data["Distributions"] = util.Sorted(distributions.Values()) 228 ctx.Data["Components"] = util.Sorted(components.Values()) 229 ctx.Data["Architectures"] = util.Sorted(architectures.Values()) 230 case packages_model.TypeRpm: 231 groups := make(container.Set[string]) 232 architectures := make(container.Set[string]) 233 234 for _, f := range pd.Files { 235 for _, pp := range f.Properties { 236 switch pp.Name { 237 case rpm_module.PropertyGroup: 238 groups.Add(pp.Value) 239 case rpm_module.PropertyArchitecture: 240 architectures.Add(pp.Value) 241 } 242 } 243 } 244 245 ctx.Data["Groups"] = util.Sorted(groups.Values()) 246 ctx.Data["Architectures"] = util.Sorted(architectures.Values()) 247 } 248 249 var ( 250 total int64 251 pvs []*packages_model.PackageVersion 252 err error 253 ) 254 switch pd.Package.Type { 255 case packages_model.TypeContainer: 256 pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{ 257 Paginator: db.NewAbsoluteListOptions(0, 5), 258 PackageID: pd.Package.ID, 259 IsTagged: true, 260 }) 261 default: 262 pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 263 Paginator: db.NewAbsoluteListOptions(0, 5), 264 PackageID: pd.Package.ID, 265 IsInternal: optional.Some(false), 266 }) 267 } 268 if err != nil { 269 ctx.ServerError("", err) 270 return 271 } 272 273 ctx.Data["LatestVersions"] = pvs 274 ctx.Data["TotalVersionCount"] = total 275 276 ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() 277 278 hasRepositoryAccess := false 279 if pd.Repository != nil { 280 permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer) 281 if err != nil { 282 ctx.ServerError("GetUserRepoPermission", err) 283 return 284 } 285 hasRepositoryAccess = permission.HasAnyUnitAccess() 286 } 287 ctx.Data["HasRepositoryAccess"] = hasRepositoryAccess 288 289 err = shared_user.LoadHeaderCount(ctx) 290 if err != nil { 291 ctx.ServerError("LoadHeaderCount", err) 292 return 293 } 294 295 ctx.HTML(http.StatusOK, tplPackagesView) 296 } 297 298 // ListPackageVersions lists all versions of a package 299 func ListPackageVersions(ctx *context.Context) { 300 shared_user.PrepareContextForProfileBigAvatar(ctx) 301 p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.Params("type")), ctx.Params("name")) 302 if err != nil { 303 if err == packages_model.ErrPackageNotExist { 304 ctx.NotFound("GetPackageByName", err) 305 } else { 306 ctx.ServerError("GetPackageByName", err) 307 } 308 return 309 } 310 311 page := ctx.FormInt("page") 312 if page <= 1 { 313 page = 1 314 } 315 pagination := &db.ListOptions{ 316 PageSize: setting.UI.PackagesPagingNum, 317 Page: page, 318 } 319 320 query := ctx.FormTrim("q") 321 sort := ctx.FormTrim("sort") 322 323 shared_user.RenderUserHeader(ctx) 324 325 ctx.Data["Title"] = ctx.Tr("packages.title") 326 ctx.Data["IsPackagesPage"] = true 327 ctx.Data["PackageDescriptor"] = &packages_model.PackageDescriptor{ 328 Package: p, 329 Owner: ctx.Package.Owner, 330 } 331 ctx.Data["Query"] = query 332 ctx.Data["Sort"] = sort 333 334 pagerParams := map[string]string{ 335 "q": query, 336 "sort": sort, 337 } 338 339 var ( 340 total int64 341 pvs []*packages_model.PackageVersion 342 ) 343 switch p.Type { 344 case packages_model.TypeContainer: 345 tagged := ctx.FormTrim("tagged") 346 347 pagerParams["tagged"] = tagged 348 ctx.Data["Tagged"] = tagged 349 350 pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{ 351 Paginator: pagination, 352 PackageID: p.ID, 353 Query: query, 354 IsTagged: tagged == "" || tagged == "tagged", 355 Sort: sort, 356 }) 357 if err != nil { 358 ctx.ServerError("SearchImageTags", err) 359 return 360 } 361 default: 362 pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ 363 Paginator: pagination, 364 PackageID: p.ID, 365 Version: packages_model.SearchValue{ 366 ExactMatch: false, 367 Value: query, 368 }, 369 IsInternal: optional.Some(false), 370 Sort: sort, 371 }) 372 if err != nil { 373 ctx.ServerError("SearchVersions", err) 374 return 375 } 376 } 377 378 ctx.Data["PackageDescriptors"], err = packages_model.GetPackageDescriptors(ctx, pvs) 379 if err != nil { 380 ctx.ServerError("GetPackageDescriptors", err) 381 return 382 } 383 384 ctx.Data["Total"] = total 385 386 err = shared_user.LoadHeaderCount(ctx) 387 if err != nil { 388 ctx.ServerError("LoadHeaderCount", err) 389 return 390 } 391 392 pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) 393 for k, v := range pagerParams { 394 pager.AddParamString(k, v) 395 } 396 ctx.Data["Page"] = pager 397 398 ctx.HTML(http.StatusOK, tplPackageVersionList) 399 } 400 401 // PackageSettings displays the package settings page 402 func PackageSettings(ctx *context.Context) { 403 pd := ctx.Package.Descriptor 404 405 shared_user.RenderUserHeader(ctx) 406 407 ctx.Data["Title"] = pd.Package.Name 408 ctx.Data["IsPackagesPage"] = true 409 ctx.Data["PackageDescriptor"] = pd 410 411 repos, _, _ := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{ 412 Actor: pd.Owner, 413 Private: true, 414 }) 415 ctx.Data["Repos"] = repos 416 ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() 417 418 err := shared_user.LoadHeaderCount(ctx) 419 if err != nil { 420 ctx.ServerError("LoadHeaderCount", err) 421 return 422 } 423 424 ctx.HTML(http.StatusOK, tplPackagesSettings) 425 } 426 427 // PackageSettingsPost updates the package settings 428 func PackageSettingsPost(ctx *context.Context) { 429 pd := ctx.Package.Descriptor 430 431 form := web.GetForm(ctx).(*forms.PackageSettingForm) 432 switch form.Action { 433 case "link": 434 success := func() bool { 435 repoID := int64(0) 436 if form.RepoID != 0 { 437 repo, err := repo_model.GetRepositoryByID(ctx, form.RepoID) 438 if err != nil { 439 log.Error("Error getting repository: %v", err) 440 return false 441 } 442 443 if repo.OwnerID != pd.Owner.ID { 444 return false 445 } 446 447 repoID = repo.ID 448 } 449 450 if err := packages_model.SetRepositoryLink(ctx, pd.Package.ID, repoID); err != nil { 451 log.Error("Error updating package: %v", err) 452 return false 453 } 454 455 return true 456 }() 457 458 if success { 459 ctx.Flash.Success(ctx.Tr("packages.settings.link.success")) 460 } else { 461 ctx.Flash.Error(ctx.Tr("packages.settings.link.error")) 462 } 463 464 ctx.Redirect(ctx.Link) 465 return 466 case "delete": 467 err := packages_service.RemovePackageVersion(ctx, ctx.Doer, ctx.Package.Descriptor.Version) 468 if err != nil { 469 log.Error("Error deleting package: %v", err) 470 ctx.Flash.Error(ctx.Tr("packages.settings.delete.error")) 471 } else { 472 ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) 473 } 474 475 redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages" 476 // redirect to the package if there are still versions available 477 if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: optional.Some(false)}); has { 478 redirectURL = ctx.Package.Descriptor.PackageWebLink() 479 } 480 481 ctx.Redirect(redirectURL) 482 return 483 } 484 } 485 486 // DownloadPackageFile serves the content of a package file 487 func DownloadPackageFile(ctx *context.Context) { 488 pf, err := packages_model.GetFileForVersionByID(ctx, ctx.Package.Descriptor.Version.ID, ctx.ParamsInt64(":fileid")) 489 if err != nil { 490 if err == packages_model.ErrPackageFileNotExist { 491 ctx.NotFound("", err) 492 } else { 493 ctx.ServerError("GetFileForVersionByID", err) 494 } 495 return 496 } 497 498 s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 499 if err != nil { 500 ctx.ServerError("GetPackageFileStream", err) 501 return 502 } 503 504 packages_helper.ServePackageFile(ctx, s, u, pf) 505 }