Skip to content

Commit c0f910e

Browse files
zlite183ona-agent
andcommitted
AI Gen inital commit
Co-authored-by: Ona <[email protected]>
1 parent b2ee5be commit c0f910e

File tree

11 files changed

+264
-0
lines changed

11 files changed

+264
-0
lines changed

PACKAGES_EXPLORE_FEATURE.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Packages Explore Feature
2+
3+
## Overview
4+
This feature adds a new "Packages" tab to the Explore page, allowing users to discover and browse packages that they have access to across the Gitea instance.
5+
6+
## Features
7+
8+
### User-Facing Features
9+
1. **Packages Tab in Explore**: A new tab in the explore navigation that displays all accessible packages
10+
2. **Search and Filter**: Users can search packages by name and filter by package type (npm, Maven, Docker, etc.)
11+
3. **Permission-Based Access**: Only shows packages that the user has permission to view based on:
12+
- Public user packages (visible to everyone)
13+
- Limited visibility user packages (visible to logged-in users)
14+
- Organization packages (visible based on org visibility and membership)
15+
- Private packages (only visible to the owner)
16+
17+
### Admin Features
18+
1. **Toggle Control**: Admins can enable/disable the packages explore page via `app.ini` configuration
19+
2. **Configuration Setting**: `[service.explore]` section with `DISABLE_PACKAGES_PAGE` option
20+
21+
## Configuration
22+
23+
Add the following to your `app.ini` file under the `[service.explore]` section:
24+
25+
```ini
26+
[service.explore]
27+
; Disable the packages explore page
28+
DISABLE_PACKAGES_PAGE = false
29+
```
30+
31+
Set to `true` to hide the packages tab from the explore page.
32+
33+
## Implementation Details
34+
35+
### Backend Changes
36+
- **New Handler**: `routers/web/explore/packages.go` - Handles package listing with permission filtering
37+
- **Configuration**: `modules/setting/service.go` - Added `DisablePackagesPage` setting
38+
- **Route**: Added `/explore/packages` route in `routers/web/web.go`
39+
40+
### Frontend Changes
41+
- **Template**: `templates/explore/packages.tmpl` - Displays package list with search/filter
42+
- **Navigation**: Updated `templates/explore/navbar.tmpl` to include packages tab
43+
44+
### Permission Logic
45+
The feature implements proper access control by:
46+
1. Fetching packages from the database
47+
2. Checking each package's owner visibility:
48+
- For user-owned packages: Check user visibility (public/limited/private)
49+
- For org-owned packages: Check org visibility and user membership
50+
3. Filtering results to only show accessible packages
51+
4. Respecting the `DISABLE_PACKAGES_PAGE` configuration setting
52+
53+
## Security Considerations
54+
- Anonymous users only see packages from public users/organizations
55+
- Logged-in users see packages from public and limited visibility users, plus organizations they're members of
56+
- Private user packages are only visible to the owner
57+
- The feature requires packages to be enabled (`[packages] ENABLED = true`)
58+
59+
## Testing
60+
To test the feature:
61+
1. Enable packages in your Gitea instance
62+
2. Create packages under different users/organizations with varying visibility settings
63+
3. Access `/explore/packages` as different user types (anonymous, logged-in, org member)
64+
4. Verify that only appropriate packages are displayed
65+
5. Test the admin toggle by setting `DISABLE_PACKAGES_PAGE = true` and verifying the tab disappears
66+
67+
## Future Enhancements
68+
Potential improvements for future versions:
69+
- Add sorting options (by date, name, downloads)
70+
- Implement more efficient database-level permission filtering
71+
- Add package statistics and trending packages
72+
- Support for package categories/tags

custom/conf/app.example.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,9 @@ LEVEL = Info
946946
;;
947947
;; Disable the code explore page.
948948
;DISABLE_CODE_PAGE = false
949+
;;
950+
;; Disable the packages explore page.
951+
;DISABLE_PACKAGES_PAGE = false
949952

950953
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
951954
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

modules/setting/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ var Service = struct {
9797
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
9898
DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
9999
DisableCodePage bool `ini:"DISABLE_CODE_PAGE"`
100+
DisablePackagesPage bool `ini:"DISABLE_PACKAGES_PAGE"`
100101
} `ini:"service.explore"`
101102

