diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 7c7a43944f09..0c76bbc6cdee 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -907,6 +907,24 @@ LEVEL = Info ;; Valid site url schemes for user profiles ;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 +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/setting/service.go b/modules/setting/service.go index 3ea1501236df..c858f803549d 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -90,8 +90,10 @@ var Service = struct { // Explore page settings Explore struct { - RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` - DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"` + DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"` + DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"` + DisableCodePage bool `ini:"DISABLE_CODE_PAGE"` } `ini:"service.explore"` }{ AllowedUserVisibilityModesSlice: []bool{true, true, true}, diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 883e694e44b7..bfc601c8356a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -356,12 +356,20 @@ func reqToken() func(ctx *context.APIContext) { func reqExploreSignIn() 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") } } } +func reqUsersExploreEnabled() func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + if setting.Service.Explore.DisableUsersPage { + ctx.NotFound() + } + } +} + func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName { @@ -955,7 +963,7 @@ func Routes() *web.Router { // Users (requires user scope) m.Group("/users", func() { - m.Get("/search", reqExploreSignIn(), user.Search) + m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search) m.Group("/{username}", func() { m.Get("", reqExploreSignIn(), user.GetInfo) diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index ecd7c33e016f..48f890332bd3 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -21,12 +21,13 @@ const ( // Code render explore code page func Code(ctx *context.Context) { - if !setting.Indexer.RepoIndexerEnabled { + if !setting.Indexer.RepoIndexerEnabled || setting.Service.Explore.DisableCodePage { ctx.Redirect(setting.AppSubURL + "/explore") 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["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go index f8fd6ec38efb..7178630b64e9 100644 --- a/routers/web/explore/org.go +++ b/routers/web/explore/org.go @@ -14,7 +14,13 @@ import ( // Organizations render explore organizations page 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["PageIsExplore"] = true ctx.Data["PageIsExploreOrganizations"] = true diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index 62090e5bf4db..5b6f612e7220 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -165,7 +165,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { // Repos render explore repositories page 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["PageIsExplore"] = true ctx.Data["PageIsExploreRepositories"] = true diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index 18337aff4867..5f6b8fcee610 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -131,9 +131,11 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, // Users render explore users page func Users(ctx *context.Context) { if setting.Service.Explore.DisableUsersPage { - ctx.Redirect(setting.AppSubURL + "/explore/repos") + ctx.Redirect(setting.AppSubURL + "/explore") return } + ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage + ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage ctx.Data["Title"] = ctx.Tr("explore") ctx.Data["PageIsExplore"] = true ctx.Data["PageIsExploreUsers"] = true diff --git a/routers/web/user/search.go b/routers/web/user/search.go index fb7729bbe141..be5eee90a971 100644 --- a/routers/web/user/search.go +++ b/routers/web/user/search.go @@ -8,37 +8,24 @@ import ( "code.gitea.io/gitea/models/db" 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/convert" ) -// Search search users -func Search(ctx *context.Context) { - listOptions := db.ListOptions{ - Page: ctx.FormInt("page"), - PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), - } - - users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ +// SearchCandidates searches candidate users for dropdown list +func SearchCandidates(ctx *context.Context) { + users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ Actor: ctx.Doer, Keyword: ctx.FormTrim("q"), - UID: ctx.FormInt64("uid"), Type: user_model.UserTypeIndividual, - IsActive: ctx.FormOptionalBool("active"), - ListOptions: listOptions, + IsActive: optional.Some(true), + ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum}, }) if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]any{ - "ok": false, - "error": err.Error(), - }) + ctx.ServerError("Unable to search users", err) return } - - ctx.SetTotalCountHeader(maxResults) - - ctx.JSON(http.StatusOK, map[string]any{ - "ok": true, - "data": convert.ToUsers(ctx, ctx.Doer, users), - }) + ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)}) } diff --git a/routers/web/web.go b/routers/web/web.go index f28ec82c8f0b..a6ccb7a7924e 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -670,7 +670,7 @@ func registerRoutes(m *web.Router) { m.Post("/forgot_password", auth.ForgotPasswdPost) m.Post("/logout", auth.SignOut) m.Get("/stopwatches", reqSignIn, user.GetStopwatches) - m.Get("/search", ignExploreSignIn, user.Search) + m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates) m.Group("/oauth2", func() { m.Get("/{provider}", auth.SignInOAuth) m.Get("/{provider}/callback", auth.SignInOAuthCallback) diff --git a/templates/explore/navbar.tmpl b/templates/explore/navbar.tmpl index a157cd4b7595..6b595af63acd 100644 --- a/templates/explore/navbar.tmpl +++ b/templates/explore/navbar.tmpl @@ -3,15 +3,17 @@ {{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}} - {{if not .UsersIsDisabled}} + {{if not .UsersPageIsDisabled}} {{svg "octicon-person"}} {{ctx.Locale.Tr "explore.users"}} {{end}} + {{if not .OrganizationsPageIsDisabled}} {{svg "octicon-organization"}} {{ctx.Locale.Tr "explore.organizations"}} - {{if and (not ctx.Consts.RepoUnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled}} + {{end}} + {{if and (not ctx.Consts.RepoUnitTypeCode.UnitGlobalDisabled) .IsRepoIndexerEnabled (not .CodePageIsDisabled)}} {{svg "octicon-code"}} {{ctx.Locale.Tr "explore.code"}} diff --git a/web_src/js/features/comp/SearchUserBox.ts b/web_src/js/features/comp/SearchUserBox.ts index 7ef23fe4b02c..ceb756b557be 100644 --- a/web_src/js/features/comp/SearchUserBox.ts +++ b/web_src/js/features/comp/SearchUserBox.ts @@ -8,41 +8,38 @@ export function initCompSearchUserBox() { const searchUserBox = document.querySelector('#search-user-box'); if (!searchUserBox) return; - const $searchUserBox = $(searchUserBox); const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true'; const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined; - $searchUserBox.search({ + $(searchUserBox).search({ minCharacters: 2, apiSettings: { - url: `${appSubUrl}/user/search?active=1&q={query}`, + url: `${appSubUrl}/user/search_candidates?q={query}`, onResponse(response) { - const items = []; - const searchQuery = $searchUserBox.find('input').val(); + const resultItems = []; + const searchQuery = searchUserBox.querySelector('input').value; const searchQueryUppercase = searchQuery.toUpperCase(); - $.each(response.data, (_i, item) => { + for (const item of response.data) { const resultItem = { title: item.login, image: item.avatar_url, + description: htmlEscape(item.full_name), }; - if (item.full_name) { - resultItem.description = htmlEscape(item.full_name); - } if (searchQueryUppercase === item.login.toUpperCase()) { - items.unshift(resultItem); + resultItems.unshift(resultItem); // add the exact match to the top } else { - items.push(resultItem); + resultItems.push(resultItem); } - }); + } - if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) { + if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) { const resultItem = { title: searchQuery, description: allowEmailDescription, }; - items.push(resultItem); + resultItems.push(resultItem); } - return {results: items}; + return {results: resultItems}; }, }, searchFields: ['login', 'full_name'],