Skip to content

Commit b449cd7

Browse files
committed
Add uploading packages through the Web UI
1 parent d1dca38 commit b449cd7

File tree

11 files changed

+388
-0
lines changed

11 files changed

+388
-0
lines changed

routers/web/repo/packages.go

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package repo
55

66
import (
7+
"fmt"
78
"net/http"
89

910
"code.gitea.io/gitea/models/db"
@@ -64,6 +65,9 @@ func Packages(ctx *context.Context) {
6465
ctx.Data["HasPackages"] = hasPackages
6566
if ctx.Repo != nil {
6667
ctx.Data["CanWritePackages"] = ctx.IsUserRepoWriter([]unit.Type{unit.TypePackages}) || ctx.IsUserSiteAdmin()
68+
ctx.Data["ShowPackageUploadButton"] = ctx.Data["CanWritePackages"]
69+
ctx.Data["PackageUploadUrl"] = fmt.Sprintf("%s/-/packages/upload", ctx.Repo.Owner.HTMLURL())
70+
ctx.Data["PackageUploadRepo"] = ctx.Repo.Repository.Name
6771
}
6872
ctx.Data["PackageDescriptors"] = pds
6973
ctx.Data["Total"] = total

routers/web/shared/packages/upload.go

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package packages
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"io"
10+
11+
packages_model "code.gitea.io/gitea/models/packages"
12+
repo_model "code.gitea.io/gitea/models/repo"
13+
"code.gitea.io/gitea/modules/context"
14+
packages_module "code.gitea.io/gitea/modules/packages"
15+
debian_module "code.gitea.io/gitea/modules/packages/debian"
16+
"code.gitea.io/gitea/modules/util"
17+
"code.gitea.io/gitea/modules/web"
18+
"code.gitea.io/gitea/services/forms"
19+
packages_service "code.gitea.io/gitea/services/packages"
20+
debian_service "code.gitea.io/gitea/services/packages/debian"
21+
)
22+
23+
func servePackageUploadError(ctx *context.Context, err error, packageType, repo string) {
24+
ctx.Flash.Error(err.Error())
25+
26+
if repo == "" {
27+
ctx.Redirect(fmt.Sprintf("%s/-/packages/upload/%s", ctx.ContextUser.HTMLURL(), packageType))
28+
} else {
29+
ctx.Redirect(fmt.Sprintf("%s/-/packages/upload/%s?repo=%s", ctx.ContextUser.HTMLURL(), packageType, repo))
30+
}
31+
}
32+
33+
func addRepoToUploadedPackage(ctx *context.Context, packageType, repoName string, packageID int64) bool {
34+
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ctx.ContextUser.Name, repoName)
35+
if err != nil {
36+
if repo_model.IsErrRepoNotExist(err) {
37+
servePackageUploadError(ctx, fmt.Errorf("repo not found"), packageType, repoName)
38+
return false
39+
} else {
40+
ctx.ServerError("GetRepositoryByOwnerAndName", err)
41+
return false
42+
}
43+
}
44+
45+
err = packages_model.SetRepositoryLink(ctx, packageID, repo.ID)
46+
if err != nil {
47+
ctx.ServerError("SetRepositoryLink", err)
48+
return false
49+
}
50+
51+
return true
52+
}
53+
54+
func UploadGenericPackagePost(ctx *context.Context) {
55+
form := web.GetForm(ctx).(*forms.PackageUploadGenericForm)
56+
upload, err := form.PackageFile.Open()
57+
if err != nil {
58+
ctx.ServerError("GetPackageFile", err)
59+
return
60+
}
61+
62+
buf, err := packages_module.CreateHashedBufferFromReader(upload)
63+
if err != nil {
64+
ctx.ServerError("CreateHashedBufferFromReader", err)
65+
return
66+
}
67+
defer buf.Close()
68+
69+
pv, _, err := packages_service.CreatePackageOrAddFileToExisting(
70+
&packages_service.PackageCreationInfo{
71+
PackageInfo: packages_service.PackageInfo{
72+
Owner: ctx.Package.Owner,
73+
PackageType: packages_model.TypeGeneric,
74+
Name: form.PackageName,
75+
Version: form.PackageVersion,
76+
},
77+
Creator: ctx.Doer,
78+
},
79+
&packages_service.PackageFileCreationInfo{
80+
PackageFileInfo: packages_service.PackageFileInfo{
81+
Filename: form.PackageFilename,
82+
},
83+
Creator: ctx.Doer,
84+
Data: buf,
85+
IsLead: true,
86+
},
87+
)
88+
if err != nil {
89+
switch err {
90+
case packages_model.ErrDuplicatePackageFile:
91+
servePackageUploadError(ctx, err, "generic", form.PackageRepo)
92+
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
93+
servePackageUploadError(ctx, err, "generic", form.PackageRepo)
94+
default:
95+
ctx.ServerError("CreatePackageOrAddFileToExisting", err)
96+
}
97+
return
98+
}
99+
100+
if form.PackageRepo != "" {
101+
if !addRepoToUploadedPackage(ctx, "generic", form.PackageRepo, pv.PackageID) {
102+
return
103+
}
104+
}
105+
106+
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
107+
if err != nil {
108+
ctx.ServerError("GetPackageDescriptor", err)
109+
return
110+
}
111+
112+
ctx.Redirect(pd.FullWebLink())
113+
}
114+
115+
func UploadDebianPackagePost(ctx *context.Context) {
116+
form := web.GetForm(ctx).(*forms.PackageUploadDebianForm)
117+
upload, err := form.PackageFile.Open()
118+
if err != nil {
119+
ctx.ServerError("GetPackageFile", err)
120+
return
121+
}
122+
123+
buf, err := packages_module.CreateHashedBufferFromReader(upload)
124+
if err != nil {
125+
ctx.ServerError("GetGenericPackageFile", err)
126+
return
127+
}
128+
defer buf.Close()
129+
130+
pck, err := debian_module.ParsePackage(buf)
131+
if err != nil {
132+
if errors.Is(err, util.ErrInvalidArgument) {
133+
servePackageUploadError(ctx, err, "debian", form.PackageRepo)
134+
} else {
135+
ctx.ServerError("ParsePackage", err)
136+
}
137+
return
138+
}
139+
140+
if _, err := buf.Seek(0, io.SeekStart); err != nil {
141+
ctx.ServerError("SeekBuffer", err)
142+
return
143+
}
144+
145+
pv, _, err := packages_service.CreatePackageOrAddFileToExisting(
146+
&packages_service.PackageCreationInfo{
147+
PackageInfo: packages_service.PackageInfo{
148+
Owner: ctx.Package.Owner,
149+
PackageType: packages_model.TypeDebian,
150+
Name: pck.Name,
151+
Version: pck.Version,
152+
},
153+
Creator: ctx.Doer,
154+
Metadata: pck.Metadata,
155+
},
156+
&packages_service.PackageFileCreationInfo{
157+
PackageFileInfo: packages_service.PackageFileInfo{
158+
Filename: fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture),
159+
CompositeKey: fmt.Sprintf("%s|%s", form.PackageDistribution, form.PackageComponent),
160+
},
161+
Creator: ctx.Doer,
162+
Data: buf,
163+
IsLead: true,
164+
Properties: map[string]string{
165+
debian_module.PropertyDistribution: form.PackageDistribution,
166+
debian_module.PropertyComponent: form.PackageComponent,
167+
debian_module.PropertyArchitecture: pck.Architecture,
168+
debian_module.PropertyControl: pck.Control,
169+
},
170+
},
171+
)
172+
if err != nil {
173+
switch err {
174+
case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
175+
servePackageUploadError(ctx, err, "debian", form.PackageRepo)
176+
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
177+
servePackageUploadError(ctx, err, "debian", form.PackageRepo)
178+
default:
179+
ctx.ServerError("CreatePackageOrAddFileToExisting", err)
180+
}
181+
return
182+
}
183+
184+
if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, form.PackageDistribution, form.PackageComponent, pck.Architecture); err != nil {
185+
ctx.ServerError("BuildSpecificRepositoryFiles", err)
186+
return
187+
}
188+
189+
if form.PackageRepo != "" {
190+
if !addRepoToUploadedPackage(ctx, "debian", form.PackageRepo, pv.PackageID) {
191+
return
192+
}
193+
}
194+
195+
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
196+
if err != nil {
197+
ctx.ServerError("GetPackageDescriptor", err)
198+
return
199+
}
200+
201+
ctx.Redirect(pd.FullWebLink())
202+
}