102103
QoS struct {

routers/web/explore/code.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func Code(ctx *context.Context) {
3030

3131
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
3232
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
33+
ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
34+
ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
3335
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
3436
ctx.Data["Title"] = ctx.Tr("explore")
3537
ctx.Data["PageIsExplore"] = true

routers/web/explore/org.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func Organizations(ctx *context.Context) {
2222

2323
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
2424
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
25+
ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
26+
ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
2527
ctx.Data["Title"] = ctx.Tr("explore")
2628
ctx.Data["PageIsExplore"] = true
2729
ctx.Data["PageIsExploreOrganizations"] = true

routers/web/explore/packages.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package explore
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+
user_model "code.gitea.io/gitea/models/user"
13+
"code.gitea.io/gitea/modules/optional"
14+
"code.gitea.io/gitea/modules/setting"
15+
"code.gitea.io/gitea/modules/structs"
16+
"code.gitea.io/gitea/modules/templates"
17+
"code.gitea.io/gitea/services/context"
18+
)
19+
20+
const (
21+
tplExplorePackages templates.TplName = "explore/packages"
22+
)
23+
24+
// Packages render explore packages page
25+
func Packages(ctx *context.Context) {
26+
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
27+
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
28+
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
29+
ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
30+
ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
31+
ctx.Data["Title"] = ctx.Tr("explore")
32+
ctx.Data["PageIsExplore"] = true
33+
ctx.Data["PageIsExplorePackages"] = true
34+
35+
page := ctx.FormInt("page")
36+
if page <= 0 {
37+
page = 1
38+
}
39+
40+
query := ctx.FormTrim("q")
41+
packageType := ctx.FormTrim("type")
42+
43+
ctx.Data["Query"] = query
44+
ctx.Data["PackageType"] = packageType
45+
ctx.Data["AvailableTypes"] = packages_model.TypeList
46+
47+
// Get all packages matching the search criteria
48+
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
49+
Paginator: &db.ListOptions{
50+
PageSize: setting.UI.PackagesPagingNum * 3, // Get more to account for filtering
51+
Page: page,
52+
},
53+
Type: packages_model.Type(packageType),
54+
Name: packages_model.SearchValue{Value: query},
55+
IsInternal: optional.Some(false),
56+
})
57+
if err != nil {
58+
ctx.ServerError("SearchLatestVersions", err)
59+
return
60+
}
61+
62+
// Filter packages based on user permissions
63+
accessiblePVs := make([]*packages_model.PackageVersion, 0, len(pvs))
64+
for _, pv := range pvs {
65+
pkg, err := packages_model.GetPackageByID(ctx, pv.PackageID)
66+
if err != nil {
67+
ctx.ServerError("GetPackageByID", err)
68+
return
69+
}
70+
71+
owner, err := user_model.GetUserByID(ctx, pkg.OwnerID)
72+
if err != nil {
73+
ctx.ServerError("GetUserByID", err)
74+
return
75+
}
76+
77+
// Check if user has access to this package based on owner visibility
78+
hasAccess := false
79+
if owner.IsOrganization() {
80+
// For organizations, check if user can see the org
81+
if ctx.Doer != nil {
82+
isMember, err := org_model.IsOrganizationMember(ctx, owner.ID, ctx.Doer.ID)
83+
if err != nil {
84+
ctx.ServerError("IsOrganizationMember", err)
85+
return
86+
}
87+
hasAccess = isMember || owner.Visibility == structs.VisibleTypePublic
88+
} else {
89+
hasAccess = owner.Visibility == structs.VisibleTypePublic
90+
}
91+
} else {
92+
// For users, check visibility
93+
if ctx.Doer != nil {
94+
hasAccess = owner.Visibility == structs.VisibleTypePublic ||
95+
owner.Visibility == structs.VisibleTypeLimited ||
96+
owner.ID == ctx.Doer.ID
97+
} else {
98+
hasAccess = owner.Visibility == structs.VisibleTypePublic
99+
}
100+
}
101+
102+
if hasAccess {
103+
accessiblePVs = append(accessiblePVs, pv)
104+
if len(accessiblePVs) >= setting.UI.PackagesPagingNum {
105+
break
106+
}
107+
}
108+
}
109+
110+
pds, err := packages_model.GetPackageDescriptors(ctx, accessiblePVs)
111+
if err != nil {
112+
ctx.ServerError("GetPackageDescriptors", err)
113+
return
114+
}
115+
116+
ctx.Data["Total"] = int64(len(accessiblePVs))
117+
ctx.Data["PackageDescriptors"] = pds
118+
119+
pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5)
120+
pager.AddParamFromRequest(ctx.Req)
121+
ctx.Data["Page"] = pager
122+
123+
ctx.HTML(http.StatusOK, tplExplorePackages)
124+
}

routers/web/explore/repo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ func Repos(ctx *context.Context) {
149149
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
150150
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
151151
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
152+
ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
153+
ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
152154
ctx.Data["Title"] = ctx.Tr("explore")
153155
ctx.Data["PageIsExplore"] = true
154156
ctx.Data["ShowRepoOwnerOnList"] = true

routers/web/explore/user.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ func Users(ctx *context.Context) {
134134
}
135135
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
136136
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
137+
ctx.Data["PackagesPageIsDisabled"] = setting.Service.Explore.DisablePackagesPage
138+
ctx.Data["PackagesEnabled"] = setting.Packages.Enabled
137139
ctx.Data["Title"] = ctx.Tr("explore")
138140
ctx.Data["PageIsExplore"] = true
139141
ctx.Data["PageIsExploreUsers"] = true

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ func registerWebRoutes(m *web.Router) {
512512
return
513513
}
514514
}, explore.Code)
515+
m.Get("/packages", packagesEnabled, explore.Packages)
515516
m.Get("/topics/search", explore.TopicSearch)
516517
}, optExploreSignIn)
517518

templates/explore/navbar.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@
1818
{{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}}
1919
</a>
2020
{{end}}
21+
{{if and .PackagesEnabled (not .PackagesPageIsDisabled)}}
22+
<a class="{{if .PageIsExplorePackages}}active {{end}}item" href="{{AppSubUrl}}/explore/packages">
23+
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
24+
</a>
25+
{{end}}
2126
</div>
2227
</overflow-menu>

0 commit comments

Comments
 (0)