home.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package user
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/http"
  6. "github.com/unknwon/com"
  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 !com.IsSliceContainsStr(types, viewType) {
  189. viewType = string(database.FilterModeYourRepos)
  190. }
  191. filterMode = database.FilterMode(viewType)
  192. }
  193. page := c.QueryInt("page")
  194. if page <= 1 {
  195. page = 1
  196. }
  197. repoID := c.QueryInt64("repo")
  198. isShowClosed := c.Query("state") == "closed"
  199. // Get repositories.
  200. var (
  201. err error
  202. repos []*database.Repository
  203. userRepoIDs []int64
  204. showRepos = make([]*database.Repository, 0, 10)
  205. )
  206. if ctxUser.IsOrganization() {
  207. repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos)
  208. if err != nil {
  209. c.Error(err, "get repositories")
  210. return
  211. }
  212. } else {
  213. repos, err = database.GetUserRepositories(
  214. &database.UserRepoOptions{
  215. UserID: ctxUser.ID,
  216. Private: true,
  217. Page: 1,
  218. PageSize: ctxUser.NumRepos,
  219. },
  220. )
  221. if err != nil {
  222. c.Error(err, "get repositories")
  223. return
  224. }
  225. }
  226. userRepoIDs = make([]int64, 0, len(repos))
  227. for _, repo := range repos {
  228. userRepoIDs = append(userRepoIDs, repo.ID)
  229. if filterMode != database.FilterModeYourRepos {
  230. continue
  231. }
  232. if isPullList {
  233. if isShowClosed && repo.NumClosedPulls == 0 ||
  234. !isShowClosed && repo.NumOpenPulls == 0 {
  235. continue
  236. }
  237. } else {
  238. if !repo.EnableIssues || repo.EnableExternalTracker ||
  239. isShowClosed && repo.NumClosedIssues == 0 ||
  240. !isShowClosed && repo.NumOpenIssues == 0 {
  241. continue
  242. }
  243. }
  244. showRepos = append(showRepos, repo)
  245. }
  246. // Filter repositories if the page shows issues.
  247. if !isPullList {
  248. userRepoIDs, err = database.FilterRepositoryWithIssues(userRepoIDs)
  249. if err != nil {
  250. c.Error(err, "filter repositories with issues")
  251. return
  252. }
  253. }
  254. issueOptions := &database.IssuesOptions{
  255. RepoID: repoID,
  256. Page: page,
  257. IsClosed: isShowClosed,
  258. IsPull: isPullList,
  259. SortType: sortType,
  260. }
  261. switch filterMode {
  262. case database.FilterModeYourRepos:
  263. // Get all issues from repositories from this user.
  264. if userRepoIDs == nil {
  265. issueOptions.RepoIDs = []int64{-1}
  266. } else {
  267. issueOptions.RepoIDs = userRepoIDs
  268. }
  269. case database.FilterModeAssign:
  270. // Get all issues assigned to this user.
  271. issueOptions.AssigneeID = ctxUser.ID
  272. case database.FilterModeCreate:
  273. // Get all issues created by this user.
  274. issueOptions.PosterID = ctxUser.ID
  275. }
  276. issues, err := database.Issues(issueOptions)
  277. if err != nil {
  278. c.Error(err, "list issues")
  279. return
  280. }
  281. if repoID > 0 {
  282. repo, err := database.GetRepositoryByID(repoID)
  283. if err != nil {
  284. c.Error(err, "get repository by ID")
  285. return
  286. }
  287. if err = repo.GetOwner(); err != nil {
  288. c.Error(err, "get owner")
  289. return
  290. }
  291. // Check if user has access to given repository.
  292. if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) {
  293. c.NotFound()
  294. return
  295. }
  296. }
  297. for _, issue := range issues {
  298. if err = issue.Repo.GetOwner(); err != nil {
  299. c.Error(err, "get owner")
  300. return
  301. }
  302. }
  303. issueStats := database.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)
  304. var total int
  305. if !isShowClosed {
  306. total = int(issueStats.OpenCount)
  307. } else {
  308. total = int(issueStats.ClosedCount)
  309. }
  310. c.Data["Issues"] = issues
  311. c.Data["Repos"] = showRepos
  312. c.Data["Page"] = paginater.New(total, conf.UI.IssuePagingNum, page, 5)
  313. c.Data["IssueStats"] = issueStats
  314. c.Data["ViewType"] = string(filterMode)
  315. c.Data["SortType"] = sortType
  316. c.Data["RepoID"] = repoID
  317. c.Data["IsShowClosed"] = isShowClosed
  318. if isShowClosed {
  319. c.Data["State"] = "closed"
  320. } else {
  321. c.Data["State"] = "open"
  322. }
  323. c.Success(tmplUserDashboardIssues)
  324. }
  325. func ShowSSHKeys(c *context.Context, uid int64) {
  326. keys, err := database.ListPublicKeys(uid)
  327. if err != nil {
  328. c.Error(err, "list public keys")
  329. return
  330. }
  331. var buf bytes.Buffer
  332. for i := range keys {
  333. buf.WriteString(keys[i].OmitEmail())
  334. buf.WriteString("\n")
  335. }
  336. c.PlainText(http.StatusOK, buf.String())
  337. }
  338. func showOrgProfile(c *context.Context) {
  339. c.SetParams(":org", c.Params(":username"))
  340. context.HandleOrgAssignment(c)
  341. if c.Written() {
  342. return
  343. }
  344. org := c.Org.Organization
  345. c.Data["Title"] = org.FullName
  346. page := c.QueryInt("page")
  347. if page <= 0 {
  348. page = 1
  349. }
  350. var (
  351. repos []*database.Repository
  352. count int64
  353. err error
  354. )
  355. if c.IsLogged && !c.User.IsAdmin {
  356. repos, count, err = org.GetUserRepositories(c.User.ID, page, conf.UI.User.RepoPagingNum)
  357. if err != nil {
  358. c.Error(err, "get user repositories")
  359. return
  360. }
  361. c.Data["Repos"] = repos
  362. } else {
  363. showPrivate := c.IsLogged && c.User.IsAdmin
  364. repos, err = database.GetUserRepositories(&database.UserRepoOptions{
  365. UserID: org.ID,
  366. Private: showPrivate,
  367. Page: page,
  368. PageSize: conf.UI.User.RepoPagingNum,
  369. })
  370. if err != nil {
  371. c.Error(err, "get user repositories")
  372. return
  373. }
  374. c.Data["Repos"] = repos
  375. count = database.CountUserRepositories(org.ID, showPrivate)
  376. }
  377. c.Data["Page"] = paginater.New(int(count), conf.UI.User.RepoPagingNum, page, 5)
  378. if err := org.GetMembers(12); err != nil {
  379. c.Error(err, "get members")
  380. return
  381. }
  382. c.Data["Members"] = org.Members
  383. c.Data["Teams"] = org.Teams
  384. c.Success(tmplOrgHome)
  385. }
  386. func Email2User(c *context.Context) {
  387. u, err := database.Handle.Users().GetByEmail(c.Req.Context(), c.Query("email"))
  388. if err != nil {
  389. c.NotFoundOrError(err, "get user by email")
  390. return
  391. }
  392. c.Redirect(conf.Server.Subpath + "/user/" + u.Name)
  393. }