routers/web/user/package.go

+51
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
package user
55

66
import (
7+
"fmt"
78
"net/http"
9+
"slices"
810

911
"code.gitea.io/gitea/models/db"
1012
org_model "code.gitea.io/gitea/models/organization"
@@ -33,6 +35,7 @@ const (
3335
tplPackagesView base.TplName = "package/view"
3436
tplPackageVersionList base.TplName = "user/overview/package_versions"
3537
tplPackagesSettings base.TplName = "package/settings"
38+
tplPackageUpload base.TplName = "user/overview/package_upload"
3639
)
3740

3841
// ListPackages displays a list of all packages of the context user
@@ -100,6 +103,7 @@ func ListPackages(ctx *context.Context) {
100103
ctx.Data["PackageDescriptors"] = pds
101104
ctx.Data["Total"] = total
102105
ctx.Data["RepositoryAccessMap"] = repositoryAccessMap
106+
ctx.Data["PackageUploadUrl"] = fmt.Sprintf("%s/-/packages/upload", ctx.ContextUser.HTMLURL())
103107

104108
err = shared_user.LoadHeaderCount(ctx)
105109
if err != nil {
@@ -116,10 +120,16 @@ func ListPackages(ctx *context.Context) {
116120
if ctx.Doer != nil {
117121
ctx.Data["IsOrganizationMember"], _ = org_model.IsOrganizationMember(ctx, org.ID, ctx.Doer.ID)
118122
ctx.Data["IsOrganizationOwner"], _ = org_model.IsOrganizationOwner(ctx, org.ID, ctx.Doer.ID)
123+
ctx.Data["ShowPackageUploadButton"] = ctx.Data["IsOrganizationMember"]
119124
} else {
120125
ctx.Data["IsOrganizationMember"] = false
121126
ctx.Data["IsOrganizationOwner"] = false
127+
ctx.Data["ShowPackageUploadButton"] = false
122128
}
129+
} else if ctx.Doer != nil {
130+
ctx.Data["ShowPackageUploadButton"] = ctx.Doer.ID == ctx.ContextUser.ID
131+
} else {
132+
ctx.Data["ShowPackageUploadButton"] = false
123133
}
124134

125135
pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
@@ -478,3 +488,44 @@ func DownloadPackageFile(ctx *context.Context) {
478488

479489
packages_helper.ServePackageFile(ctx, s, u, pf)
480490
}
491+
492+
func UploadPackageChoose(ctx *context.Context) {
493+
shared_user.PrepareContextForProfileBigAvatar(ctx)
494+
495+
ctx.Data["IsPackagesPage"] = true
496+
ctx.Data["PackageUploadPage"] = "choose"
497+
ctx.Data["PackageUploadRepo"] = ctx.FormString("repo")
498+
499+
err := shared_user.LoadHeaderCount(ctx)
500+
if err != nil {
501+
ctx.ServerError("LoadHeaderCount", err)
502+
return
503+
}
504+
505+
ctx.HTML(http.StatusOK, tplPackageUpload)
506+
}
507+
508+
func UploadPackagePage(ctx *context.Context) {
509+
shared_user.PrepareContextForProfileBigAvatar(ctx)
510+
511+
packageType := ctx.Params("upload_type")
512+
513+
allowdTypes := []string{"generic", "debian"}
514+
515+
if !slices.Contains(allowdTypes, packageType) {
516+
ctx.NotFound("", nil)
517+
return
518+
}
519+
520+
ctx.Data["IsPackagesPage"] = true
521+
ctx.Data["PackageUploadPage"] = packageType
522+
ctx.Data["PackageUploadRepo"] = ctx.FormString("repo")
523+
524+
err := shared_user.LoadHeaderCount(ctx)
525+
if err != nil {
526+
ctx.ServerError("LoadHeaderCount", err)
527+
return
528+
}
529+
530+
ctx.HTML(http.StatusOK, tplPackageUpload)
531+
}

routers/web/web.go

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"code.gitea.io/gitea/routers/web/repo"
3535
"code.gitea.io/gitea/routers/web/repo/actions"
3636
repo_setting "code.gitea.io/gitea/routers/web/repo/setting"
37+
"code.gitea.io/gitea/routers/web/shared/packages"
3738
"code.gitea.io/gitea/routers/web/user"
3839
user_setting "code.gitea.io/gitea/routers/web/user/setting"
3940
"code.gitea.io/gitea/routers/web/user/setting/security"
@@ -828,6 +829,12 @@ func registerRoutes(m *web.Route) {
828829
}, reqPackageAccess(perm.AccessModeWrite))
829830
})
830831
})
832+
m.Group("/upload", func() {
833+
m.Get("", user.UploadPackageChoose)
834+
m.Get("/{upload_type}", user.UploadPackagePage)
835+
m.Post("/generic/upload", web.Bind(forms.PackageUploadGenericForm{}), packages.UploadGenericPackagePost)
836+
m.Post("/debian/upload", web.Bind(forms.PackageUploadDebianForm{}), packages.UploadDebianPackagePost)
837+
}, reqPackageAccess(perm.AccessModeWrite))
831838
}, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
832839
}
833840

