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  }