summaryrefslogtreecommitdiffstats
path: root/routers/web/explore
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /routers/web/explore
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--routers/web/explore/code.go144
-rw-r--r--routers/web/explore/org.go48
-rw-r--r--routers/web/explore/repo.go193
-rw-r--r--routers/web/explore/topic.go41
-rw-r--r--routers/web/explore/user.go163
5 files changed, 589 insertions, 0 deletions
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
new file mode 100644
index 0000000..f61b832
--- /dev/null
+++ b/routers/web/explore/code.go
@@ -0,0 +1,144 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package explore
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/base"
+ code_indexer "code.gitea.io/gitea/modules/indexer/code"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
+)
+
+const (
+ // tplExploreCode explore code page template
+ tplExploreCode base.TplName = "explore/code"
+)
+
+// Code render explore code page
+func Code(ctx *context.Context) {
+ if !setting.Indexer.RepoIndexerEnabled {
+ ctx.Redirect(setting.AppSubURL + "/explore")
+ return
+ }
+
+ ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+ ctx.Data["Title"] = ctx.Tr("explore")
+ ctx.Data["PageIsExplore"] = true
+ ctx.Data["PageIsExploreCode"] = true
+
+ language := ctx.FormTrim("l")
+ keyword := ctx.FormTrim("q")
+
+ isFuzzy := ctx.FormOptionalBool("fuzzy").ValueOrDefault(true)
+
+ ctx.Data["Keyword"] = keyword
+ ctx.Data["Language"] = language
+ ctx.Data["IsFuzzy"] = isFuzzy
+ ctx.Data["PageIsViewCode"] = true
+
+ if keyword == "" {
+ ctx.HTML(http.StatusOK, tplExploreCode)
+ return
+ }
+
+ page := ctx.FormInt("page")
+ if page <= 0 {
+ page = 1
+ }
+
+ var (
+ repoIDs []int64
+ err error
+ isAdmin bool
+ )
+ if ctx.Doer != nil {
+ isAdmin = ctx.Doer.IsAdmin
+ }
+
+ // guest user or non-admin user
+ if ctx.Doer == nil || !isAdmin {
+ repoIDs, err = repo_model.FindUserCodeAccessibleRepoIDs(ctx, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("FindUserCodeAccessibleRepoIDs", err)
+ return
+ }
+ }
+
+ var (
+ total int
+ searchResults []*code_indexer.Result
+ searchResultLanguages []*code_indexer.SearchResultLanguages
+ )
+
+ if (len(repoIDs) > 0) || isAdmin {
+ total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
+ RepoIDs: repoIDs,
+ Keyword: keyword,
+ IsKeywordFuzzy: isFuzzy,
+ Language: language,
+ Paginator: &db.ListOptions{
+ Page: page,
+ PageSize: setting.UI.RepoSearchPagingNum,
+ },
+ })
+ if err != nil {
+ if code_indexer.IsAvailable(ctx) {
+ ctx.ServerError("SearchResults", err)
+ return
+ }
+ ctx.Data["CodeIndexerUnavailable"] = true
+ } else {
+ ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
+ }
+
+ loadRepoIDs := make([]int64, 0, len(searchResults))
+ for _, result := range searchResults {
+ var find bool
+ for _, id := range loadRepoIDs {
+ if id == result.RepoID {
+ find = true
+ break
+ }
+ }
+ if !find {
+ loadRepoIDs = append(loadRepoIDs, result.RepoID)
+ }
+ }
+
+ repoMaps, err := repo_model.GetRepositoriesMapByIDs(ctx, loadRepoIDs)
+ if err != nil {
+ ctx.ServerError("GetRepositoriesMapByIDs", err)
+ return
+ }
+
+ ctx.Data["RepoMaps"] = repoMaps
+
+ if len(loadRepoIDs) != len(repoMaps) {
+ // Remove deleted repos from search results
+ cleanedSearchResults := make([]*code_indexer.Result, 0, len(repoMaps))
+ for _, sr := range searchResults {
+ if _, found := repoMaps[sr.RepoID]; found {
+ cleanedSearchResults = append(cleanedSearchResults, sr)
+ }
+ }
+
+ searchResults = cleanedSearchResults
+ }
+ }
+
+ ctx.Data["SearchResults"] = searchResults
+ ctx.Data["SearchResultLanguages"] = searchResultLanguages
+
+ pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5)
+ pager.SetDefaultParams(ctx)
+ pager.AddParam(ctx, "l", "Language")
+ ctx.Data["Page"] = pager
+
+ ctx.HTML(http.StatusOK, tplExploreCode)
+}
diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go
new file mode 100644
index 0000000..f8fd6ec
--- /dev/null
+++ b/routers/web/explore/org.go
@@ -0,0 +1,48 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package explore
+
+import (
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
+)
+
+// Organizations render explore organizations page
+func Organizations(ctx *context.Context) {
+ ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
+ ctx.Data["Title"] = ctx.Tr("explore")
+ ctx.Data["PageIsExplore"] = true
+ ctx.Data["PageIsExploreOrganizations"] = true
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+
+ visibleTypes := []structs.VisibleType{structs.VisibleTypePublic}
+ if ctx.Doer != nil {
+ visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate)
+ }
+
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
+ }
+
+ RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ Actor: ctx.Doer,
+ Type: user_model.UserTypeOrganization,
+ ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
+ Visible: visibleTypes,
+
+ SupportedSortOrders: supportedSortOrders,
+ }, tplExploreUsers)
+}
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
new file mode 100644
index 0000000..116b983
--- /dev/null
+++ b/routers/web/explore/repo.go
@@ -0,0 +1,193 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package explore
+
+import (
+ "fmt"
+ "net/http"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/sitemap"
+ "code.gitea.io/gitea/services/context"
+)
+
+const (
+ // tplExploreRepos explore repositories page template
+ tplExploreRepos base.TplName = "explore/repos"
+ relevantReposOnlyParam string = "only_show_relevant"
+)
+
+// RepoSearchOptions when calling search repositories
+type RepoSearchOptions struct {
+ OwnerID int64
+ Private bool
+ Restricted bool
+ PageSize int
+ OnlyShowRelevant bool
+ TplName base.TplName
+}
+
+// RenderRepoSearch render repositories search page
+// This function is also used to render the Admin Repository Management page.
+func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
+ // Sitemap index for sitemap paths
+ page := int(ctx.ParamsInt64("idx"))
+ isSitemap := ctx.Params("idx") != ""
+ if page <= 1 {
+ page = ctx.FormInt("page")
+ }
+
+ if page <= 0 {
+ page = 1
+ }
+
+ if isSitemap {
+ opts.PageSize = setting.UI.SitemapPagingNum
+ }
+
+ var (
+ repos []*repo_model.Repository
+ count int64
+ err error
+ orderBy db.SearchOrderBy
+ )
+
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = setting.UI.ExploreDefaultSort
+ }
+
+ if order, ok := repo_model.OrderByFlatMap[sortOrder]; ok {
+ orderBy = order
+ } else {
+ sortOrder = "recentupdate"
+ orderBy = db.SearchOrderByRecentUpdated
+ }
+ ctx.Data["SortType"] = sortOrder
+
+ keyword := ctx.FormTrim("q")
+
+ ctx.Data["OnlyShowRelevant"] = opts.OnlyShowRelevant
+
+ topicOnly := ctx.FormBool("topic")
+ ctx.Data["TopicOnly"] = topicOnly
+
+ language := ctx.FormTrim("language")
+ ctx.Data["Language"] = language
+
+ archived := ctx.FormOptionalBool("archived")
+ ctx.Data["IsArchived"] = archived
+
+ fork := ctx.FormOptionalBool("fork")
+ ctx.Data["IsFork"] = fork
+
+ mirror := ctx.FormOptionalBool("mirror")
+ ctx.Data["IsMirror"] = mirror
+
+ template := ctx.FormOptionalBool("template")
+ ctx.Data["IsTemplate"] = template
+
+ private := ctx.FormOptionalBool("private")
+ ctx.Data["IsPrivate"] = private
+
+ repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+ ListOptions: db.ListOptions{
+ Page: page,
+ PageSize: opts.PageSize,
+ },
+ Actor: ctx.Doer,
+ OrderBy: orderBy,
+ Private: opts.Private,
+ Keyword: keyword,
+ OwnerID: opts.OwnerID,
+ AllPublic: true,
+ AllLimited: true,
+ TopicOnly: topicOnly,
+ Language: language,
+ IncludeDescription: setting.UI.SearchRepoDescription,
+ OnlyShowRelevant: opts.OnlyShowRelevant,
+ Archived: archived,
+ Fork: fork,
+ Mirror: mirror,
+ Template: template,
+ IsPrivate: private,
+ })
+ if err != nil {
+ ctx.ServerError("SearchRepository", err)
+ return
+ }
+ if isSitemap {
+ m := sitemap.NewSitemap()
+ for _, item := range repos {
+ m.Add(sitemap.URL{URL: item.HTMLURL(), LastMod: item.UpdatedUnix.AsTimePtr()})
+ }
+ ctx.Resp.Header().Set("Content-Type", "text/xml")
+ if _, err := m.WriteTo(ctx.Resp); err != nil {
+ log.Error("Failed writing sitemap: %v", err)
+ }
+ return
+ }
+
+ ctx.Data["Keyword"] = keyword
+ ctx.Data["Total"] = count
+ ctx.Data["Repos"] = repos
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+
+ pager := context.NewPagination(int(count), opts.PageSize, page, 5)
+ pager.SetDefaultParams(ctx)
+ pager.AddParam(ctx, "topic", "TopicOnly")
+ pager.AddParam(ctx, "language", "Language")
+ pager.AddParamString(relevantReposOnlyParam, fmt.Sprint(opts.OnlyShowRelevant))
+ if archived.Has() {
+ pager.AddParamString("archived", fmt.Sprint(archived.Value()))
+ }
+ if fork.Has() {
+ pager.AddParamString("fork", fmt.Sprint(fork.Value()))
+ }
+ if mirror.Has() {
+ pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
+ }
+ if template.Has() {
+ pager.AddParamString("template", fmt.Sprint(template.Value()))
+ }
+ if private.Has() {
+ pager.AddParamString("private", fmt.Sprint(private.Value()))
+ }
+ ctx.Data["Page"] = pager
+
+ ctx.HTML(http.StatusOK, opts.TplName)
+}
+
+// Repos render explore repositories page
+func Repos(ctx *context.Context) {
+ ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage
+ ctx.Data["Title"] = ctx.Tr("explore")
+ ctx.Data["PageIsExplore"] = true
+ ctx.Data["PageIsExploreRepositories"] = true
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+
+ var ownerID int64
+ if ctx.Doer != nil && !ctx.Doer.IsAdmin {
+ ownerID = ctx.Doer.ID
+ }
+
+ onlyShowRelevant := setting.UI.OnlyShowRelevantRepos
+
+ _ = ctx.Req.ParseForm() // parse the form first, to prepare the ctx.Req.Form field
+ if len(ctx.Req.Form[relevantReposOnlyParam]) != 0 {
+ onlyShowRelevant = ctx.FormBool(relevantReposOnlyParam)
+ }
+
+ RenderRepoSearch(ctx, &RepoSearchOptions{
+ PageSize: setting.UI.ExplorePagingNum,
+ OwnerID: ownerID,
+ Private: ctx.Doer != nil,
+ TplName: tplExploreRepos,
+ OnlyShowRelevant: onlyShowRelevant,
+ })
+}
diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go
new file mode 100644
index 0000000..95fecfe
--- /dev/null
+++ b/routers/web/explore/topic.go
@@ -0,0 +1,41 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package explore
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+)
+
+// TopicSearch search for creating topic
+func TopicSearch(ctx *context.Context) {
+ opts := &repo_model.FindTopicOptions{
+ Keyword: ctx.FormString("q"),
+ ListOptions: db.ListOptions{
+ Page: ctx.FormInt("page"),
+ PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
+ },
+ }
+
+ topics, total, err := repo_model.FindTopics(ctx, opts)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError)
+ return
+ }
+
+ topicResponses := make([]*api.TopicResponse, len(topics))
+ for i, topic := range topics {
+ topicResponses[i] = convert.ToTopicResponse(topic)
+ }
+
+ ctx.SetTotalCountHeader(total)
+ ctx.JSON(http.StatusOK, map[string]any{
+ "topics": topicResponses,
+ })
+}
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
new file mode 100644
index 0000000..b79a79f
--- /dev/null
+++ b/routers/web/explore/user.go
@@ -0,0 +1,163 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package explore
+
+import (
+ "bytes"
+ "net/http"
+
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/sitemap"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
+)
+
+const (
+ // tplExploreUsers explore users page template
+ tplExploreUsers base.TplName = "explore/users"
+)
+
+var nullByte = []byte{0x00}
+
+func isKeywordValid(keyword string) bool {
+ return !bytes.Contains([]byte(keyword), nullByte)
+}
+
+// RenderUserSearch render user search page
+func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName base.TplName) {
+ // Sitemap index for sitemap paths
+ opts.Page = int(ctx.ParamsInt64("idx"))
+ isSitemap := ctx.Params("idx") != ""
+ if opts.Page <= 1 {
+ opts.Page = ctx.FormInt("page")
+ }
+ if opts.Page <= 1 {
+ opts.Page = 1
+ }
+
+ if isSitemap {
+ opts.PageSize = setting.UI.SitemapPagingNum
+ }
+
+ var (
+ users []*user_model.User
+ count int64
+ err error
+ orderBy db.SearchOrderBy
+ )
+
+ // we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns
+
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = setting.UI.ExploreDefaultSort
+ }
+ ctx.Data["SortType"] = sortOrder
+
+ switch sortOrder {
+ case "newest":
+ orderBy = "`user`.id DESC"
+ case "oldest":
+ orderBy = "`user`.id ASC"
+ case "leastupdate":
+ orderBy = "`user`.updated_unix ASC"
+ case "reversealphabetically":
+ orderBy = "`user`.name DESC"
+ case "lastlogin":
+ orderBy = "`user`.last_login_unix ASC"
+ case "reverselastlogin":
+ orderBy = "`user`.last_login_unix DESC"
+ case "alphabetically":
+ orderBy = "`user`.name ASC"
+ case "recentupdate":
+ fallthrough
+ default:
+ // in case the sortType is not valid, we set it to recentupdate
+ sortOrder = "recentupdate"
+ ctx.Data["SortType"] = "recentupdate"
+ orderBy = "`user`.updated_unix DESC"
+ }
+
+ if opts.SupportedSortOrders != nil && !opts.SupportedSortOrders.Contains(sortOrder) {
+ ctx.NotFound("unsupported sort order", nil)
+ return
+ }
+
+ opts.Keyword = ctx.FormTrim("q")
+ opts.OrderBy = orderBy
+ if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
+ users, count, err = user_model.SearchUsers(ctx, opts)
+ if err != nil {
+ ctx.ServerError("SearchUsers", err)
+ return
+ }
+ }
+ if isSitemap {
+ m := sitemap.NewSitemap()
+ for _, item := range users {
+ m.Add(sitemap.URL{URL: item.HTMLURL(), LastMod: item.UpdatedUnix.AsTimePtr()})
+ }
+ ctx.Resp.Header().Set("Content-Type", "text/xml")
+ if _, err := m.WriteTo(ctx.Resp); err != nil {
+ log.Error("Failed writing sitemap: %v", err)
+ }
+ return
+ }
+
+ ctx.Data["Keyword"] = opts.Keyword
+ ctx.Data["Total"] = count
+ ctx.Data["Users"] = users
+ ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus(ctx)
+ ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+
+ pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+ pager.SetDefaultParams(ctx)
+ for paramKey, paramVal := range opts.ExtraParamStrings {
+ pager.AddParamString(paramKey, paramVal)
+ }
+ ctx.Data["Page"] = pager
+
+ ctx.HTML(http.StatusOK, tplName)
+}
+
+// Users render explore users page
+func Users(ctx *context.Context) {
+ if setting.Service.Explore.DisableUsersPage {
+ ctx.Redirect(setting.AppSubURL + "/explore/repos")
+ return
+ }
+ ctx.Data["Title"] = ctx.Tr("explore")
+ ctx.Data["PageIsExplore"] = true
+ ctx.Data["PageIsExploreUsers"] = true
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+
+ supportedSortOrders := container.SetOf(
+ "newest",
+ "oldest",
+ "alphabetically",
+ "reversealphabetically",
+ )
+ sortOrder := ctx.FormString("sort")
+ if sortOrder == "" {
+ sortOrder = "newest"
+ ctx.SetFormString("sort", sortOrder)
+ }
+
+ RenderUserSearch(ctx, &user_model.SearchUserOptions{
+ Actor: ctx.Doer,
+ Type: user_model.UserTypeIndividual,
+ ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
+ IsActive: optional.Some(true),
+ Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
+
+ SupportedSortOrders: supportedSortOrders,
+ }, tplExploreUsers)
+}