services/forms/package_form.go

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package forms
55

66
import (
7+
"mime/multipart"
78
"net/http"
89

910
"code.gitea.io/gitea/modules/context"
@@ -28,3 +29,18 @@ func (f *PackageCleanupRuleForm) Validate(req *http.Request, errs binding.Errors
2829
ctx := context.GetValidateContext(req)
2930
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
3031
}
32+
33+
type PackageUploadGenericForm struct {
34+
PackageRepo string
35+
PackageName string
36+
PackageVersion string
37+
PackageFilename string
38+
PackageFile *multipart.FileHeader
39+
}
40+
41+
type PackageUploadDebianForm struct {
42+
PackageRepo string
43+
PackageDistribution string
44+
PackageComponent string
45+
PackageFile *multipart.FileHeader
46+
}

templates/package/shared/list.tmpl

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
{{end}}
1111
</select>
1212
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
13+
{{if .ShowPackageUploadButton}}
14+
<a class="ui green button" href="{{.PackageUploadUrl}}{{if .PackageUploadRepo}}?repo={{.PackageUploadRepo}}{{end}}">Upload</a>
15+
{{end}}
1316
</div>
1417
</form>
1518
<div>

templates/package/upload/base.tmpl

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{if eq $.PackageUploadPage "choose"}}
2+
{{template "package/upload/choose" .}}
3+
{{else if eq $.PackageUploadPage "generic"}}
4+
{{template "package/upload/generic" .}}
5+
{{else if eq $.PackageUploadPage "debian"}}
6+
{{template "package/upload/debian" .}}
7+
{{end}}

templates/package/upload/choose.tmpl

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<table class="ui very basic table gt-mt-0 gt-px-4">
2+
<tbody>
3+
<tr>
4+
<td>Generic</td>
5+
<td class="text right"><a class="ui green button" href="{{.Link}}/generic{{if .PackageUploadRepo}}?repo={{.PackageUploadRepo}}{{end}}">Upload</a></td>
6+
</tr>
7+
<tr>
8+
<td>Debian</td>
9+
<td class="text right"><a class="ui green button" href="{{.Link}}/debian{{if .PackageUploadRepo}}?repo={{.PackageUploadRepo}}{{end}}">Upload</a></td>
10+
</tr>
11+
</tbody>
12+
</table>

0 commit comments

Comments
 (0)