1
0

home.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. package user
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/http"
  6. "slices"
  7. "github.com/unknwon/paginater"
  8. "gogs.io/gogs/internal/conf"
  9. "gogs.io/gogs/internal/context"
  10. "gogs.io/gogs/internal/database"
  11. )
  12. const (
  13. tmplUserDashboard = "user/dashboard/dashboard"
  14. tmplUserDashboardFeeds = "user/dashboard/feeds"
  15. tmplUserDashboardIssues = "user/dashboard/issues"
  16. tmplUserProfile = "user/profile"
  17. tmplOrgHome = "org/home"
  18. )
  19. // getDashboardContextUser finds out dashboard is viewing as which context user.
  20. func getDashboardContextUser(c *context.Context) *database.User {
  21. ctxUser := c.User
  22. orgName := c.Params(":org")
  23. if len(orgName) > 0 {
  24. // Organization.
  25. org, err := database.Handle.Users().GetByUsername(c.Req.Context(), orgName)
  26. if err != nil {
  27. c.NotFoundOrError(err, "get user by name")
  28. return nil
  29. }
  30. ctxUser = org
  31. }
  32. c.Data["ContextUser"] = ctxUser
  33. orgs, err := database.Handle.Organizations().List(
  34. c.Req.Context(),
  35. database.ListOrgsOptions{
  36. MemberID: c.User.ID,
  37. IncludePrivateMembers: true,
  38. },
  39. )
  40. if err != nil {
  41. c.Error(err, "list organizations")
  42. return nil
  43. }
  44. c.Data["Orgs"] = orgs
  45. return ctxUser
  46. }
  47. // retrieveFeeds loads feeds from database by given context user.
  48. // The user could be organization so it is not always the logged in user,
  49. // which is why we have to explicitly pass the context user ID.
  50. func retrieveFeeds(c *context.Context, ctxUser *database.User, userID int64, isProfile bool) {
  51. afterID := c.QueryInt64("after_id")
  52. var err error
  53. var actions []*database.Action
  54. if ctxUser.IsOrganization() {
  55. actions, err = database.Handle.Actions().ListByOrganization(c.Req.Context(), ctxUser.ID, userID, afterID)
  56. } else {
  57. actions, err = database.Handle.Actions().ListByUser(c.Req.Context(), ctxUser.ID, userID, afterID, isProfile)
  58. }
  59. if err != nil {
  60. c.Error(err, "list actions")
  61. return
  62. }
  63. // Check access of private repositories.
  64. feeds := make([]*database.Action, 0, len(actions))
  65. unameAvatars := make(map[string]string)
  66. for _, act := range actions {
  67. // Cache results to reduce queries.
  68. _, ok := unameAvatars[act.ActUserName]
  69. if !ok {
  70. u, err := database.Handle.Users().GetByUsername(c.Req.Context(), act.ActUserName)
  71. if err != nil {
  72. if database.IsErrUserNotExist(err) {
  73. continue
  74. }
  75. c.Error(err, "get user by name")
  76. return
  77. }
  78. unameAvatars[act.ActUserName] = u.AvatarURLPath()
  79. }
  80. act.ActAvatar = unameAvatars[act.ActUserName]
  81. feeds = append(feeds, act)
  82. }
  83. c.Data["Feeds"] = feeds
  84. if len(feeds) > 0 {
  85. afterID := feeds[len(feeds)-1].ID
  86. c.Data["AfterID"] = afterID
  87. c.Header().Set("X-AJAX-URL", fmt.Sprintf("%s?after_id=%d", c.Data["Link"], afterID))
  88. }
  89. }
  90. func Dashboard(c *context.Context) {
  91. ctxUser := getDashboardContextUser(c)
  92. if c.Written() {
  93. return
  94. }
  95. retrieveFeeds(c, ctxUser, c.User.ID, false)
  96. if c.Written() {
  97. return
  98. }
  99. if c.Req.Header.Get("X-AJAX") == "true" {
  100. c.Success(tmplUserDashboardFeeds)
  101. return
  102. }
  103. c.Data["Title"] = ctxUser.DisplayName() + " - " + c.Tr("dashboard")
  104. c.Data["PageIsDashboard"] = true
  105. c.Data["PageIsNews"] = true
  106. // Only user can have collaborative repositories.
  107. if !ctxUser.IsOrganization() {
  108. collaborateRepos, err := database.Handle.Repositories().GetByCollaboratorID(c.Req.Context(), c.User.ID, conf.UI.User.RepoPagingNum, "updated_unix DESC")
  109. if err != nil {
  110. c.Error(err, "get accessible repositories by collaborator")
  111. return
  112. } else if err = database.RepositoryList(collaborateRepos).LoadAttributes(); err != nil {
  113. c.Error(err, "load attributes")
  114. return
  115. }
  116. c.Data["CollaborativeRepos"] = collaborateRepos
  117. }
  118. var err error
  119. var repos, mirrors []*database.Repository
  120. var repoCount int64
  121. if ctxUser.IsOrganization() {
  122. repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, conf.UI.User.RepoPagingNum)
  123. if err != nil {
  124. c.Error(err, "get user repositories")
  125. return
  126. }
  127. mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID)
  128. if err != nil {
  129. c.Error(err, "get user mirror repositories")
  130. return
  131. }
  132. } else {
  133. repos, err = database.GetUserRepositories(
  134. &database.UserRepoOptions{
  135. UserID: ctxUser.ID,
  136. Private: true,
  137. Page: 1,
  138. PageSize: conf.UI.User.RepoPagingNum,
  139. },
  140. )
  141. if err != nil {
  142. c.Error(err, "get repositories")
  143. return
  144. }
  145. repoCount = int64(ctxUser.NumRepos)
  146. mirrors, err = database.GetUserMirrorRepositories(ctxUser.ID)
  147. if err != nil {
  148. c.Error(err, "get mirror repositories")
  149. return
  150. }
  151. }
  152. c.Data["Repos"] = repos
  153. c.Data["RepoCount"] = repoCount
  154. c.Data["MaxShowRepoNum"] = conf.UI.User.RepoPagingNum
  155. if err := database.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
  156. c.Error(err, "load attributes")
  157. return
  158. }
  159. c.Data["MirrorCount"] = len(mirrors)
  160. c.Data["Mirrors"] = mirrors
  161. c.Success(tmplUserDashboard)
  162. }
  163. func Issues(c *context.Context) {
  164. isPullList := c.Params(":type") == "pulls"
  165. if isPullList {
  166. c.Data["Title"] = c.Tr("pull_requests")
  167. c.Data["PageIsPulls"] = true
  168. } else {
  169. c.Data["Title"] = c.Tr("issues")
  170. c.Data["PageIsIssues"] = true
  171. }
  172. ctxUser := getDashboardContextUser(c)
  173. if c.Written() {
  174. return
  175. }
  176. var (
  177. sortType = c.Query("sort")
  178. filterMode = database.FilterModeYourRepos
  179. )
  180. // Note: Organization does not have view type and filter mode.
  181. if !ctxUser.IsOrganization() {
  182. viewType := c.Query("type")
  183. types := []string{
  184. string(database.FilterModeYourRepos),
  185. string(database.FilterModeAssign),
  186. string(database.FilterModeCreate),
  187. }
  188. if !slices.Contains(types, viewType) {
  189. viewType = string(database.FilterModeYourRepos)
  190. }
  191. filterMode = database.FilterMode(viewType)
  192. }
  193. page := max(c.QueryInt("page"), 1)
  194. repoID := c.QueryInt64("repo")
  195. isShowClosed := c.Query("state") == "closed"
  196. // Get repositories.
  197. var (
  198. err error
  199. repos []*database.Repository
  200. userRepoIDs []int64
  201. showRepos = make([]*database.Repository, 0, 10)
  202. )
  203. if ctxUser.IsOrganization() {
  204. repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos)
  205. if err != nil {
  206. c.Error(err, "get repositories")
  207. return
  208. }
  209. } else {
  210. repos, err = database.GetUserRepositories(
  211. &database.UserRepoOptions{
  212. UserID: ctxUser.ID,
  213. Private: true,
  214. Page: 1,
  215. PageSize: ctxUser.NumRepos,
  216. },
  217. )
  218. if err != nil {
  219. c.Error(err, "get repositories")
  220. return
  221. }
  222. }
  223. userRepoIDs = make([]int64, 0, len(repos))
  224. for _, repo := range repos {
  225. userRepoIDs = append(userRepoIDs, repo.ID)
  226. if filterMode != database.FilterModeYourRepos {
  227. continue
  228. }
  229. if isPullList {
  230. if isShowClosed && repo.NumClosedPulls == 0 ||
  231. !isShowClosed && repo.NumOpenPulls == 0 {
  232. continue
  233. }
  234. } else {
  235. if !repo.EnableIssues || repo.EnableExternalTracker ||
  236. isShowClosed && repo.NumClosedIssues == 0 ||
  237. !isShowClosed && repo.NumOpenIssues == 0 {
  238. continue
  239. }
  240. }
  241. showRepos = append(showRepos, repo)
  242. }
  243. // Filter repositories if the page shows issues.
  244. if !isPullList {
  245. userRepoIDs, err = database.FilterRepositoryWithIssues(userRepoIDs)
  246. if err != nil {
  247. c.Error(err, "filter repositories with issues")
  248. return
  249. }
  250. }
  251. issueOptions := &database.IssuesOptions{
  252. RepoID: repoID,
  253. Page: page,
  254. IsClosed: isShowClosed,
  255. IsPull: isPullList,
  256. SortType: sortType,
  257. }
  258. switch filterMode {
  259. case database.FilterModeYourRepos:
  260. // Get all issues from repositories from this user.
  261. if userRepoIDs == nil {
  262. issueOptions.RepoIDs = []int64{-1}
  263. } else {
  264. issueOptions.RepoIDs = userRepoIDs
  265. }
  266. case database.FilterModeAssign:
  267. // Get all issues assigned to this user.
  268. issueOptions.AssigneeID = ctxUser.ID
  269. case database.FilterModeCreate:
  270. // Get all issues created by this user.
  271. issueOptions.PosterID = ctxUser.ID
  272. }
  273. issues, err := database.Issues(issueOptions)
  274. if err != nil {
  275. c.Error(err, "list issues")
  276. return
  277. }
  278. if repoID > 0 {
  279. repo, err := database.GetRepositoryByID(repoID)
  280. if err != nil {
  281. c.Error(err, "get repository by ID")
  282. return
  283. }
  284. if err = repo.GetOwner(); err != nil {
  285. c.Error(err, "get owner")
  286. return
  287. }
  288. // Check if user has access to given repository.
  289. if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) {
  290. c.NotFound()
  291. return
  292. }
  293. }
  294. for _, issue := range issues {
  295. if err = issue.Repo.GetOwner(); err != nil {
  296. c.Error(err, "get owner")
  297. return
  298. }
  299. }
  300. issueStats := database.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)
  301. var total int
  302. if !isShowClosed {
  303. total = int(issueStats.OpenCount)
  304. } else {
  305. total = int(issueStats.ClosedCount)
  306. }
  307. c.Data["Issues"] = issues
  308. c.Data["Repos"] = showRepos
  309. c.Data["Page"] = paginater.New(total, conf.UI.IssuePagingNum, page, 5)
  310. c.Data["IssueStats"] = issueStats
  311. c.Data["ViewType"] = string(filterMode)
  312. c.Data["SortType"] = sortType
  313. c.Data["RepoID"] = repoID
  314. c.Data["IsShowClosed"] = isShowClosed
  315. if isShowClosed {
  316. c.Data["State"] = "closed"
  317. } else {
  318. c.Data["State"] = "open"
  319. }
  320. c.Success(tmplUserDashboardIssues)
  321. }
  322. func ShowSSHKeys(c *context.Context, uid int64) {
  323. keys, err := database.ListPublicKeys(uid)
  324. if err != nil {
  325. c.Error(err, "list public keys")
  326. return
  327. }
  328. var buf bytes.Buffer
  329. for i := range keys {
  330. buf.WriteString(keys[i].OmitEmail())
  331. buf.WriteString("\n")
  332. }
  333. c.PlainText(http.StatusOK, buf.String())
  334. }
  335. func showOrgProfile(c *context.Context) {
  336. c.SetParams(":org", c.Params(":username"))
  337. context.HandleOrgAssignment(c)
  338. if c.Written() {
  339. return
  340. }
  341. org := c.Org.Organization
  342. c.Data["Title"] = org.FullName
  343. page := c.QueryInt("page")
  344. if page <= 0 {
  345. page = 1
  346. }
  347. var (
  348. repos []*database.Repository
  349. count int64
  350. err error
  351. )
  352. if c.IsLogged && !c.User.IsAdmin {
  353. repos, count, err = org.GetUserRepositories(c.User.ID, page, conf.UI.User.RepoPagingNum)
  354. if err != nil {
  355. c.Error(err, "get user repositories")
  356. return
  357. }
  358. c.Data["Repos"] = repos
  359. } else {
  360. showPrivate := c.IsLogged && c.User.IsAdmin
  361. repos, err = database.GetUserRepositories(&database.UserRepoOptions{
  362. UserID: org.ID,
  363. Private: showPrivate,
  364. Page: page,
  365. PageSize: conf.UI.User.RepoPagingNum,
  366. })
  367. if err != nil {
  368. c.Error(err, "get user repositories")
  369. return
  370. }
  371. c.Data["Repos"] = repos
  372. count = database.CountUserRepositories(org.ID, showPrivate)
  373. }
  374. c.Data["Page"] = paginater.New(int(count), conf.UI.User.RepoPagingNum, page, 5)
  375. if err := org.GetMembers(12); err != nil {
  376. c.Error(err, "get members")
  377. return
  378. }
  379. c.Data["Members"] = org.Members
  380. c.Data["Teams"] = org.Teams
  381. c.Success(tmplOrgHome)
  382. }
  383. func Email2User(c *context.Context) {
  384. u, err := database.Handle.Users().GetByEmail(c.Req.Context(), c.Query("email"))
  385. if err != nil {
  386. c.NotFoundOrError(err, "get user by email")
  387. return
  388. }
  389. c.Redirect(conf.Server.Subpath + "/user/" + u.Name)
  390. }