gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package user
  5. import (
  6. "fmt"
  7. "net/http"
  8. "path"
  9. "strings"
  10. activities_model "code.gitea.io/gitea/models/activities"
  11. "code.gitea.io/gitea/models/db"
  12. "code.gitea.io/gitea/models/organization"
  13. "code.gitea.io/gitea/models/renderhelper"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/markup/markdown"
  19. "code.gitea.io/gitea/modules/optional"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/templates"
  22. "code.gitea.io/gitea/modules/util"
  23. "code.gitea.io/gitea/routers/web/feed"
  24. "code.gitea.io/gitea/routers/web/org"
  25. shared_user "code.gitea.io/gitea/routers/web/shared/user"
  26. "code.gitea.io/gitea/services/context"
  27. feed_service "code.gitea.io/gitea/services/feed"
  28. )
  29. const (
  30. tplProfileBigAvatar templates.TplName = "shared/user/profile_big_avatar"
  31. tplFollowUnfollow templates.TplName = "org/follow_unfollow"
  32. )
  33. // OwnerProfile render profile page for a user or a organization (aka, repo owner)
  34. func OwnerProfile(ctx *context.Context) {
  35. if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") {
  36. feed.ShowUserFeedRSS(ctx)
  37. return
  38. }
  39. if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") {
  40. feed.ShowUserFeedAtom(ctx)
  41. return
  42. }
  43. if ctx.ContextUser.IsOrganization() {
  44. org.Home(ctx)
  45. } else {
  46. userProfile(ctx)
  47. }
  48. }
  49. func userProfile(ctx *context.Context) {
  50. // check view permissions
  51. if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
  52. ctx.NotFound(fmt.Errorf("%s", ctx.ContextUser.Name))
  53. return
  54. }
  55. ctx.Data["Title"] = ctx.ContextUser.DisplayName()
  56. ctx.Data["PageIsUserProfile"] = true
  57. profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer)
  58. prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob)
  59. // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing
  60. // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them
  61. // and the "profile readme" related logic also duplicates in both of FindOwnerProfileReadme and RenderUserOrgHeader
  62. // TODO: it is a bad design and should be refactored later,
  63. if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
  64. ctx.ServerError("RenderUserOrgHeader", err)
  65. return
  66. }
  67. ctx.HTML(http.StatusOK, tplProfile)
  68. }
  69. func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
  70. // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
  71. // if there is not a profile readme, the overview tab should be treated as the repositories tab
  72. tab := ctx.FormString("tab")
  73. if tab == "" || tab == "overview" {
  74. if profileReadme != nil {
  75. tab = "overview"
  76. } else {
  77. tab = "repositories"
  78. }
  79. }
  80. ctx.Data["TabName"] = tab
  81. ctx.Data["HasUserProfileReadme"] = profileReadme != nil
  82. page := ctx.FormInt("page")
  83. if page <= 0 {
  84. page = 1
  85. }
  86. pagingNum := setting.UI.User.RepoPagingNum
  87. topicOnly := ctx.FormBool("topic")
  88. var (
  89. repos []*repo_model.Repository
  90. count int64
  91. total int
  92. orderBy db.SearchOrderBy
  93. )
  94. sortOrder := ctx.FormString("sort")
  95. if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
  96. sortOrder = setting.UI.ExploreDefaultSort // TODO: add new default sort order for user home?
  97. }
  98. ctx.Data["SortType"] = sortOrder
  99. orderBy = repo_model.OrderByFlatMap[sortOrder]
  100. keyword := ctx.FormTrim("q")
  101. ctx.Data["Keyword"] = keyword
  102. language := ctx.FormTrim("language")
  103. ctx.Data["Language"] = language
  104. followers, numFollowers, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
  105. PageSize: pagingNum,
  106. Page: page,
  107. })
  108. if err != nil {
  109. ctx.ServerError("GetUserFollowers", err)
  110. return
  111. }
  112. ctx.Data["NumFollowers"] = numFollowers
  113. following, numFollowing, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
  114. PageSize: pagingNum,
  115. Page: page,
  116. })
  117. if err != nil {
  118. ctx.ServerError("GetUserFollowing", err)
  119. return
  120. }
  121. ctx.Data["NumFollowing"] = numFollowing
  122. archived := ctx.FormOptionalBool("archived")
  123. ctx.Data["IsArchived"] = archived
  124. fork := ctx.FormOptionalBool("fork")
  125. ctx.Data["IsFork"] = fork
  126. mirror := ctx.FormOptionalBool("mirror")
  127. ctx.Data["IsMirror"] = mirror
  128. template := ctx.FormOptionalBool("template")
  129. ctx.Data["IsTemplate"] = template
  130. private := ctx.FormOptionalBool("private")
  131. ctx.Data["IsPrivate"] = private
  132. switch tab {
  133. case "followers":
  134. ctx.Data["Cards"] = followers
  135. total = int(numFollowers)
  136. case "following":
  137. ctx.Data["Cards"] = following
  138. total = int(numFollowing)
  139. case "activity":
  140. // prepare heatmap data
  141. if setting.Service.EnableUserHeatmap {
  142. data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
  143. if err != nil {
  144. ctx.ServerError("GetUserHeatmapDataByUser", err)
  145. return
  146. }
  147. ctx.Data["HeatmapData"] = data
  148. ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
  149. }
  150. date := ctx.FormString("date")
  151. pagingNum = setting.UI.FeedPagingNum
  152. showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
  153. items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
  154. RequestedUser: ctx.ContextUser,
  155. Actor: ctx.Doer,
  156. IncludePrivate: showPrivate,
  157. OnlyPerformedBy: true,
  158. IncludeDeleted: false,
  159. Date: date,
  160. ListOptions: db.ListOptions{
  161. PageSize: pagingNum,
  162. Page: page,
  163. },
  164. })
  165. if err != nil {
  166. ctx.ServerError("GetFeeds", err)
  167. return
  168. }
  169. ctx.Data["Feeds"] = items
  170. ctx.Data["Date"] = date
  171. total = int(count)
  172. case "stars":
  173. ctx.Data["PageIsProfileStarList"] = true
  174. ctx.Data["ShowRepoOwnerOnList"] = true
  175. repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
  176. ListOptions: db.ListOptions{
  177. PageSize: pagingNum,
  178. Page: page,
  179. },
  180. Actor: ctx.Doer,
  181. Keyword: keyword,
  182. OrderBy: orderBy,
  183. Private: ctx.IsSigned,
  184. StarredByID: ctx.ContextUser.ID,
  185. Collaborate: optional.Some(false),
  186. TopicOnly: topicOnly,
  187. Language: language,
  188. IncludeDescription: setting.UI.SearchRepoDescription,
  189. Archived: archived,
  190. Fork: fork,
  191. Mirror: mirror,
  192. Template: template,
  193. IsPrivate: private,
  194. })
  195. if err != nil {
  196. ctx.ServerError("SearchRepository", err)
  197. return
  198. }
  199. total = int(count)
  200. case "watching":
  201. repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
  202. ListOptions: db.ListOptions{
  203. PageSize: pagingNum,
  204. Page: page,
  205. },
  206. Actor: ctx.Doer,
  207. Keyword: keyword,
  208. OrderBy: orderBy,
  209. Private: ctx.IsSigned,
  210. WatchedByID: ctx.ContextUser.ID,
  211. Collaborate: optional.Some(false),
  212. TopicOnly: topicOnly,
  213. Language: language,
  214. IncludeDescription: setting.UI.SearchRepoDescription,
  215. Archived: archived,
  216. Fork: fork,
  217. Mirror: mirror,
  218. Template: template,
  219. IsPrivate: private,
  220. })
  221. if err != nil {
  222. ctx.ServerError("SearchRepository", err)
  223. return
  224. }
  225. total = int(count)
  226. case "overview":
  227. if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
  228. log.Error("failed to GetBlobContent: %v", err)
  229. } else {
  230. rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
  231. CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
  232. })
  233. if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
  234. log.Error("failed to RenderString: %v", err)
  235. } else {
  236. ctx.Data["ProfileReadmeContent"] = profileContent
  237. }
  238. }
  239. case "organizations":
  240. orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
  241. UserID: ctx.ContextUser.ID,
  242. IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
  243. ListOptions: db.ListOptions{
  244. Page: page,
  245. PageSize: pagingNum,
  246. },
  247. })
  248. if err != nil {
  249. ctx.ServerError("GetUserOrganizations", err)
  250. return
  251. }
  252. ctx.Data["Cards"] = orgs
  253. total = int(count)
  254. default: // default to "repositories"
  255. repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
  256. ListOptions: db.ListOptions{
  257. PageSize: pagingNum,
  258. Page: page,
  259. },
  260. Actor: ctx.Doer,
  261. Keyword: keyword,
  262. OwnerID: ctx.ContextUser.ID,
  263. OrderBy: orderBy,
  264. Private: ctx.IsSigned,
  265. Collaborate: optional.Some(false),
  266. TopicOnly: topicOnly,
  267. Language: language,
  268. IncludeDescription: setting.UI.SearchRepoDescription,
  269. Archived: archived,
  270. Fork: fork,
  271. Mirror: mirror,
  272. Template: template,
  273. IsPrivate: private,
  274. })
  275. if err != nil {
  276. ctx.ServerError("SearchRepository", err)
  277. return
  278. }
  279. total = int(count)
  280. }
  281. ctx.Data["Repos"] = repos
  282. ctx.Data["Total"] = total
  283. if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
  284. ctx.ServerError("RenderUserOrgHeader", err)
  285. return
  286. }
  287. pager := context.NewPagination(total, pagingNum, page, 5)
  288. pager.AddParamFromRequest(ctx.Req)
  289. ctx.Data["Page"] = pager
  290. }
  291. // ActionUserFollow is for follow/unfollow user request
  292. func ActionUserFollow(ctx *context.Context) {
  293. var err error
  294. switch ctx.FormString("action") {
  295. case "follow":
  296. err = user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser)
  297. case "unfollow":
  298. err = user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
  299. }
  300. if err != nil {
  301. log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err)
  302. ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
  303. return
  304. }
  305. if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
  306. ctx.ServerError("RenderUserOrgHeader", err)
  307. return
  308. }
  309. if ctx.ContextUser.IsIndividual() {
  310. ctx.HTML(http.StatusOK, tplProfileBigAvatar)
  311. return
  312. } else if ctx.ContextUser.IsOrganization() {
  313. ctx.Data["Org"] = ctx.ContextUser
  314. ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
  315. ctx.HTML(http.StatusOK, tplFollowUnfollow)
  316. return
  317. }
  318. log.Error("Failed to apply action %q: unsupported context user type: %s", ctx.FormString("action"), ctx.ContextUser.Type)
  319. ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
  320. }