Skip to content

Commit 03f50d7

Browse files
committed
Add ui.explore settings to control view of explore pages
Add `[ui.explore]` settings to allow restricting the explore pages to logged in users only and to restrict the showing of users to only those with publically available repositories. The two proposed settings are: - `REQUIRE_SIGNIN_VIEW`: Only allows access to the explore pages if the user is signed in. Also restricts `/api/v1/user/search`. - `ONLY_SHOW_USERS_WITH_PUBLIC_REPOS`: Only shows users with public repos on the explore page. `/api/v1/user/search` will only show users with public repos unless the user the is signed in. Fix go-gitea#2908 Signed-off-by: Andrew Thornton <art27@cantab.net>
1 parent 24330f7 commit 03f50d7

File tree

11 files changed

+141
-23
lines changed

11 files changed

+141
-23
lines changed

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
180180
- `NOTICE_PAGING_NUM`: **25**: Number of notices that are shown in one page.
181181
- `ORG_PAGING_NUM`: **50**: Number of organizations that are shown in one page.
182182

183+
### UI - Expore (`ui.explore`)
184+
185+
- `REQUIRE_SIGNIN_VIEW`: **false**: Only allow signed in users to view the explore pages.
186+
- `ONLY_SHOW_USERS_WITH_PUBLIC_REPOS`: **false**: Only show users with public repos on the explore users page.
187+
183188
### UI - Metadata (`ui.meta`)
184189

185190
- `AUTHOR`: **Gitea - Git with a cup of tea**: Author meta tag of the homepage.

models/consistency.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func assertCount(t *testing.T, bean interface{}, expected int) {
7171

7272
func (user *User) checkForConsistency(t *testing.T) {
7373
assertCount(t, &Repository{OwnerID: user.ID}, user.NumRepos)
74+
assertCount(t, &Repository{OwnerID: user.ID, IsPrivate: false}, user.NumPublicRepos)
7475
assertCount(t, &Star{UID: user.ID}, user.NumStars)
7576
assertCount(t, &OrgUser{OrgID: user.ID}, user.NumMembers)
7677
assertCount(t, &Team{OrgID: user.ID}, user.NumTeams)

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ var migrations = []Migration{
254254
NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
255255
// v159 -> v160
256256
NewMigration("update reactions constraint", updateReactionConstraint),
257+
// v160 -> v161
258+
NewMigration("add num public repos", updateNumPublicRepos),
257259
}
258260

259261
// GetCurrentDBVersion returns the current db version

models/migrations/v160.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"xorm.io/xorm"
9+
)
10+
11+
func updateNumPublicRepos(x *xorm.Engine) error {
12+
// User represents a user
13+
type User struct {
14+
ID int64
15+
NumPublicRepos int `xorm:"INDEX NOT NULL DEFAULT 0"`
16+
}
17+
18+
// Repository represents a Repo
19+
type Repository struct {
20+
ID int64
21+
OwnerID int64
22+
IsPrivate bool
23+
}
24+
25+
if err := x.Sync2(&User{}, &Repository{}); err != nil {
26+
return err
27+
}
28+
var batchSize = 100
29+
30+
users := make([]*User, 0, batchSize)
31+
32+
sess := x.NewSession()
33+
defer sess.Close()
34+
35+
sess.SetExpr("num_public_repos", 0).Update(&User{})
36+
37+
for start := 0; ; start += batchSize {
38+
users = users[:0]
39+
40+
if err := sess.Begin(); err != nil {
41+
return err
42+
}
43+
44+
if err := sess.Select("owner_id as id, count(id) AS num_public_repos").Table("repository").Where("repository.is_private", false).GroupBy("owner_id").Limit(batchSize, start).Asc("id").Find(&users); err != nil {
45+
return err
46+
}
47+
48+
if len(users) == 0 {
49+
break
50+
}
51+
52+
for _, user := range users {
53+
if _, err := sess.ID(user.ID).Cols("num_public_repos").Update(user); err != nil {
54+
return err
55+
}
56+
}
57+
58+
if err := sess.Commit(); err != nil {
59+
return err
60+
}
61+
}
62+
return nil
63+
}

models/repo.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,12 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteO
12121212
return fmt.Errorf("increment user total_repos: %v", err)
12131213
}
12141214
u.NumRepos++
1215+
if !repo.IsPrivate {
1216+
if _, err = ctx.e.Incr("num_public_repos").ID(u.ID).Update(new(User)); err != nil {
1217+
return fmt.Errorf("increment user total_public_repos: %v", err)
1218+
}
1219+
u.NumPublicRepos++
1220+
}
12151221

12161222
// Give access to all members in teams with access to all repositories.
12171223
if u.IsOrganization() {
@@ -1524,6 +1530,14 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
15241530
if err != nil {
15251531
return err
15261532
}
1533+
repo.Owner.NumPublicRepos--
1534+
} else {
1535+
repo.Owner.NumPublicRepos++
1536+
}
1537+
1538+
_, err = e.ID(repo.OwnerID).Cols("num_public_repos").Update(repo.Owner)
1539+
if err != nil {
1540+
return err
15271541
}
15281542

