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.

action.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package activities
  5. import (
  6. "context"
  7. "fmt"
  8. "net/url"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/models/db"
  14. issues_model "code.gitea.io/gitea/models/issues"
  15. "code.gitea.io/gitea/models/organization"
  16. access_model "code.gitea.io/gitea/models/perm/access"
  17. repo_model "code.gitea.io/gitea/models/repo"
  18. "code.gitea.io/gitea/models/unit"
  19. user_model "code.gitea.io/gitea/models/user"
  20. "code.gitea.io/gitea/modules/base"
  21. "code.gitea.io/gitea/modules/git"
  22. "code.gitea.io/gitea/modules/log"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/timeutil"
  26. "xorm.io/builder"
  27. "xorm.io/xorm/schemas"
  28. )
  29. // ActionType represents the type of an action.
  30. type ActionType int
  31. // Possible action types.
  32. const (
  33. ActionCreateRepo ActionType = iota + 1 // 1
  34. ActionRenameRepo // 2
  35. ActionStarRepo // 3
  36. ActionWatchRepo // 4
  37. ActionCommitRepo // 5
  38. ActionCreateIssue // 6
  39. ActionCreatePullRequest // 7
  40. ActionTransferRepo // 8
  41. ActionPushTag // 9
  42. ActionCommentIssue // 10
  43. ActionMergePullRequest // 11
  44. ActionCloseIssue // 12
  45. ActionReopenIssue // 13
  46. ActionClosePullRequest // 14
  47. ActionReopenPullRequest // 15
  48. ActionDeleteTag // 16
  49. ActionDeleteBranch // 17
  50. ActionMirrorSyncPush // 18
  51. ActionMirrorSyncCreate // 19
  52. ActionMirrorSyncDelete // 20
  53. ActionApprovePullRequest // 21
  54. ActionRejectPullRequest // 22
  55. ActionCommentPull // 23
  56. ActionPublishRelease // 24
  57. ActionPullReviewDismissed // 25
  58. ActionPullRequestReadyForReview // 26
  59. ActionAutoMergePullRequest // 27
  60. )
  61. func (at ActionType) String() string {
  62. switch at {
  63. case ActionCreateRepo:
  64. return "create_repo"
  65. case ActionRenameRepo:
  66. return "rename_repo"
  67. case ActionStarRepo:
  68. return "star_repo"
  69. case ActionWatchRepo:
  70. return "watch_repo"
  71. case ActionCommitRepo:
  72. return "commit_repo"
  73. case ActionCreateIssue:
  74. return "create_issue"
  75. case ActionCreatePullRequest:
  76. return "create_pull_request"
  77. case ActionTransferRepo:
  78. return "transfer_repo"
  79. case ActionPushTag:
  80. return "push_tag"
  81. case ActionCommentIssue:
  82. return "comment_issue"
  83. case ActionMergePullRequest:
  84. return "merge_pull_request"
  85. case ActionCloseIssue:
  86. return "close_issue"
  87. case ActionReopenIssue:
  88. return "reopen_issue"
  89. case ActionClosePullRequest:
  90. return "close_pull_request"
  91. case ActionReopenPullRequest:
  92. return "reopen_pull_request"
  93. case ActionDeleteTag:
  94. return "delete_tag"
  95. case ActionDeleteBranch:
  96. return "delete_branch"
  97. case ActionMirrorSyncPush:
  98. return "mirror_sync_push"
  99. case ActionMirrorSyncCreate:
  100. return "mirror_sync_create"
  101. case ActionMirrorSyncDelete:
  102. return "mirror_sync_delete"
  103. case ActionApprovePullRequest:
  104. return "approve_pull_request"
  105. case ActionRejectPullRequest:
  106. return "reject_pull_request"
  107. case ActionCommentPull:
  108. return "comment_pull"
  109. case ActionPublishRelease:
  110. return "publish_release"
  111. case ActionPullReviewDismissed:
  112. return "pull_review_dismissed"
  113. case ActionPullRequestReadyForReview:
  114. return "pull_request_ready_for_review"
  115. case ActionAutoMergePullRequest:
  116. return "auto_merge_pull_request"
  117. default:
  118. return "action-" + strconv.Itoa(int(at))
  119. }
  120. }
  121. func (at ActionType) InActions(actions ...string) bool {
  122. for _, action := range actions {
  123. if action == at.String() {
  124. return true
  125. }
  126. }
  127. return false
  128. }
  129. // Action represents user operation type and other information to
  130. // repository. It implemented interface base.Actioner so that can be
  131. // used in template render.
  132. type Action struct {
  133. ID int64 `xorm:"pk autoincr"`
  134. UserID int64 `xorm:"INDEX"` // Receiver user id.
  135. OpType ActionType
  136. ActUserID int64 // Action user id.
  137. ActUser *user_model.User `xorm:"-"`
  138. RepoID int64
  139. Repo *repo_model.Repository `xorm:"-"`
  140. CommentID int64 `xorm:"INDEX"`
  141. Comment *issues_model.Comment `xorm:"-"`
  142. IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
  143. RefName string
  144. IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
  145. Content string `xorm:"TEXT"`
  146. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  147. }
  148. func init() {
  149. db.RegisterModel(new(Action))
  150. }
  151. // TableIndices implements xorm's TableIndices interface
  152. func (a *Action) TableIndices() []*schemas.Index {
  153. repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
  154. repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
  155. actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
  156. actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
  157. cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
  158. cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
  159. indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex}
  160. return indices
  161. }
  162. // GetOpType gets the ActionType of this action.
  163. func (a *Action) GetOpType() ActionType {
  164. return a.OpType
  165. }
  166. // LoadActUser loads a.ActUser
  167. func (a *Action) LoadActUser(ctx context.Context) {
  168. if a.ActUser != nil {
  169. return
  170. }
  171. var err error
  172. a.ActUser, err = user_model.GetUserByID(ctx, a.ActUserID)
  173. if err == nil {
  174. return
  175. } else if user_model.IsErrUserNotExist(err) {
  176. a.ActUser = user_model.NewGhostUser()
  177. } else {
  178. log.Error("GetUserByID(%d): %v", a.ActUserID, err)
  179. }
  180. }
  181. func (a *Action) loadRepo(ctx context.Context) {
  182. if a.Repo != nil {
  183. return
  184. }
  185. var err error
  186. a.Repo, err = repo_model.GetRepositoryByID(ctx, a.RepoID)
  187. if err != nil {
  188. log.Error("repo_model.GetRepositoryByID(%d): %v", a.RepoID, err)
  189. }
  190. }
  191. // GetActFullName gets the action's user full name.
  192. func (a *Action) GetActFullName(ctx context.Context) string {
  193. a.LoadActUser(ctx)
  194. return a.ActUser.FullName
  195. }
  196. // GetActUserName gets the action's user name.
  197. func (a *Action) GetActUserName(ctx context.Context) string {
  198. a.LoadActUser(ctx)
  199. return a.ActUser.Name
  200. }
  201. // ShortActUserName gets the action's user name trimmed to max 20
  202. // chars.
  203. func (a *Action) ShortActUserName(ctx context.Context) string {
  204. return base.EllipsisString(a.GetActUserName(ctx), 20)
  205. }
  206. // GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
  207. func (a *Action) GetDisplayName(ctx context.Context) string {
  208. if setting.UI.DefaultShowFullName {
  209. trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
  210. if len(trimmedFullName) > 0 {
  211. return trimmedFullName
  212. }
  213. }
  214. return a.ShortActUserName(ctx)
  215. }
  216. // GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
  217. func (a *Action) GetDisplayNameTitle(ctx context.Context) string {
  218. if setting.UI.DefaultShowFullName {
  219. return a.ShortActUserName(ctx)
  220. }
  221. return a.GetActFullName(ctx)
  222. }
  223. // GetRepoUserName returns the name of the action repository owner.
  224. func (a *Action) GetRepoUserName(ctx context.Context) string {
  225. a.loadRepo(ctx)
  226. return a.Repo.OwnerName
  227. }
  228. // ShortRepoUserName returns the name of the action repository owner
  229. // trimmed to max 20 chars.
  230. func (a *Action) ShortRepoUserName(ctx context.Context) string {
  231. return base.EllipsisString(a.GetRepoUserName(ctx), 20)
  232. }
  233. // GetRepoName returns the name of the action repository.
  234. func (a *Action) GetRepoName(ctx context.Context) string {
  235. a.loadRepo(ctx)
  236. return a.Repo.Name
  237. }
  238. // ShortRepoName returns the name of the action repository
  239. // trimmed to max 33 chars.
  240. func (a *Action) ShortRepoName(ctx context.Context) string {
  241. return base.EllipsisString(a.GetRepoName(ctx), 33)
  242. }
  243. // GetRepoPath returns the virtual path to the action repository.
  244. func (a *Action) GetRepoPath(ctx context.Context) string {
  245. return path.Join(a.GetRepoUserName(ctx), a.GetRepoName(ctx))
  246. }
  247. // ShortRepoPath returns the virtual path to the action repository
  248. // trimmed to max 20 + 1 + 33 chars.
  249. func (a *Action) ShortRepoPath(ctx context.Context) string {
  250. return path.Join(a.ShortRepoUserName(ctx), a.ShortRepoName(ctx))
  251. }
  252. // GetRepoLink returns relative link to action repository.
  253. func (a *Action) GetRepoLink(ctx context.Context) string {
  254. // path.Join will skip empty strings
  255. return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName(ctx)), url.PathEscape(a.GetRepoName(ctx)))
  256. }
  257. // GetRepoAbsoluteLink returns the absolute link to action repository.
  258. func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string {
  259. return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx))
  260. }
  261. // GetCommentHTMLURL returns link to action comment.
  262. func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
  263. return a.getCommentHTMLURL(ctx)
  264. }
  265. func (a *Action) loadComment(ctx context.Context) (err error) {
  266. if a.CommentID == 0 || a.Comment != nil {
  267. return nil
  268. }
  269. a.Comment, err = issues_model.GetCommentByID(ctx, a.CommentID)
  270. return err
  271. }
  272. func (a *Action) getCommentHTMLURL(ctx context.Context) string {
  273. if a == nil {
  274. return "#"
  275. }
  276. _ = a.loadComment(ctx)
  277. if a.Comment != nil {
  278. return a.Comment.HTMLURL(ctx)
  279. }
  280. if len(a.GetIssueInfos()) == 0 {
  281. return "#"
  282. }
  283. // Return link to issue
  284. issueIDString := a.GetIssueInfos()[0]
  285. issueID, err := strconv.ParseInt(issueIDString, 10, 64)
  286. if err != nil {
  287. return "#"
  288. }
  289. issue, err := issues_model.GetIssueByID(ctx, issueID)
  290. if err != nil {
  291. return "#"
  292. }
  293. if err = issue.LoadRepo(ctx); err != nil {
  294. return "#"
  295. }
  296. return issue.HTMLURL()
  297. }
  298. // GetCommentLink returns link to action comment.
  299. func (a *Action) GetCommentLink(ctx context.Context) string {
  300. return a.getCommentLink(ctx)
  301. }
  302. func (a *Action) getCommentLink(ctx context.Context) string {
  303. if a == nil {
  304. return "#"
  305. }
  306. _ = a.loadComment(ctx)
  307. if a.Comment != nil {
  308. return a.Comment.Link(ctx)
  309. }
  310. if len(a.GetIssueInfos()) == 0 {
  311. return "#"
  312. }
  313. // Return link to issue
  314. issueIDString := a.GetIssueInfos()[0]
  315. issueID, err := strconv.ParseInt(issueIDString, 10, 64)
  316. if err != nil {
  317. return "#"
  318. }
  319. issue, err := issues_model.GetIssueByID(ctx, issueID)
  320. if err != nil {
  321. return "#"
  322. }
  323. if err = issue.LoadRepo(ctx); err != nil {
  324. return "#"
  325. }
  326. return issue.Link()
  327. }
  328. // GetBranch returns the action's repository branch.
  329. func (a *Action) GetBranch() string {
  330. return strings.TrimPrefix(a.RefName, git.BranchPrefix)
  331. }
  332. // GetRefLink returns the action's ref link.
  333. func (a *Action) GetRefLink(ctx context.Context) string {
  334. return git.RefURL(a.GetRepoLink(ctx), a.RefName)
  335. }
  336. // GetTag returns the action's repository tag.
  337. func (a *Action) GetTag() string {
  338. return strings.TrimPrefix(a.RefName, git.TagPrefix)
  339. }
  340. // GetContent returns the action's content.
  341. func (a *Action) GetContent() string {
  342. return a.Content
  343. }
  344. // GetCreate returns the action creation time.
  345. func (a *Action) GetCreate() time.Time {
  346. return a.CreatedUnix.AsTime()
  347. }
  348. // GetIssueInfos returns a list of associated information with the action.
  349. func (a *Action) GetIssueInfos() []string {
  350. // make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
  351. ret := strings.SplitN(a.Content, "|", 3)
  352. for len(ret) < 3 {
  353. ret = append(ret, "")
  354. }
  355. return ret
  356. }
  357. // GetIssueTitle returns the title of first issue associated with the action.
  358. func (a *Action) GetIssueTitle(ctx context.Context) string {
  359. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  360. issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
  361. if err != nil {
  362. log.Error("GetIssueByIndex: %v", err)
  363. return "500 when get issue"
  364. }
  365. return issue.Title
  366. }
  367. // GetIssueContent returns the content of first issue associated with
  368. // this action.
  369. func (a *Action) GetIssueContent(ctx context.Context) string {
  370. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  371. issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
  372. if err != nil {
  373. log.Error("GetIssueByIndex: %v", err)
  374. return "500 when get issue"
  375. }
  376. return issue.Content
  377. }
  378. // GetFeedsOptions options for retrieving feeds
  379. type GetFeedsOptions struct {
  380. db.ListOptions
  381. RequestedUser *user_model.User // the user we want activity for
  382. RequestedTeam *organization.Team // the team we want activity for
  383. RequestedRepo *repo_model.Repository // the repo we want activity for
  384. Actor *user_model.User // the user viewing the activity
  385. IncludePrivate bool // include private actions
  386. OnlyPerformedBy bool // only actions performed by requested user
  387. IncludeDeleted bool // include deleted actions
  388. Date string // the day we want activity for: YYYY-MM-DD
  389. }
  390. // GetFeeds returns actions according to the provided options
  391. func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) {
  392. if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
  393. return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
  394. }
  395. cond, err := activityQueryCondition(ctx, opts)
  396. if err != nil {
  397. return nil, 0, err
  398. }
  399. sess := db.GetEngine(ctx).Where(cond).
  400. Select("`action`.*"). // this line will avoid select other joined table's columns
  401. Join("INNER", "repository", "`repository`.id = `action`.repo_id")
  402. opts.SetDefaultValues()
  403. sess = db.SetSessionPagination(sess, &opts)
  404. actions := make([]*Action, 0, opts.PageSize)
  405. count, err := sess.Desc("`action`.created_unix").FindAndCount(&actions)
  406. if err != nil {
  407. return nil, 0, fmt.Errorf("FindAndCount: %w", err)
  408. }
  409. if err := ActionList(actions).loadAttributes(ctx); err != nil {
  410. return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
  411. }
  412. return actions, count, nil
  413. }
  414. // ActivityReadable return whether doer can read activities of user
  415. func ActivityReadable(user, doer *user_model.User) bool {
  416. return !user.KeepActivityPrivate ||
  417. doer != nil && (doer.IsAdmin || user.ID == doer.ID)
  418. }
  419. func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
  420. cond := builder.NewCond()
  421. if opts.RequestedTeam != nil && opts.RequestedUser == nil {
  422. org, err := user_model.GetUserByID(ctx, opts.RequestedTeam.OrgID)
  423. if err != nil {
  424. return nil, err
  425. }
  426. opts.RequestedUser = org
  427. }
  428. // check activity visibility for actor ( similar to activityReadable() )
  429. if opts.Actor == nil {
  430. cond = cond.And(builder.In("act_user_id",
  431. builder.Select("`user`.id").Where(
  432. builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
  433. ).From("`user`"),
  434. ))
  435. } else if !opts.Actor.IsAdmin {
  436. uidCond := builder.Select("`user`.id").From("`user`").Where(
  437. builder.Eq{"keep_activity_private": false}.
  438. And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
  439. Or(builder.Eq{"id": opts.Actor.ID})
  440. if opts.RequestedUser != nil {
  441. if opts.RequestedUser.IsOrganization() {
  442. // An organization can always see the activities whose `act_user_id` is the same as its id.
  443. uidCond = uidCond.Or(builder.Eq{"id": opts.RequestedUser.ID})
  444. } else {
  445. // A user can always see the activities of the organizations to which the user belongs.
  446. uidCond = uidCond.Or(
  447. builder.Eq{"type": user_model.UserTypeOrganization}.
  448. And(builder.In("`user`.id", builder.Select("org_id").
  449. Where(builder.Eq{"uid": opts.RequestedUser.ID}).
  450. From("team_user"))),
  451. )
  452. }
  453. }
  454. cond = cond.And(builder.In("act_user_id", uidCond))
  455. }
  456. // check readable repositories by doer/actor
  457. if opts.Actor == nil || !opts.Actor.IsAdmin {
  458. cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor)))
  459. }
  460. if opts.RequestedRepo != nil {
  461. cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
  462. }
  463. if opts.RequestedTeam != nil {
  464. env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam)
  465. teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
  466. if err != nil {
  467. return nil, fmt.Errorf("GetTeamRepositories: %w", err)
  468. }
  469. cond = cond.And(builder.In("repo_id", teamRepoIDs))
  470. }
  471. if opts.RequestedUser != nil {
  472. cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
  473. if opts.OnlyPerformedBy {
  474. cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
  475. }
  476. }
  477. if !opts.IncludePrivate {
  478. cond = cond.And(builder.Eq{"`action`.is_private": false})
  479. }
  480. if !opts.IncludeDeleted {
  481. cond = cond.And(builder.Eq{"is_deleted": false})
  482. }
  483. if opts.Date != "" {
  484. dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
  485. if err != nil {
  486. log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
  487. } else {
  488. dateHigh := dateLow.Add(86399000000000) // 23h59m59s
  489. cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
  490. cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
  491. }
  492. }
  493. return cond, nil
  494. }
  495. // DeleteOldActions deletes all old actions from database.
  496. func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) {
  497. if olderThan <= 0 {
  498. return nil
  499. }
  500. _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{})
  501. return err
  502. }
  503. // NotifyWatchers creates batch of actions for every watcher.
  504. func NotifyWatchers(ctx context.Context, actions ...*Action) error {
  505. var watchers []*repo_model.Watch
  506. var repo *repo_model.Repository
  507. var err error
  508. var permCode []bool
  509. var permIssue []bool
  510. var permPR []bool
  511. e := db.GetEngine(ctx)
  512. for _, act := range actions {
  513. repoChanged := repo == nil || repo.ID != act.RepoID
  514. if repoChanged {
  515. // Add feeds for user self and all watchers.
  516. watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
  517. if err != nil {
  518. return fmt.Errorf("get watchers: %w", err)
  519. }
  520. }
  521. // Add feed for actioner.
  522. act.UserID = act.ActUserID
  523. if _, err = e.Insert(act); err != nil {
  524. return fmt.Errorf("insert new actioner: %w", err)
  525. }
  526. if repoChanged {
  527. act.loadRepo(ctx)
  528. repo = act.Repo
  529. // check repo owner exist.
  530. if err := act.Repo.LoadOwner(ctx); err != nil {
  531. return fmt.Errorf("can't get repo owner: %w", err)
  532. }
  533. } else if act.Repo == nil {
  534. act.Repo = repo
  535. }
  536. // Add feed for organization
  537. if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID {
  538. act.ID = 0
  539. act.UserID = act.Repo.Owner.ID
  540. if err = db.Insert(ctx, act); err != nil {
  541. return fmt.Errorf("insert new actioner: %w", err)
  542. }
  543. }
  544. if repoChanged {
  545. permCode = make([]bool, len(watchers))
  546. permIssue = make([]bool, len(watchers))
  547. permPR = make([]bool, len(watchers))
  548. for i, watcher := range watchers {
  549. user, err := user_model.GetUserByID(ctx, watcher.UserID)
  550. if err != nil {
  551. permCode[i] = false
  552. permIssue[i] = false
  553. permPR[i] = false
  554. continue
  555. }
  556. perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
  557. if err != nil {
  558. permCode[i] = false
  559. permIssue[i] = false
  560. permPR[i] = false
  561. continue
  562. }
  563. permCode[i] = perm.CanRead(unit.TypeCode)
  564. permIssue[i] = perm.CanRead(unit.TypeIssues)
  565. permPR[i] = perm.CanRead(unit.TypePullRequests)
  566. }
  567. }
  568. for i, watcher := range watchers {
  569. if act.ActUserID == watcher.UserID {
  570. continue
  571. }
  572. act.ID = 0
  573. act.UserID = watcher.UserID
  574. act.Repo.Units = nil
  575. switch act.OpType {
  576. case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch:
  577. if !permCode[i] {
  578. continue
  579. }
  580. case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
  581. if !permIssue[i] {
  582. continue
  583. }
  584. case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest, ActionAutoMergePullRequest:
  585. if !permPR[i] {
  586. continue
  587. }
  588. }
  589. if err = db.Insert(ctx, act); err != nil {
  590. return fmt.Errorf("insert new action: %w", err)
  591. }
  592. }
  593. }
  594. return nil
  595. }
  596. // NotifyWatchersActions creates batch of actions for every watcher.
  597. func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
  598. ctx, committer, err := db.TxContext(ctx)
  599. if err != nil {
  600. return err
  601. }
  602. defer committer.Close()
  603. for _, act := range acts {
  604. if err := NotifyWatchers(ctx, act); err != nil {
  605. return err
  606. }
  607. }
  608. return committer.Commit()
  609. }
  610. // DeleteIssueActions delete all actions related with issueID
  611. func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error {
  612. // delete actions assigned to this issue
  613. e := db.GetEngine(ctx)
  614. // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
  615. // so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
  616. var lastCommentID int64
  617. commentIDs := make([]int64, 0, db.DefaultMaxInSize)
  618. for {
  619. commentIDs = commentIDs[:0]
  620. err := e.Select("`id`").Table(&issues_model.Comment{}).
  621. Where(builder.Eq{"issue_id": issueID}).And("`id` > ?", lastCommentID).
  622. OrderBy("`id`").Limit(db.DefaultMaxInSize).
  623. Find(&commentIDs)
  624. if err != nil {
  625. return err
  626. } else if len(commentIDs) == 0 {
  627. break
  628. } else if _, err = db.GetEngine(ctx).In("comment_id", commentIDs).Delete(&Action{}); err != nil {
  629. return err
  630. } else {
  631. lastCommentID = commentIDs[len(commentIDs)-1]
  632. }
  633. }
  634. _, err := e.Where("repo_id = ?", repoID).
  635. In("op_type", ActionCreateIssue, ActionCreatePullRequest).
  636. Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
  637. Delete(&Action{})
  638. return err
  639. }
  640. // CountActionCreatedUnixString count actions where created_unix is an empty string
  641. func CountActionCreatedUnixString(ctx context.Context) (int64, error) {
  642. if setting.Database.Type.IsSQLite3() {
  643. return db.GetEngine(ctx).Where(`created_unix = ""`).Count(new(Action))
  644. }
  645. return 0, nil
  646. }
  647. // FixActionCreatedUnixString set created_unix to zero if it is an empty string
  648. func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
  649. if setting.Database.Type.IsSQLite3() {
  650. res, err := db.GetEngine(ctx).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
  651. if err != nil {
  652. return 0, err
  653. }
  654. return res.RowsAffected()
  655. }
  656. return 0, nil
  657. }