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