15291543
// Create/Remove git-daemon-export-ok for git-daemon...
@@ -2074,6 +2088,12 @@ func CheckRepoStats(ctx context.Context) error {
20742088
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?",
20752089
"user count 'num_repos'",
20762090
},
2091+
// User.NumPublicRepos
2092+
{
2093+
"SELECT `user`.id FROM `user` WHERE `user`.num_public_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id AND is_private=0)",
2094+
"UPDATE `user` SET num_public_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=? AND is_private=0) WHERE id=?",
2095+
"user count 'num_public_repos'",
2096+
},
20772097
// Issue.NumComments
20782098
{
20792099
"SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)",

models/user.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,11 @@ type User struct {
145145
UseCustomAvatar bool
146146

147147
// Counters
148-
NumFollowers int
149-
NumFollowing int `xorm:"NOT NULL DEFAULT 0"`
150-
NumStars int
151-
NumRepos int
148+
NumFollowers int
149+
NumFollowing int `xorm:"NOT NULL DEFAULT 0"`
150+
NumStars int
151+
NumRepos int
152+
NumPublicRepos int `xorm:"INDEX NOT NULL DEFAULT 0"`
152153

153154
// For organization
154155
NumTeams int
@@ -1428,14 +1429,15 @@ func GetUser(user *User) (bool, error) {
14281429
// SearchUserOptions contains the options for searching
14291430
type SearchUserOptions struct {
14301431
ListOptions
1431-
Keyword string
1432-
Type UserType
1433-
UID int64
1434-
OrderBy SearchOrderBy
1435-
Visible []structs.VisibleType
1436-
Actor *User // The user doing the search
1437-
IsActive util.OptionalBool
1438-
SearchByEmail bool // Search by email as well as username/full name
1432+
Keyword string
1433+
Type UserType
1434+
UID int64
1435+
OrderBy SearchOrderBy
1436+
Visible []structs.VisibleType
1437+
Actor *User // The user doing the search
1438+
IsActive util.OptionalBool
1439+
SearchByEmail bool // Search by email as well as username/full name
1440+
OnlyUsersWithPublicRepos bool
14391441
}
14401442

14411443
func (opts *SearchUserOptions) toConds() builder.Cond {
@@ -1489,6 +1491,10 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
14891491
cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
14901492
}
14911493

1494+
if opts.OnlyUsersWithPublicRepos {
1495+
cond = cond.And(builder.Gt{"num_public_repos": 0})
1496+
}
1497+
14921498
return cond
14931499
}
14941500

modules/setting/setting.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ var (
201201
Description string
202202
Keywords string
203203
} `ini:"ui.meta"`
204+
// Explore page settings
205+
Explore struct {
206+
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
207+
OnlyShowUsersWithPublicRepos bool `ini:"ONLY_SHOW_USERS_WITH_PUBLIC_REPOS"`
208+
} `ini:"ui.explore"`
204209
}{
205210
ExplorePagingNum: 20,
206211
IssuePagingNum: 10,
@@ -252,6 +257,10 @@ var (
252257
Description: "Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go",
253258
Keywords: "go,git,self-hosted,gitea",
254259
},
260+
Explore: struct {
261+
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
262+
OnlyShowUsersWithPublicRepos bool `ini:"ONLY_SHOW_USERS_WITH_PUBLIC_REPOS"`
263+
}{},
255264
}
256265

257266
// Markdown settings

routers/api/v1/api.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ func reqToken() macaron.Handler {
195195
}
196196
}
197197

198+
func reqExploreSignIn() macaron.Handler {
199+
return func(ctx *context.APIContext) {
200+
if setting.UI.Explore.RequireSigninView && !ctx.IsSigned {
201+
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
202+
}
203+
}
204+
}
205+
198206
func reqBasicAuth() macaron.Handler {
199207
return func(ctx *context.APIContext) {
200208
if !ctx.Context.IsBasicAuth {
@@ -538,7 +546,7 @@ func RegisterRoutes(m *macaron.Macaron) {
538546

539547
// Users
540548
m.Group("/users", func() {
541-
m.Get("/search", user.Search)
549+
m.Get("/search", reqExploreSignIn, user.Search)
542550

543551
m.Group("/:username", func() {
544552
m.Get("", user.GetInfo)

routers/api/v1/user/user.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"code.gitea.io/gitea/models"
1414
"code.gitea.io/gitea/modules/context"
1515
"code.gitea.io/gitea/modules/convert"
16+
"code.gitea.io/gitea/modules/setting"
1617
api "code.gitea.io/gitea/modules/structs"
1718
"code.gitea.io/gitea/routers/api/v1/utils"
1819

@@ -60,10 +61,11 @@ func Search(ctx *context.APIContext) {
6061
listOptions := utils.GetListOptions(ctx)
6162

6263
opts := &models.SearchUserOptions{
63-
Keyword: strings.Trim(ctx.Query("q"), " "),
64-
UID: com.StrTo(ctx.Query("uid")).MustInt64(),
65-
Type: models.UserTypeIndividual,
66-
ListOptions: listOptions,
64+
Keyword: strings.Trim(ctx.Query("q"), " "),
65+
UID: com.StrTo(ctx.Query("uid")).MustInt64(),
66+
Type: models.UserTypeIndividual,
67+
ListOptions: listOptions,
68+
OnlyUsersWithPublicRepos: setting.UI.Explore.OnlyShowUsersWithPublicRepos && !ctx.IsSigned,
6769
}
6870

6971
users, maxResults, err := models.SearchUsers(opts)

routers/home.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,12 @@ func ExploreUsers(ctx *context.Context) {
252252
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
253253

254254
RenderUserSearch(ctx, &models.SearchUserOptions{
255-
Actor: ctx.User,
256-
Type: models.UserTypeIndividual,
257-
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum},
258-
IsActive: util.OptionalBoolTrue,
259-
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
255+
Actor: ctx.User,
256+
Type: models.UserTypeIndividual,
257+
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum},
258+
IsActive: util.OptionalBoolTrue,
259+
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
260+
OnlyUsersWithPublicRepos: setting.UI.Explore.OnlyShowUsersWithPublicRepos,
260261
}, tplExploreUsers)
261262
}
262263

0 commit comments

Comments
 (0)