Add DISABLE_ORGANIZATIONS_PAGE and DISABLE_CODE_PAGE settings for explore pages and fix an issue related to user search ()

These settings can allow users to only display the repositories explore page.

Thanks to yp05327 and wxiaoguang !

---------

Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 9206fbb55fd28f21720072fce6a36cc22277934c)

Conflicts:
	 - templates/explore/navbar.tmpl
	   Resolved by manually applying the last hunk to our template.
This commit is contained in:
Zettat123 2024-10-22 13:09:19 +08:00 committed by Gergely Nagy
parent c9cb470034
commit 8c79008d6f
No known key found for this signature in database
11 changed files with 75 additions and 49 deletions
custom/conf
modules/setting
routers
templates/explore
web_src/js/features/comp

View file

@ -925,6 +925,24 @@ LEVEL = Info
;; Valid site url schemes for user profiles ;; Valid site url schemes for user profiles
;VALID_SITE_URL_SCHEMES=http,https ;VALID_SITE_URL_SCHEMES=http,https
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[service.explore]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Only allow signed in users to view the explore pages.
;REQUIRE_SIGNIN_VIEW = false
;;
;; Disable the users explore page.
;DISABLE_USERS_PAGE = false
;;
;; Disable the organizations explore page.
;DISABLE_ORGANIZATIONS_PAGE = false
;;
;; Disable the code explore page.
;DISABLE_CODE_PAGE = false
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -93,6 +93,8 @@ var Service = struct {
Explore struct { Explore struct {
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
DisableCodePage bool `ini:"DISABLE_CODE_PAGE"`
} `ini:"service.explore"` } `ini:"service.explore"`
}{ }{
AllowedUserVisibilityModesSlice: []bool{true, true, true}, AllowedUserVisibilityModesSlice: []bool{true, true, true},

View file

@ -395,12 +395,20 @@ func reqToken() func(ctx *context.APIContext) {
func reqExploreSignIn() func(ctx *context.APIContext) { func reqExploreSignIn() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned { if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users") ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
} }
} }
} }
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if setting.Service.Explore.DisableUsersPage {
ctx.NotFound()
}
}
}
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) { func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName { if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
@ -887,7 +895,7 @@ func Routes() *web.Route {
// Users (requires user scope) // Users (requires user scope)
m.Group("/users", func() { m.Group("/users", func() {
m.Get("/search", reqExploreSignIn(), user.Search) m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search)
m.Group("/{username}", func() { m.Group("/{username}", func() {
m.Get("", reqExploreSignIn(), user.GetInfo) m.Get("", reqExploreSignIn(), user.GetInfo)

View file

@ -21,12 +21,13 @@ const (
// Code render explore code page // Code render explore code page
func Code(ctx *context.Context) { func Code(ctx *context.Context) {
if !setting.Indexer.RepoIndexerEnabled { if !setting.Indexer.RepoIndexerEnabled || setting.Service.Explore.DisableCodePage {
ctx.Redirect(setting.AppSubURL + "/explore") ctx.Redirect(setting.AppSubURL + "/explore")
return return
} }
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true

View file

@ -14,7 +14,13 @@ import (
// Organizations render explore organizations page // Organizations render explore organizations page
func Organizations(ctx *context.Context) { func Organizations(ctx *context.Context) {
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage if setting.Service.Explore.DisableOrganizationsPage {
ctx.Redirect(setting.AppSubURL + "/explore")
return
}
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreOrganizations"] = true ctx.Data["PageIsExploreOrganizations"] = true

View file

@ -165,7 +165,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Repos render explore repositories page // Repos render explore repositories page
func Repos(ctx *context.Context) { func Repos(ctx *context.Context) {
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreRepositories"] = true ctx.Data["PageIsExploreRepositories"] = true

View file

@ -131,9 +131,11 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
// Users render explore users page // Users render explore users page
func Users(ctx *context.Context) { func Users(ctx *context.Context) {
if setting.Service.Explore.DisableUsersPage { if setting.Service.Explore.DisableUsersPage {
ctx.Redirect(setting.AppSubURL + "/explore/repos") ctx.Redirect(setting.AppSubURL + "/explore")
return return
} }
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreUsers"] = true ctx.Data["PageIsExploreUsers"] = true

View file

@ -8,37 +8,24 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
) )
// Search search users // SearchCandidates searches candidate users for dropdown list
func Search(ctx *context.Context) { func SearchCandidates(ctx *context.Context) {
listOptions := db.ListOptions{ users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Page: ctx.FormInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
}
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
UID: ctx.FormInt64("uid"),
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
IsActive: ctx.FormOptionalBool("active"), IsActive: optional.Some(true),
ListOptions: listOptions, ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
}) })
if err != nil { if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]any{ ctx.ServerError("Unable to search users", err)
"ok": false,
"error": err.Error(),
})
return return
} }
ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)})
ctx.SetTotalCountHeader(maxResults)
ctx.JSON(http.StatusOK, map[string]any{
"ok": true,
"data": convert.ToUsers(ctx, ctx.Doer, users),
})
} }

View file

@ -642,7 +642,7 @@ func registerRoutes(m *web.Route) {
m.Post("/logout", auth.SignOut) m.Post("/logout", auth.SignOut)
m.Get("/task/{task}", reqSignIn, user.TaskStatus) m.Get("/task/{task}", reqSignIn, user.TaskStatus)
m.Get("/stopwatches", reqSignIn, user.GetStopwatches) m.Get("/stopwatches", reqSignIn, user.GetStopwatches)
m.Get("/search", ignExploreSignIn, user.Search) m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates)
m.Group("/oauth2", func() { m.Group("/oauth2", func() {
m.Get("/{provider}", auth.SignInOAuth) m.Get("/{provider}", auth.SignInOAuth)
m.Get("/{provider}/callback", auth.SignInOAuthCallback) m.Get("/{provider}/callback", auth.SignInOAuthCallback)

View file

@ -3,15 +3,18 @@
<a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos"> <a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos">
{{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}} {{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}}
</a> </a>
{{if not .UsersIsDisabled}} {{if not .UsersPageIsDisabled}}
<a class="{{if .PageIsExploreUsers}}active {{end}}item" href="{{AppSubUrl}}/explore/users"> <a class="{{if .PageIsExploreUsers}}active {{end}}item" href="{{AppSubUrl}}/explore/users">
{{svg "octicon-person"}} {{ctx.Locale.Tr "explore.users"}} {{svg "octicon-person"}} {{ctx.Locale.Tr "explore.users"}}
</a> </a>
{{end}} {{end}}
{{if not .OrganizationsPageIsDisabled}}
<a class="{{if .PageIsExploreOrganizations}}active {{end}}item" href="{{AppSubUrl}}/explore/organizations"> <a class="{{if .PageIsExploreOrganizations}}active {{end}}item" href="{{AppSubUrl}}/explore/organizations">
{{svg "octicon-organization"}} {{ctx.Locale.Tr "explore.organizations"}} {{svg "octicon-organization"}} {{ctx.Locale.Tr "explore.organizations"}}
</a> </a>
{{if and (not $.UnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled}} {{end}}
{{if and (not $.UnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled (not .CodePageIsDisabled)}}
<a class="{{if .PageIsExploreCode}}active {{end}}item" href="{{AppSubUrl}}/explore/code"> <a class="{{if .PageIsExploreCode}}active {{end}}item" href="{{AppSubUrl}}/explore/code">
{{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}} {{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}}
</a> </a>

View file

@ -8,41 +8,38 @@ export function initCompSearchUserBox() {
const searchUserBox = document.getElementById('search-user-box'); const searchUserBox = document.getElementById('search-user-box');
if (!searchUserBox) return; if (!searchUserBox) return;
const $searchUserBox = $(searchUserBox);
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true'; const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined; const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
$searchUserBox.search({ $(searchUserBox).search({
minCharacters: 2, minCharacters: 2,
apiSettings: { apiSettings: {
url: `${appSubUrl}/user/search?active=1&q={query}`, url: `${appSubUrl}/user/search_candidates?q={query}`,
onResponse(response) { onResponse(response) {
const items = []; const resultItems = [];
const searchQuery = $searchUserBox.find('input').val(); const searchQuery = searchUserBox.querySelector('input').value;
const searchQueryUppercase = searchQuery.toUpperCase(); const searchQueryUppercase = searchQuery.toUpperCase();
$.each(response.data, (_i, item) => { for (const item of response.data) {
const resultItem = { const resultItem = {
title: item.login, title: item.login,
image: item.avatar_url, image: item.avatar_url,
description: htmlEscape(item.full_name),
}; };
if (item.full_name) {
resultItem.description = htmlEscape(item.full_name);
}
if (searchQueryUppercase === item.login.toUpperCase()) { if (searchQueryUppercase === item.login.toUpperCase()) {
items.unshift(resultItem); resultItems.unshift(resultItem); // add the exact match to the top
} else { } else {
items.push(resultItem); resultItems.push(resultItem);
}
} }
});
if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) { if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) {
const resultItem = { const resultItem = {
title: searchQuery, title: searchQuery,
description: allowEmailDescription, description: allowEmailDescription,
}; };
items.push(resultItem); resultItems.push(resultItem);
} }
return {results: items}; return {results: resultItems};
}, },
}, },
searchFields: ['login', 'full_name'], searchFields: ['login', 'full_name'],