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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  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 issues associated with
  349. // the action.
  350. func (a *Action) GetIssueInfos() []string {
  351. return strings.SplitN(a.Content, "|", 3)
  352. }
  353. // GetIssueTitle returns the title of first issue associated with the action.
  354. func (a *Action) GetIssueTitle(ctx context.Context) string {
  355. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  356. issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
  357. if err != nil {
  358. log.Error("GetIssueByIndex: %v", err)
  359. return "500 when get issue"
  360. }
  361. return issue.Title
  362. }
  363. // GetIssueContent returns the content of first issue associated with
  364. // this action.
  365. func (a *Action) GetIssueContent(ctx context.Context) string {
  366. index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
  367. issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
  368. if err != nil {
  369. log.Error("GetIssueByIndex: %v", err)
  370. return "500 when get issue"
  371. }
  372. return issue.Content
  373. }
  374. // GetFeedsOptions options for retrieving feeds
  375. type GetFeedsOptions struct {
  376. db.ListOptions
  377. RequestedUser *user_model.User // the user we want activity for
  378. RequestedTeam *organization.Team // the team we want activity for
  379. RequestedRepo *repo_model.Repository // the repo we want activity for
  380. Actor *user_model.User // the user viewing the activity
  381. IncludePrivate bool // include private actions
  382. OnlyPerformedBy bool // only actions performed by requested user
  383. IncludeDeleted bool // include deleted actions
  384. Date string // the day we want activity for: YYYY-MM-DD
  385. }
  386. // GetFeeds returns actions according to the provided options
  387. func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) {
  388. if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil {
  389. return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
  390. }
  391. cond, err := activityQueryCondition(ctx, opts)
  392. if err != nil {
  393. return nil, 0, err
  394. }
  395. sess := db.GetEngine(ctx).Where(cond).
  396. Select("`action`.*"). // this line will avoid select other joined table's columns
  397. Join("INNER", "repository", "`repository`.id = `action`.repo_id")
  398. opts.SetDefaultValues()
  399. sess = db.SetSessionPagination(sess, &opts)
  400. actions := make([]*Action, 0, opts.PageSize)
  401. count, err := sess.Desc("`action`.created_unix").FindAndCount(&actions)
  402. if err != nil {
  403. return nil, 0, fmt.Errorf("FindAndCount: %w", err)
  404. }
  405. if err := ActionList(actions).loadAttributes(ctx); err != nil {
  406. return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
  407. }
  408. return actions, count, nil
  409. }
  410. // ActivityReadable return whether doer can read activities of user
  411. func ActivityReadable(user, doer *user_model.User) bool {
  412. return !user.KeepActivityPrivate ||
  413. doer != nil && (doer.IsAdmin || user.ID == doer.ID)
  414. }
  415. func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
  416. cond := builder.NewCond()
  417. if opts.RequestedTeam != nil && opts.RequestedUser == nil {
  418. org, err := user_model.GetUserByID(ctx, opts.RequestedTeam.OrgID)
  419. if err != nil {
  420. return nil, err
  421. }
  422. opts.RequestedUser = org
  423. }
  424. // check activity visibility for actor ( similar to activityReadable() )
  425. if opts.Actor == nil {
  426. cond = cond.And(builder.In("act_user_id",
  427. builder.Select("`user`.id").Where(
  428. builder.Eq{"keep_activity_private": false, "visibility": structs.VisibleTypePublic},
  429. ).From("`user`"),
  430. ))
  431. } else if !opts.Actor.IsAdmin {
  432. uidCond := builder.Select("`user`.id").From("`user`").Where(
  433. builder.Eq{"keep_activity_private": false}.
  434. And(builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))).
  435. Or(builder.Eq{"id": opts.Actor.ID})
  436. if opts.RequestedUser != nil {
  437. if opts.RequestedUser.IsOrganization() {
  438. // An organization can always see the activities whose `act_user_id` is the same as its id.
  439. uidCond = uidCond.Or(builder.Eq{"id": opts.RequestedUser.ID})
  440. } else {
  441. // A user can always see the activities of the organizations to which the user belongs.
  442. uidCond = uidCond.Or(
  443. builder.Eq{"type": user_model.UserTypeOrganization}.
  444. And(builder.In("`user`.id", builder.Select("org_id").
  445. Where(builder.Eq{"uid": opts.RequestedUser.ID}).
  446. From("team_user"))),
  447. )
  448. }
  449. }
  450. cond = cond.And(builder.In("act_user_id", uidCond))
  451. }
  452. // check readable repositories by doer/actor
  453. if opts.Actor == nil || !opts.Actor.IsAdmin {
  454. cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor)))
  455. }
  456. if opts.RequestedRepo != nil {
  457. cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
  458. }
  459. if opts.RequestedTeam != nil {
  460. env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam)
  461. teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
  462. if err != nil {
  463. return nil, fmt.Errorf("GetTeamRepositories: %w", err)
  464. }
  465. cond = cond.And(builder.In("repo_id", teamRepoIDs))
  466. }
  467. if opts.RequestedUser != nil {
  468. cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
  469. if opts.OnlyPerformedBy {
  470. cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
  471. }
  472. }
  473. if !opts.IncludePrivate {
  474. cond = cond.And(builder.Eq{"`action`.is_private": false})
  475. }
  476. if !opts.IncludeDeleted {
  477. cond = cond.And(builder.Eq{"is_deleted": false})
  478. }
  479. if opts.Date != "" {
  480. dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
  481. if err != nil {
  482. log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
  483. } else {
  484. dateHigh := dateLow.Add(86399000000000) // 23h59m59s
  485. cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
  486. cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
  487. }
  488. }
  489. return cond, nil
  490. }
  491. // DeleteOldActions deletes all old actions from database.
  492. func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) {
  493. if olderThan <= 0 {
  494. return nil
  495. }
  496. _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{})
  497. return err
  498. }
  499. // NotifyWatchers creates batch of actions for every watcher.
  500. func NotifyWatchers(ctx context.Context, actions ...*Action) error {
  501. var watchers []*repo_model.Watch
  502. var repo *repo_model.Repository
  503. var err error
  504. var permCode []bool
  505. var permIssue []bool
  506. var permPR []bool
  507. e := db.GetEngine(ctx)
  508. for _, act := range actions {
  509. repoChanged := repo == nil || repo.ID != act.RepoID
  510. if repoChanged {
  511. // Add feeds for user self and all watchers.
  512. watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
  513. if err != nil {
  514. return fmt.Errorf("get watchers: %w", err)
  515. }
  516. }
  517. // Add feed for actioner.
  518. act.UserID = act.ActUserID
  519. if _, err = e.Insert(act); err != nil {
  520. return fmt.Errorf("insert new actioner: %w", err)
  521. }
  522. if repoChanged {
  523. act.loadRepo(ctx)
  524. repo = act.Repo
  525. // check repo owner exist.
  526. if err := act.Repo.LoadOwner(ctx); err != nil {
  527. return fmt.Errorf("can't get repo owner: %w", err)
  528. }
  529. } else if act.Repo == nil {
  530. act.Repo = repo
  531. }
  532. // Add feed for organization
  533. if act.Repo.Owner.IsOrganization() && act.ActUserID != act.Repo.Owner.ID {
  534. act.ID = 0
  535. act.UserID = act.Repo.Owner.ID
  536. if err = db.Insert(ctx, act); err != nil {
  537. return fmt.Errorf("insert new actioner: %w", err)
  538. }
  539. }
  540. if repoChanged {
  541. permCode = make([]bool, len(watchers))
  542. permIssue = make([]bool, len(watchers))
  543. permPR = make([]bool, len(watchers))
  544. for i, watcher := range watchers {
  545. user, err := user_model.GetUserByID(ctx, watcher.UserID)
  546. if err != nil {
  547. permCode[i] = false
  548. permIssue[i] = false
  549. permPR[i] = false
  550. continue
  551. }
  552. perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
  553. if err != nil {
  554. permCode[i] = false
  555. permIssue[i] = false
  556. permPR[i] = false
  557. continue
  558. }
  559. permCode[i] = perm.CanRead(unit.TypeCode)
  560. permIssue[i] = perm.CanRead(unit.TypeIssues)
  561. permPR[i] = perm.CanRead(unit.TypePullRequests)
  562. }
  563. }
  564. for i, watcher := range watchers {
  565. if act.ActUserID == watcher.UserID {
  566. continue
  567. }
  568. act.ID = 0
  569. act.UserID = watcher.UserID
  570. act.Repo.Units = nil
  571. switch act.OpType {
  572. case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionPublishRelease, ActionDeleteBranch:
  573. if !permCode[i] {
  574. continue
  575. }
  576. case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
  577. if !permIssue[i] {
  578. continue
  579. }
  580. case ActionCreatePullRequest, ActionCommentPull, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest, ActionAutoMergePullRequest:
  581. if !permPR[i] {
  582. continue
  583. }
  584. }
  585. if err = db.Insert(ctx, act); err != nil {
  586. return fmt.Errorf("insert new action: %w", err)
  587. }
  588. }
  589. }
  590. return nil
  591. }
  592. // NotifyWatchersActions creates batch of actions for every watcher.
  593. func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
  594. ctx, committer, err := db.TxContext(ctx)
  595. if err != nil {
  596. return err
  597. }
  598. defer committer.Close()
  599. for _, act := range acts {
  600. if err := NotifyWatchers(ctx, act); err != nil {
  601. return err
  602. }
  603. }
  604. return committer.Commit()
  605. }
  606. // DeleteIssueActions delete all actions related with issueID
  607. func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) error {
  608. // delete actions assigned to this issue
  609. e := db.GetEngine(ctx)
  610. // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
  611. // so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
  612. var lastCommentID int64
  613. commentIDs := make([]int64, 0, db.DefaultMaxInSize)
  614. for {
  615. commentIDs = commentIDs[:0]
  616. err := e.Select("`id`").Table(&issues_model.Comment{}).
  617. Where(builder.Eq{"issue_id": issueID}).And("`id` > ?", lastCommentID).
  618. OrderBy("`id`").Limit(db.DefaultMaxInSize).
  619. Find(&commentIDs)
  620. if err != nil {
  621. return err
  622. } else if len(commentIDs) == 0 {
  623. break
  624. } else if _, err = db.GetEngine(ctx).In("comment_id", commentIDs).Delete(&Action{}); err != nil {
  625. return err
  626. } else {
  627. lastCommentID = commentIDs[len(commentIDs)-1]
  628. }
  629. }
  630. _, err := e.Where("repo_id = ?", repoID).
  631. In("op_type", ActionCreateIssue, ActionCreatePullRequest).
  632. Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..."
  633. Delete(&Action{})
  634. return err
  635. }
  636. // CountActionCreatedUnixString count actions where created_unix is an empty string
  637. func CountActionCreatedUnixString(ctx context.Context) (int64, error) {
  638. if setting.Database.Type.IsSQLite3() {
  639. return db.GetEngine(ctx).Where(`created_unix = ""`).Count(new(Action))
  640. }
  641. return 0, nil
  642. }
  643. // FixActionCreatedUnixString set created_unix to zero if it is an empty string
  644. func FixActionCreatedUnixString(ctx context.Context) (int64, error) {
  645. if setting.Database.Type.IsSQLite3() {
  646. res, err := db.GetEngine(ctx).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
  647. if err != nil {
  648. return 0, err
  649. }
  650. return res.RowsAffected()
  651. }
  652. return 0, nil
  653. }