Files

352 lines
11 KiB
Go
Raw Permalink Normal View History

2015-12-21 04:24:11 -08:00
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2015-12-21 04:24:11 -08:00
package user
import (
"fmt"
"net/http"
2024-01-15 09:49:24 +01:00
"path"
2015-12-21 04:24:11 -08:00
"strings"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
2024-11-24 16:18:57 +08:00
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
2023-05-09 01:57:24 -04:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
2021-06-09 07:33:54 +08:00
"code.gitea.io/gitea/routers/web/org"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
2024-11-29 09:53:49 -08:00
feed_service "code.gitea.io/gitea/services/feed"
2015-12-21 04:24:11 -08:00
)
const (
tplProfileBigAvatar templates.TplName = "shared/user/profile_big_avatar"
tplFollowUnfollow templates.TplName = "org/follow_unfollow"
)
// OwnerProfile render profile page for a user or a organization (aka, repo owner)
func OwnerProfile(ctx *context.Context) {
if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") {
feed.ShowUserFeedRSS(ctx)
2015-12-21 04:24:11 -08:00
return
}
if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") {
feed.ShowUserFeedAtom(ctx)
2015-12-21 04:24:11 -08:00
return
}
if ctx.ContextUser.IsOrganization() {
org.Home(ctx)
} else {
userProfile(ctx)
}
}
func userProfile(ctx *context.Context) {
// check view permissions
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name))
return
}
ctx.Data["Title"] = ctx.ContextUser.DisplayName()
2015-12-21 04:24:11 -08:00
ctx.Data["PageIsUserProfile"] = true
2020-11-18 23:00:16 +01:00
profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer)
2023-05-09 01:57:24 -04:00
prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob)
2025-04-11 03:12:55 +08:00
// prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing
// because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them
// and the "profile readme" related logic also duplicates in both of FindOwnerProfileReadme and RenderUserOrgHeader
// TODO: it is a bad design and should be refactored later,
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err)
return
}
ctx.HTML(http.StatusOK, tplProfile)
}
func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
// if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
// if there is not a profile readme, the overview tab should be treated as the repositories tab
tab := ctx.FormString("tab")
if tab == "" || tab == "overview" {
if profileReadme != nil {
tab = "overview"
} else {
tab = "repositories"
}
}
2015-12-21 04:24:11 -08:00
ctx.Data["TabName"] = tab
ctx.Data["HasUserProfileReadme"] = profileReadme != nil
page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
pagingNum := setting.UI.User.RepoPagingNum
topicOnly := ctx.FormBool("topic")
var (
repos []*repo_model.Repository
count int64
2026-03-08 15:35:50 +01:00
total int64
curRows int
orderBy db.SearchOrderBy
)
sortOrder := ctx.FormString("sort")
if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
2024-09-01 20:25:02 +02:00
sortOrder = setting.UI.ExploreDefaultSort // TODO: add new default sort order for user home?
}
ctx.Data["SortType"] = sortOrder
orderBy = repo_model.OrderByFlatMap[sortOrder]
keyword := ctx.FormTrim("q")
ctx.Data["Keyword"] = keyword
language := ctx.FormTrim("language")
ctx.Data["Language"] = language
followers, numFollowers, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
PageSize: pagingNum,
Page: page,
})
if err != nil {
ctx.ServerError("GetUserFollowers", err)
return
}
ctx.Data["NumFollowers"] = numFollowers
following, numFollowing, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
PageSize: pagingNum,
Page: page,
})
if err != nil {
ctx.ServerError("GetUserFollowing", err)
return
}
ctx.Data["NumFollowing"] = numFollowing
2024-03-03 11:18:34 +01:00
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
2015-12-21 04:24:11 -08:00
switch tab {
case "followers":
ctx.Data["Cards"] = followers
2026-03-08 15:35:50 +01:00
total = numFollowers
case "following":
ctx.Data["Cards"] = following
2026-03-08 15:35:50 +01:00
total = numFollowing
2015-12-21 04:24:11 -08:00
case "activity":
2026-02-17 15:03:55 +01:00
if setting.Service.EnableUserHeatmap && activities_model.ActivityReadable(ctx.ContextUser, ctx.Doer) {
ctx.Data["EnableHeatmap"] = true
ctx.Data["HeatmapURL"] = ctx.ContextUser.HomeLink() + "/-/heatmap"
}
date := ctx.FormString("date")
pagingNum = setting.UI.FeedPagingNum
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
items, feedCount, err := feed_service.GetFeedsForDashboard(ctx, activities_model.GetFeedsOptions{
RequestedUser: ctx.ContextUser,
2022-03-22 08:03:22 +01:00
Actor: ctx.Doer,
IncludePrivate: showPrivate,
OnlyPerformedBy: true,
IncludeDeleted: false,
Date: date,
ListOptions: db.ListOptions{
PageSize: pagingNum,
Page: page,
},
})
2022-03-13 17:40:47 +01:00
if err != nil {
ctx.ServerError("GetFeeds", err)
2015-12-21 04:24:11 -08:00
return
}
ctx.Data["Feeds"] = items
ctx.Data["Date"] = date
curRows = len(items)
total = feedCount
case "stars":
ctx.Data["PageIsProfileStarList"] = true
ctx.Data["ShowRepoOwnerOnList"] = true
repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
2020-01-24 19:00:29 +00:00
Page: page,
},
2022-03-22 08:03:22 +01:00
Actor: ctx.Doer,
Keyword: keyword,
OrderBy: orderBy,
Private: ctx.IsSigned,
StarredByID: ctx.ContextUser.ID,
Collaborate: optional.Some(false),
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
2024-03-03 11:18:34 +01:00
Archived: archived,
Fork: fork,
Mirror: mirror,
Template: template,
IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
}
2026-03-08 15:35:50 +01:00
total = count
2021-04-15 10:53:57 -06:00
case "watching":
repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
2021-04-15 10:53:57 -06:00
Page: page,
},
2022-03-22 08:03:22 +01:00
Actor: ctx.Doer,
2021-04-15 10:53:57 -06:00
Keyword: keyword,
OrderBy: orderBy,
Private: ctx.IsSigned,
WatchedByID: ctx.ContextUser.ID,
Collaborate: optional.Some(false),
2021-04-15 10:53:57 -06:00
TopicOnly: topicOnly,
Language: language,
2021-04-15 10:53:57 -06:00
IncludeDescription: setting.UI.SearchRepoDescription,
2024-03-03 11:18:34 +01:00
Archived: archived,
Fork: fork,
Mirror: mirror,
Template: template,
IsPrivate: private,
2021-04-15 10:53:57 -06:00
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
}
2026-03-08 15:35:50 +01:00
total = count
case "overview":
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
2024-11-24 16:18:57 +08:00
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
})
if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
ctx.Data["ProfileReadmeContent"] = profileContent
}
}
case "organizations":
orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
UserID: ctx.ContextUser.ID,
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{
Page: page,
PageSize: pagingNum,
},
})
if err != nil {
ctx.ServerError("GetUserOrganizations", err)
return
}
ctx.Data["Cards"] = orgs
2026-03-08 15:35:50 +01:00
total = count
default: // default to "repositories"
repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
PageSize: pagingNum,
2020-01-24 19:00:29 +00:00
Page: page,
},
2022-03-22 08:03:22 +01:00
Actor: ctx.Doer,
Keyword: keyword,
OwnerID: ctx.ContextUser.ID,
OrderBy: orderBy,
Private: ctx.IsSigned,
Collaborate: optional.Some(false),
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
2024-03-03 11:18:34 +01:00
Archived: archived,
Fork: fork,
Mirror: mirror,
Template: template,
IsPrivate: private,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
return
2015-12-21 04:24:11 -08:00
}
2026-03-08 15:35:50 +01:00
total = count
2015-12-21 04:24:11 -08:00
}
ctx.Data["Repos"] = repos
ctx.Data["Total"] = total
2025-04-11 03:12:55 +08:00
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err)
return
}
pager := context.NewPagination(total, pagingNum, page, 5)
if tab == "activity" {
pager.WithCurRows(curRows)
}
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
2015-12-21 04:24:11 -08:00
}
2025-02-05 02:14:03 +08:00
// ActionUserFollow is for follow/unfollow user request
func ActionUserFollow(ctx *context.Context) {
2015-12-21 04:24:11 -08:00
var err error
switch ctx.FormString("action") {
2015-12-21 04:24:11 -08:00
case "follow":
2024-03-04 09:16:03 +01:00
err = user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser)
2015-12-21 04:24:11 -08:00
case "unfollow":
err = user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
2015-12-21 04:24:11 -08:00
}
if err != nil {
log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err)
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
2015-12-21 04:24:11 -08:00
return
}
2025-04-11 03:12:55 +08:00
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err)
return
}
2024-02-17 14:13:37 +09:00
if ctx.ContextUser.IsIndividual() {
ctx.HTML(http.StatusOK, tplProfileBigAvatar)
return
} else if ctx.ContextUser.IsOrganization() {
2024-02-23 01:24:57 +01:00
ctx.Data["Org"] = ctx.ContextUser
2024-02-17 14:13:37 +09:00
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.HTML(http.StatusOK, tplFollowUnfollow)
return
}
2025-02-05 02:14:03 +08:00
log.Error("Failed to apply action %q: unsupported context user type: %s", ctx.FormString("action"), ctx.ContextUser.Type)
2025-02-17 14:13:17 +08:00
ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
2015-12-21 04:24:11 -08:00
}