You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

home.go 14 kB

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package user
  6. import (
  7. "bytes"
  8. "fmt"
  9. "sort"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/base"
  13. "code.gitea.io/gitea/modules/context"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/util"
  17. "github.com/keybase/go-crypto/openpgp"
  18. "github.com/keybase/go-crypto/openpgp/armor"
  19. "github.com/unknwon/com"
  20. )
  21. const (
  22. tplDashboard base.TplName = "user/dashboard/dashboard"
  23. tplIssues base.TplName = "user/dashboard/issues"
  24. tplProfile base.TplName = "user/profile"
  25. tplOrgHome base.TplName = "org/home"
  26. )
  27. // getDashboardContextUser finds out dashboard is viewing as which context user.
  28. func getDashboardContextUser(ctx *context.Context) *models.User {
  29. ctxUser := ctx.User
  30. orgName := ctx.Params(":org")
  31. if len(orgName) > 0 {
  32. // Organization.
  33. org, err := models.GetUserByName(orgName)
  34. if err != nil {
  35. if models.IsErrUserNotExist(err) {
  36. ctx.NotFound("GetUserByName", err)
  37. } else {
  38. ctx.ServerError("GetUserByName", err)
  39. }
  40. return nil
  41. }
  42. ctxUser = org
  43. }
  44. ctx.Data["ContextUser"] = ctxUser
  45. if err := ctx.User.GetOrganizations(true); err != nil {
  46. ctx.ServerError("GetOrganizations", err)
  47. return nil
  48. }
  49. ctx.Data["Orgs"] = ctx.User.Orgs
  50. return ctxUser
  51. }
  52. // retrieveFeeds loads feeds for the specified user
  53. func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) {
  54. actions, err := models.GetFeeds(options)
  55. if err != nil {
  56. ctx.ServerError("GetFeeds", err)
  57. return
  58. }
  59. userCache := map[int64]*models.User{options.RequestedUser.ID: options.RequestedUser}
  60. if ctx.User != nil {
  61. userCache[ctx.User.ID] = ctx.User
  62. }
  63. for _, act := range actions {
  64. if act.ActUser != nil {
  65. userCache[act.ActUserID] = act.ActUser
  66. }
  67. repoOwner, ok := userCache[act.Repo.OwnerID]
  68. if !ok {
  69. repoOwner, err = models.GetUserByID(act.Repo.OwnerID)
  70. if err != nil {
  71. if models.IsErrUserNotExist(err) {
  72. continue
  73. }
  74. ctx.ServerError("GetUserByID", err)
  75. return
  76. }
  77. userCache[repoOwner.ID] = repoOwner
  78. }
  79. act.Repo.Owner = repoOwner
  80. }
  81. ctx.Data["Feeds"] = actions
  82. }
  83. // Dashboard render the dashborad page
  84. func Dashboard(ctx *context.Context) {
  85. ctxUser := getDashboardContextUser(ctx)
  86. if ctx.Written() {
  87. return
  88. }
  89. ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Tr("dashboard")
  90. ctx.Data["PageIsDashboard"] = true
  91. ctx.Data["PageIsNews"] = true
  92. ctx.Data["SearchLimit"] = setting.UI.User.RepoPagingNum
  93. ctx.Data["EnableHeatmap"] = setting.Service.EnableUserHeatmap
  94. ctx.Data["HeatmapUser"] = ctxUser.Name
  95. var err error
  96. var mirrors []*models.Repository
  97. if ctxUser.IsOrganization() {
  98. env, err := ctxUser.AccessibleReposEnv(ctx.User.ID)
  99. if err != nil {
  100. ctx.ServerError("AccessibleReposEnv", err)
  101. return
  102. }
  103. mirrors, err = env.MirrorRepos()
  104. if err != nil {
  105. ctx.ServerError("env.MirrorRepos", err)
  106. return
  107. }
  108. } else {
  109. mirrors, err = ctxUser.GetMirrorRepositories()
  110. if err != nil {
  111. ctx.ServerError("GetMirrorRepositories", err)
  112. return
  113. }
  114. }
  115. ctx.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum
  116. if err := models.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
  117. ctx.ServerError("MirrorRepositoryList.LoadAttributes", err)
  118. return
  119. }
  120. ctx.Data["MirrorCount"] = len(mirrors)
  121. ctx.Data["Mirrors"] = mirrors
  122. retrieveFeeds(ctx, models.GetFeedsOptions{
  123. RequestedUser: ctxUser,
  124. IncludePrivate: true,
  125. OnlyPerformedBy: false,
  126. IncludeDeleted: false,
  127. })
  128. if ctx.Written() {
  129. return
  130. }
  131. ctx.HTML(200, tplDashboard)
  132. }
  133. // Issues render the user issues page
  134. func Issues(ctx *context.Context) {
  135. isPullList := ctx.Params(":type") == "pulls"
  136. if isPullList {
  137. ctx.Data["Title"] = ctx.Tr("pull_requests")
  138. ctx.Data["PageIsPulls"] = true
  139. } else {
  140. ctx.Data["Title"] = ctx.Tr("issues")
  141. ctx.Data["PageIsIssues"] = true
  142. }
  143. ctxUser := getDashboardContextUser(ctx)
  144. if ctx.Written() {
  145. return
  146. }
  147. // Organization does not have view type and filter mode.
  148. var (
  149. viewType string
  150. sortType = ctx.Query("sort")
  151. filterMode = models.FilterModeAll
  152. )
  153. if ctxUser.IsOrganization() {
  154. viewType = "all"
  155. } else {
  156. viewType = ctx.Query("type")
  157. switch viewType {
  158. case "assigned":
  159. filterMode = models.FilterModeAssign
  160. case "created_by":
  161. filterMode = models.FilterModeCreate
  162. case "mentioned":
  163. filterMode = models.FilterModeMention
  164. case "all": // filterMode already set to All
  165. default:
  166. viewType = "all"
  167. }
  168. }
  169. page := ctx.QueryInt("page")
  170. if page <= 1 {
  171. page = 1
  172. }
  173. repoID := ctx.QueryInt64("repo")
  174. isShowClosed := ctx.Query("state") == "closed"
  175. // Get repositories.
  176. var err error
  177. var userRepoIDs []int64
  178. if ctxUser.IsOrganization() {
  179. env, err := ctxUser.AccessibleReposEnv(ctx.User.ID)
  180. if err != nil {
  181. ctx.ServerError("AccessibleReposEnv", err)
  182. return
  183. }
  184. userRepoIDs, err = env.RepoIDs(1, ctxUser.NumRepos)
  185. if err != nil {
  186. ctx.ServerError("env.RepoIDs", err)
  187. return
  188. }
  189. } else {
  190. unitType := models.UnitTypeIssues
  191. if isPullList {
  192. unitType = models.UnitTypePullRequests
  193. }
  194. userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType)
  195. if err != nil {
  196. ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
  197. return
  198. }
  199. }
  200. if len(userRepoIDs) == 0 {
  201. userRepoIDs = []int64{-1}
  202. }
  203. opts := &models.IssuesOptions{
  204. IsClosed: util.OptionalBoolOf(isShowClosed),
  205. IsPull: util.OptionalBoolOf(isPullList),
  206. SortType: sortType,
  207. }
  208. if repoID > 0 {
  209. opts.RepoIDs = []int64{repoID}
  210. }
  211. switch filterMode {
  212. case models.FilterModeAll:
  213. if repoID > 0 {
  214. if !com.IsSliceContainsInt64(userRepoIDs, repoID) {
  215. // force an empty result
  216. opts.RepoIDs = []int64{-1}
  217. }
  218. } else {
  219. opts.RepoIDs = userRepoIDs
  220. }
  221. case models.FilterModeAssign:
  222. opts.AssigneeID = ctxUser.ID
  223. case models.FilterModeCreate:
  224. opts.PosterID = ctxUser.ID
  225. case models.FilterModeMention:
  226. opts.MentionedID = ctxUser.ID
  227. }
  228. counts, err := models.CountIssuesByRepo(opts)
  229. if err != nil {
  230. ctx.ServerError("CountIssuesByRepo", err)
  231. return
  232. }
  233. opts.Page = page
  234. opts.PageSize = setting.UI.IssuePagingNum
  235. var labelIDs []int64
  236. selectLabels := ctx.Query("labels")
  237. if len(selectLabels) > 0 && selectLabels != "0" {
  238. labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
  239. if err != nil {
  240. ctx.ServerError("StringsToInt64s", err)
  241. return
  242. }
  243. }
  244. opts.LabelIDs = labelIDs
  245. issues, err := models.Issues(opts)
  246. if err != nil {
  247. ctx.ServerError("Issues", err)
  248. return
  249. }
  250. showReposMap := make(map[int64]*models.Repository, len(counts))
  251. for repoID := range counts {
  252. repo, err := models.GetRepositoryByID(repoID)
  253. if err != nil {
  254. ctx.ServerError("GetRepositoryByID", err)
  255. return
  256. }
  257. showReposMap[repoID] = repo
  258. }
  259. if repoID > 0 {
  260. if _, ok := showReposMap[repoID]; !ok {
  261. repo, err := models.GetRepositoryByID(repoID)
  262. if models.IsErrRepoNotExist(err) {
  263. ctx.NotFound("GetRepositoryByID", err)
  264. return
  265. } else if err != nil {
  266. ctx.ServerError("GetRepositoryByID", fmt.Errorf("[%d]%v", repoID, err))
  267. return
  268. }
  269. showReposMap[repoID] = repo
  270. }
  271. repo := showReposMap[repoID]
  272. // Check if user has access to given repository.
  273. perm, err := models.GetUserRepoPermission(repo, ctxUser)
  274. if err != nil {
  275. ctx.ServerError("GetUserRepoPermission", fmt.Errorf("[%d]%v", repoID, err))
  276. return
  277. }
  278. if !perm.CanRead(models.UnitTypeIssues) {
  279. if log.IsTrace() {
  280. log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
  281. "User in repo has Permissions: %-+v",
  282. ctxUser,
  283. models.UnitTypeIssues,
  284. repo,
  285. perm)
  286. }
  287. ctx.Status(404)
  288. return
  289. }
  290. }
  291. showRepos := models.RepositoryListOfMap(showReposMap)
  292. sort.Sort(showRepos)
  293. if err = showRepos.LoadAttributes(); err != nil {
  294. ctx.ServerError("LoadAttributes", err)
  295. return
  296. }
  297. var commitStatus = make(map[int64]*models.CommitStatus, len(issues))
  298. for _, issue := range issues {
  299. issue.Repo = showReposMap[issue.RepoID]
  300. if isPullList {
  301. commitStatus[issue.PullRequest.ID], _ = issue.PullRequest.GetLastCommitStatus()
  302. }
  303. }
  304. issueStats, err := models.GetUserIssueStats(models.UserIssueStatsOptions{
  305. UserID: ctxUser.ID,
  306. RepoID: repoID,
  307. UserRepoIDs: userRepoIDs,
  308. FilterMode: filterMode,
  309. IsPull: isPullList,
  310. IsClosed: isShowClosed,
  311. })
  312. if err != nil {
  313. ctx.ServerError("GetUserIssueStats", err)
  314. return
  315. }
  316. var total int
  317. if !isShowClosed {
  318. total = int(issueStats.OpenCount)
  319. } else {
  320. total = int(issueStats.ClosedCount)
  321. }
  322. ctx.Data["Issues"] = issues
  323. ctx.Data["CommitStatus"] = commitStatus
  324. ctx.Data["Repos"] = showRepos
  325. ctx.Data["Counts"] = counts
  326. ctx.Data["IssueStats"] = issueStats
  327. ctx.Data["ViewType"] = viewType
  328. ctx.Data["SortType"] = sortType
  329. ctx.Data["RepoID"] = repoID
  330. ctx.Data["IsShowClosed"] = isShowClosed
  331. if isShowClosed {
  332. ctx.Data["State"] = "closed"
  333. } else {
  334. ctx.Data["State"] = "open"
  335. }
  336. pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
  337. pager.AddParam(ctx, "type", "ViewType")
  338. pager.AddParam(ctx, "repo", "RepoID")
  339. pager.AddParam(ctx, "sort", "SortType")
  340. pager.AddParam(ctx, "state", "State")
  341. pager.AddParam(ctx, "labels", "SelectLabels")
  342. pager.AddParam(ctx, "milestone", "MilestoneID")
  343. pager.AddParam(ctx, "assignee", "AssigneeID")
  344. ctx.Data["Page"] = pager
  345. ctx.HTML(200, tplIssues)
  346. }
  347. // ShowSSHKeys output all the ssh keys of user by uid
  348. func ShowSSHKeys(ctx *context.Context, uid int64) {
  349. keys, err := models.ListPublicKeys(uid)
  350. if err != nil {
  351. ctx.ServerError("ListPublicKeys", err)
  352. return
  353. }
  354. var buf bytes.Buffer
  355. for i := range keys {
  356. buf.WriteString(keys[i].OmitEmail())
  357. buf.WriteString("\n")
  358. }
  359. ctx.PlainText(200, buf.Bytes())
  360. }
  361. // ShowGPGKeys output all the public GPG keys of user by uid
  362. func ShowGPGKeys(ctx *context.Context, uid int64) {
  363. keys, err := models.ListGPGKeys(uid)
  364. if err != nil {
  365. ctx.ServerError("ListGPGKeys", err)
  366. return
  367. }
  368. entities := make([]*openpgp.Entity, 0)
  369. failedEntitiesID := make([]string, 0)
  370. for _, k := range keys {
  371. e, err := models.GPGKeyToEntity(k)
  372. if err != nil {
  373. if models.IsErrGPGKeyImportNotExist(err) {
  374. failedEntitiesID = append(failedEntitiesID, k.KeyID)
  375. continue //Skip previous import without backup of imported armored key
  376. }
  377. ctx.ServerError("ShowGPGKeys", err)
  378. return
  379. }
  380. entities = append(entities, e)
  381. }
  382. var buf bytes.Buffer
  383. headers := make(map[string]string)
  384. if len(failedEntitiesID) > 0 { //If some key need re-import to be exported
  385. headers["Note"] = fmt.Sprintf("The keys with the following IDs couldn't be exported and need to be reuploaded %s", strings.Join(failedEntitiesID, ", "))
  386. }
  387. writer, _ := armor.Encode(&buf, "PGP PUBLIC KEY BLOCK", headers)
  388. for _, e := range entities {
  389. err = e.Serialize(writer) //TODO find why key are exported with a different cipherTypeByte as original (should not be blocking but strange)
  390. if err != nil {
  391. ctx.ServerError("ShowGPGKeys", err)
  392. return
  393. }
  394. }
  395. writer.Close()
  396. ctx.PlainText(200, buf.Bytes())
  397. }
  398. func showOrgProfile(ctx *context.Context) {
  399. ctx.SetParams(":org", ctx.Params(":username"))
  400. context.HandleOrgAssignment(ctx)
  401. if ctx.Written() {
  402. return
  403. }
  404. org := ctx.Org.Organization
  405. if !models.HasOrgVisible(org, ctx.User) {
  406. ctx.NotFound("HasOrgVisible", nil)
  407. return
  408. }
  409. ctx.Data["Title"] = org.DisplayName()
  410. var orderBy models.SearchOrderBy
  411. ctx.Data["SortType"] = ctx.Query("sort")
  412. switch ctx.Query("sort") {
  413. case "newest":
  414. orderBy = models.SearchOrderByNewest
  415. case "oldest":
  416. orderBy = models.SearchOrderByOldest
  417. case "recentupdate":
  418. orderBy = models.SearchOrderByRecentUpdated
  419. case "leastupdate":
  420. orderBy = models.SearchOrderByLeastUpdated
  421. case "reversealphabetically":
  422. orderBy = models.SearchOrderByAlphabeticallyReverse
  423. case "alphabetically":
  424. orderBy = models.SearchOrderByAlphabetically
  425. case "moststars":
  426. orderBy = models.SearchOrderByStarsReverse
  427. case "feweststars":
  428. orderBy = models.SearchOrderByStars
  429. case "mostforks":
  430. orderBy = models.SearchOrderByForksReverse
  431. case "fewestforks":
  432. orderBy = models.SearchOrderByForks
  433. default:
  434. ctx.Data["SortType"] = "recentupdate"
  435. orderBy = models.SearchOrderByRecentUpdated
  436. }
  437. keyword := strings.Trim(ctx.Query("q"), " ")
  438. ctx.Data["Keyword"] = keyword
  439. page := ctx.QueryInt("page")
  440. if page <= 0 {
  441. page = 1
  442. }
  443. var (
  444. repos []*models.Repository
  445. count int64
  446. err error
  447. )
  448. repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
  449. Keyword: keyword,
  450. OwnerID: org.ID,
  451. OrderBy: orderBy,
  452. Private: ctx.IsSigned,
  453. UserIsAdmin: ctx.IsUserSiteAdmin(),
  454. UserID: ctx.Data["SignedUserID"].(int64),
  455. Page: page,
  456. IsProfile: true,
  457. PageSize: setting.UI.User.RepoPagingNum,
  458. IncludeDescription: setting.UI.SearchRepoDescription,
  459. })
  460. if err != nil {
  461. ctx.ServerError("SearchRepository", err)
  462. return
  463. }
  464. if err := org.GetMembers(); err != nil {
  465. ctx.ServerError("GetMembers", err)
  466. return
  467. }
  468. ctx.Data["Repos"] = repos
  469. ctx.Data["Total"] = count
  470. ctx.Data["Members"] = org.Members
  471. ctx.Data["Teams"] = org.Teams
  472. pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
  473. pager.SetDefaultParams(ctx)
  474. ctx.Data["Page"] = pager
  475. ctx.HTML(200, tplOrgHome)
  476. }
  477. // Email2User show user page via email
  478. func Email2User(ctx *context.Context) {
  479. u, err := models.GetUserByEmail(ctx.Query("email"))
  480. if err != nil {
  481. if models.IsErrUserNotExist(err) {
  482. ctx.NotFound("GetUserByEmail", err)
  483. } else {
  484. ctx.ServerError("GetUserByEmail", err)
  485. }
  486. return
  487. }
  488. ctx.Redirect(setting.AppSubURL + "/user/" + u.Name)
  489. }