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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 models
  6. import (
  7. "fmt"
  8. "net/url"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/models/db"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/timeutil"
  19. "xorm.io/builder"
  20. )
  21. // ActionType represents the type of an action.
  22. type ActionType int
  23. // Possible action types.
  24. const (
  25. ActionCreateRepo ActionType = iota + 1 // 1
  26. ActionRenameRepo // 2
  27. ActionStarRepo // 3
  28. ActionWatchRepo // 4
  29. ActionCommitRepo // 5
  30. ActionCreateIssue // 6
  31. ActionCreatePullRequest // 7
  32. ActionTransferRepo // 8
  33. ActionPushTag // 9
  34. ActionCommentIssue // 10
  35. ActionMergePullRequest // 11
  36. ActionCloseIssue // 12
  37. ActionReopenIssue // 13
  38. ActionClosePullRequest // 14
  39. ActionReopenPullRequest // 15
  40. ActionDeleteTag // 16
  41. ActionDeleteBranch // 17
  42. ActionMirrorSyncPush // 18
  43. ActionMirrorSyncCreate // 19
  44. ActionMirrorSyncDelete // 20
  45. ActionApprovePullRequest // 21
  46. ActionRejectPullRequest // 22
  47. ActionCommentPull // 23
  48. ActionPublishRelease // 24
  49. ActionPullReviewDismissed // 25
  50. ActionPullRequestReadyForReview // 26
  51. )
  52. // Action represents user operation type and other information to
  53. // repository. It implemented interface base.Actioner so that can be
  54. // used in template render.
  55. type Action struct {
  56. ID int64 `xorm:"pk autoincr"`
  57. UserID int64 `xorm:"INDEX"` // Receiver user id.
  58. OpType ActionType
  59. ActUserID int64 `xorm:"INDEX"` // Action user id.
  60. ActUser *User `xorm:"-"`
  61. RepoID int64 `xorm:"INDEX"`
  62. Repo *Repository `xorm:"-"`
  63. CommentID int64 `xorm:"INDEX"`
  64. Comment *Comment `xorm:"-"`
  65. IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"`
  66. RefName string
  67. IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
  68. Content string `xorm:"TEXT"`
  69. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  70. }
  71. func init() {
  72. db.RegisterModel(new(Action))
  73. }
  74. // GetOpType gets the ActionType of this action.
  75. func (a *Action) GetOpType() ActionType {
  76. return a.OpType
  77. }
  78. // LoadActUser loads a.ActUser
  79. func (a *Action) LoadActUser() {
  80. if a.ActUser != nil {
  81. return
  82. }
  83. var err error
  84. a.ActUser, err = GetUserByID(a.ActUserID)
  85. if err == nil {
  86. return
  87. } else if IsErrUserNotExist(err) {
  88. a.ActUser = NewGhostUser()
  89. } else {
  90. log.Error("GetUserByID(%d): %v", a.ActUserID, err)
  91. }
  92. }
  93. func (a *Action) loadRepo() {
  94. if a.Repo != nil {
  95. return
  96. }
  97. var err error
  98. a.Repo, err = GetRepositoryByID(a.RepoID)
  99. if err != nil {
  100. log.Error("GetRepositoryByID(%d): %v", a.RepoID, err)
  101. }
  102. }
  103. // GetActFullName gets the action's user full name.
  104. func (a *Action) GetActFullName() string {
  105. a.LoadActUser()
  106. return a.ActUser.FullName
  107. }
  108. // GetActUserName gets the action's user name.
  109. func (a *Action) GetActUserName() string {
  110. a.LoadActUser()
  111. return a.ActUser.Name
  112. }
  113. // ShortActUserName gets the action's user name trimmed to max 20
  114. // chars.
  115. func (a *Action) ShortActUserName() string {
  116. return base.EllipsisString(a.GetActUserName(), 20)
  117. }
  118. // GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
  119. func (a *Action) GetDisplayName() string {
  120. if setting.UI.DefaultShowFullName {
  121. trimmedFullName := strings.TrimSpace(a.GetActFullName())
  122. if len(trimmedFullName) > 0 {
  123. return trimmedFullName
  124. }
  125. }
  126. return a.ShortActUserName()
  127. }
  128. // GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
  129. func (a *Action) GetDisplayNameTitle() string {
  130. if setting.UI.DefaultShowFullName {
  131. return a.ShortActUserName()
  132. }
  133. return a.GetActFullName()
  134. }
  135. // GetRepoUserName returns the name of the action repository owner.
  136. func (a *Action) GetRepoUserName() string {
  137. a.loadRepo()
  138. return a.Repo.OwnerName
  139. }
  140. // ShortRepoUserName returns the name of the action repository owner
  141. // trimmed to max 20 chars.
  142. func (a *Action) ShortRepoUserName() string {
  143. return base.EllipsisString(a.GetRepoUserName(), 20)
  144. }
  145. // GetRepoName returns the name of the action repository.
  146. func (a *Action) GetRepoName() string {
  147. a.loadRepo()
  148. return a.Repo.Name
  149. }
  150. // ShortRepoName returns the name of the action repository
  151. // trimmed to max 33 chars.
  152. func (a *Action) ShortRepoName() string {
  153. return base.EllipsisString(a.GetRepoName(), 33)
  154. }
  155. // GetRepoPath returns the virtual path to the action repository.
  156. func (a *Action) GetRepoPath() string {
  157. return path.Join(a.GetRepoUserName(), a.GetRepoName())
  158. }
  159. // ShortRepoPath returns the virtual path to the action repository
  160. // trimmed to max 20 + 1 + 33 chars.
  161. func (a *Action) ShortRepoPath() string {
  162. return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
  163. }
  164. // GetRepoLink returns relative link to action repository.
  165. func (a *Action) GetRepoLink() string {
  166. // path.Join will skip empty strings
  167. return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName()))
  168. }
  169. // GetRepositoryFromMatch returns a *Repository from a username and repo strings
  170. func GetRepositoryFromMatch(ownerName, repoName string) (*Repository, error) {
  171. var err error
  172. refRepo, err := GetRepositoryByOwnerAndName(ownerName, repoName)
  173. if err != nil {
  174. if IsErrRepoNotExist(err) {
  175. log.Warn("Repository referenced in commit but does not exist: %v", err)
  176. return nil, err
  177. }
  178. log.Error("GetRepositoryByOwnerAndName: %v", err)
  179. return nil, err
  180. }
  181. return refRepo, nil
  182. }
  183. // GetCommentLink returns link to action comment.
  184. func (a *Action) GetCommentLink() string {
  185. return a.getCommentLink(db.GetEngine(db.DefaultContext))
  186. }
  187. func (a *Action) getCommentLink(e db.Engine) string {
  188. if a == nil {
  189. return "#"
  190. }
  191. if a.Comment == nil && a.CommentID != 0 {
  192. a.Comment, _ = getCommentByID(e, a.CommentID)
  193. }
  194. if a.Comment != nil {
  195. return a.Comment.HTMLURL()
  196. }
  197. if len(a.GetIssueInfos()) == 0 {
  198. return "#"
  199. }
  200. // Return link to issue
  201. issueIDString := a.GetIssueInfos()[0]
  202. issueID, err := strconv.ParseInt(issueIDString, 10, 64)
  203. if err != nil {
  204. return "#"
  205. }
  206. issue, err := getIssueByID(e, issueID)
  207. if err != nil {
  208. return "#"
  209. }
  210. if err = issue.loadRepo(e); err != nil {
  211. return "#"
  212. }
  213. return issue.HTMLURL()
  214. }
  215. // GetBranch returns the action's repository branch.
  216. func (a *Action) GetBranch() string {
  217. return strings.TrimPrefix(a.RefName, git.BranchPrefix)
  218. }
  219. // GetTag returns the action's repository tag.
  220. func (a *Action) GetTag() string {
  221. return strings.TrimPrefix(a.RefName, git.TagPrefix)
  222. }
  223. // GetContent returns the action's content.
  224. func (a *Action) GetContent() string {
  225. return a.Content
  226. }
  227. // GetCreate returns the action creation time.
  228. func (a *Action) GetCreate() time.Time {
  229. return a.CreatedUnix.AsTime()
  230. }
  231. // GetIssueInfos returns a list of issues associated with
  232. // the action.
  233. func (a *Action) GetIssueInfos() []string {
  234. return strings.SplitN(a.Content, "|", 3)
  235. }
  236. // GetIssueTitle returns the title of first issue associated
  237. // with the action.
  238. func (a *Action) GetIssueTitle() string {
  239. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  240. issue, err := GetIssueByIndex(a.RepoID, index)
  241. if err != nil {
  242. log.Error("GetIssueByIndex: %v", err)
  243. return "500 when get issue"
  244. }
  245. return issue.Title
  246. }
  247. // GetIssueContent returns the content of first issue associated with
  248. // this action.
  249. func (a *Action) GetIssueContent() string {
  250. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  251. issue, err := GetIssueByIndex(a.RepoID, index)
  252. if err != nil {
  253. log.Error("GetIssueByIndex: %v", err)
  254. return "500 when get issue"
  255. }
  256. return issue.Content
  257. }
  258. // GetFeedsOptions options for retrieving feeds
  259. type GetFeedsOptions struct {
  260. RequestedUser *User // the user we want activity for
  261. RequestedTeam *Team // the team we want activity for
  262. Actor *User // the user viewing the activity
  263. IncludePrivate bool // include private actions
  264. OnlyPerformedBy bool // only actions performed by requested user
  265. IncludeDeleted bool // include deleted actions
  266. Date string // the day we want activity for: YYYY-MM-DD
  267. }
  268. // GetFeeds returns actions according to the provided options
  269. func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
  270. if !activityReadable(opts.RequestedUser, opts.Actor) {
  271. return make([]*Action, 0), nil
  272. }
  273. cond, err := activityQueryCondition(opts)
  274. if err != nil {
  275. return nil, err
  276. }
  277. actions := make([]*Action, 0, setting.UI.FeedPagingNum)
  278. if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
  279. return nil, fmt.Errorf("Find: %v", err)
  280. }
  281. if err := ActionList(actions).LoadAttributes(); err != nil {
  282. return nil, fmt.Errorf("LoadAttributes: %v", err)
  283. }
  284. return actions, nil
  285. }
  286. func activityReadable(user, doer *User) bool {
  287. var doerID int64
  288. if doer != nil {
  289. doerID = doer.ID
  290. }
  291. if doer == nil || !doer.IsAdmin {
  292. if user.KeepActivityPrivate && doerID != user.ID {
  293. return false
  294. }
  295. }
  296. return true
  297. }
  298. func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
  299. cond := builder.NewCond()
  300. var repoIDs []int64
  301. var actorID int64
  302. if opts.Actor != nil {
  303. actorID = opts.Actor.ID
  304. }
  305. // check readable repositories by doer/actor
  306. if opts.Actor == nil || !opts.Actor.IsAdmin {
  307. if opts.RequestedUser.IsOrganization() {
  308. env, err := OrgFromUser(opts.RequestedUser).AccessibleReposEnv(actorID)
  309. if err != nil {
  310. return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
  311. }
  312. if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
  313. return nil, fmt.Errorf("GetUserRepositories: %v", err)
  314. }
  315. cond = cond.And(builder.In("repo_id", repoIDs))
  316. } else {
  317. cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
  318. }
  319. }
  320. if opts.RequestedTeam != nil {
  321. env := OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam)
  322. teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
  323. if err != nil {
  324. return nil, fmt.Errorf("GetTeamRepositories: %v", err)
  325. }
  326. cond = cond.And(builder.In("repo_id", teamRepoIDs))
  327. }
  328. cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
  329. if opts.OnlyPerformedBy {
  330. cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
  331. }
  332. if !opts.IncludePrivate {
  333. cond = cond.And(builder.Eq{"is_private": false})
  334. }
  335. if !opts.IncludeDeleted {
  336. cond = cond.And(builder.Eq{"is_deleted": false})
  337. }
  338. if opts.Date != "" {
  339. dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
  340. if err != nil {
  341. log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
  342. } else {
  343. dateHigh := dateLow.Add(86399000000000) // 23h59m59s
  344. cond = cond.And(builder.Gte{"created_unix": dateLow.Unix()})
  345. cond = cond.And(builder.Lte{"created_unix": dateHigh.Unix()})
  346. }
  347. }
  348. return cond, nil
  349. }
  350. // DeleteOldActions deletes all old actions from database.
  351. func DeleteOldActions(olderThan time.Duration) (err error) {
  352. if olderThan <= 0 {
  353. return nil
  354. }
  355. _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{})
  356. return
  357. }