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  }