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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package webhook
  5. import (
  6. "code.gitea.io/gitea/models"
  7. "code.gitea.io/gitea/modules/log"
  8. "code.gitea.io/gitea/modules/notification/base"
  9. api "code.gitea.io/gitea/modules/structs"
  10. webhook_module "code.gitea.io/gitea/modules/webhook"
  11. )
  12. type webhookNotifier struct {
  13. base.NullNotifier
  14. }
  15. var (
  16. _ base.Notifier = &webhookNotifier{}
  17. )
  18. // NewNotifier create a new webhookNotifier notifier
  19. func NewNotifier() base.Notifier {
  20. return &webhookNotifier{}
  21. }
  22. func (m *webhookNotifier) NotifyIssueClearLabels(doer *models.User, issue *models.Issue) {
  23. if err := issue.LoadPoster(); err != nil {
  24. log.Error("loadPoster: %v", err)
  25. return
  26. }
  27. if err := issue.LoadRepo(); err != nil {
  28. log.Error("LoadRepo: %v", err)
  29. return
  30. }
  31. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  32. var err error
  33. if issue.IsPull {
  34. if err = issue.LoadPullRequest(); err != nil {
  35. log.Error("LoadPullRequest: %v", err)
  36. return
  37. }
  38. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  39. Action: api.HookIssueLabelCleared,
  40. Index: issue.Index,
  41. PullRequest: issue.PullRequest.APIFormat(),
  42. Repository: issue.Repo.APIFormat(mode),
  43. Sender: doer.APIFormat(),
  44. })
  45. } else {
  46. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  47. Action: api.HookIssueLabelCleared,
  48. Index: issue.Index,
  49. Issue: issue.APIFormat(),
  50. Repository: issue.Repo.APIFormat(mode),
  51. Sender: doer.APIFormat(),
  52. })
  53. }
  54. if err != nil {
  55. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  56. }
  57. }
  58. func (m *webhookNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository) {
  59. oldMode, _ := models.AccessLevel(doer, oldRepo)
  60. mode, _ := models.AccessLevel(doer, repo)
  61. // forked webhook
  62. if err := webhook_module.PrepareWebhooks(oldRepo, models.HookEventFork, &api.ForkPayload{
  63. Forkee: oldRepo.APIFormat(oldMode),
  64. Repo: repo.APIFormat(mode),
  65. Sender: doer.APIFormat(),
  66. }); err != nil {
  67. log.Error("PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err)
  68. }
  69. u := repo.MustOwner()
  70. // Add to hook queue for created repo after session commit.
  71. if u.IsOrganization() {
  72. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  73. Action: api.HookRepoCreated,
  74. Repository: repo.APIFormat(models.AccessModeOwner),
  75. Organization: u.APIFormat(),
  76. Sender: doer.APIFormat(),
  77. }); err != nil {
  78. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  79. }
  80. }
  81. }
  82. func (m *webhookNotifier) NotifyCreateRepository(doer *models.User, u *models.User, repo *models.Repository) {
  83. // Add to hook queue for created repo after session commit.
  84. if u.IsOrganization() {
  85. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  86. Action: api.HookRepoCreated,
  87. Repository: repo.APIFormat(models.AccessModeOwner),
  88. Organization: u.APIFormat(),
  89. Sender: doer.APIFormat(),
  90. }); err != nil {
  91. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  92. }
  93. }
  94. }
  95. func (m *webhookNotifier) NotifyDeleteRepository(doer *models.User, repo *models.Repository) {
  96. u := repo.MustOwner()
  97. if u.IsOrganization() {
  98. if err := webhook_module.PrepareWebhooks(repo, models.HookEventRepository, &api.RepositoryPayload{
  99. Action: api.HookRepoDeleted,
  100. Repository: repo.APIFormat(models.AccessModeOwner),
  101. Organization: u.APIFormat(),
  102. Sender: doer.APIFormat(),
  103. }); err != nil {
  104. log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
  105. }
  106. }
  107. }
  108. func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) {
  109. if issue.IsPull {
  110. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypePullRequests)
  111. if err := issue.LoadPullRequest(); err != nil {
  112. log.Error("LoadPullRequest failed: %v", err)
  113. return
  114. }
  115. issue.PullRequest.Issue = issue
  116. apiPullRequest := &api.PullRequestPayload{
  117. Index: issue.Index,
  118. PullRequest: issue.PullRequest.APIFormat(),
  119. Repository: issue.Repo.APIFormat(mode),
  120. Sender: doer.APIFormat(),
  121. }
  122. if removed {
  123. apiPullRequest.Action = api.HookIssueUnassigned
  124. } else {
  125. apiPullRequest.Action = api.HookIssueAssigned
  126. }
  127. // Assignee comment triggers a webhook
  128. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest); err != nil {
  129. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  130. return
  131. }
  132. } else {
  133. mode, _ := models.AccessLevelUnit(doer, issue.Repo, models.UnitTypeIssues)
  134. apiIssue := &api.IssuePayload{
  135. Index: issue.Index,
  136. Issue: issue.APIFormat(),
  137. Repository: issue.Repo.APIFormat(mode),
  138. Sender: doer.APIFormat(),
  139. }
  140. if removed {
  141. apiIssue.Action = api.HookIssueUnassigned
  142. } else {
  143. apiIssue.Action = api.HookIssueAssigned
  144. }
  145. // Assignee comment triggers a webhook
  146. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue); err != nil {
  147. log.Error("PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
  148. return
  149. }
  150. }
  151. }
  152. func (m *webhookNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
  153. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  154. var err error
  155. if issue.IsPull {
  156. if err = issue.LoadPullRequest(); err != nil {
  157. log.Error("LoadPullRequest failed: %v", err)
  158. return
  159. }
  160. issue.PullRequest.Issue = issue
  161. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  162. Action: api.HookIssueEdited,
  163. Index: issue.Index,
  164. Changes: &api.ChangesPayload{
  165. Title: &api.ChangesFromPayload{
  166. From: oldTitle,
  167. },
  168. },
  169. PullRequest: issue.PullRequest.APIFormat(),
  170. Repository: issue.Repo.APIFormat(mode),
  171. Sender: doer.APIFormat(),
  172. })
  173. } else {
  174. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  175. Action: api.HookIssueEdited,
  176. Index: issue.Index,
  177. Changes: &api.ChangesPayload{
  178. Title: &api.ChangesFromPayload{
  179. From: oldTitle,
  180. },
  181. },
  182. Issue: issue.APIFormat(),
  183. Repository: issue.Repo.APIFormat(mode),
  184. Sender: issue.Poster.APIFormat(),
  185. })
  186. }
  187. if err != nil {
  188. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  189. }
  190. }
  191. func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, isClosed bool) {
  192. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  193. var err error
  194. if issue.IsPull {
  195. if err = issue.LoadPullRequest(); err != nil {
  196. log.Error("LoadPullRequest: %v", err)
  197. return
  198. }
  199. // Merge pull request calls issue.changeStatus so we need to handle separately.
  200. apiPullRequest := &api.PullRequestPayload{
  201. Index: issue.Index,
  202. PullRequest: issue.PullRequest.APIFormat(),
  203. Repository: issue.Repo.APIFormat(mode),
  204. Sender: doer.APIFormat(),
  205. }
  206. if isClosed {
  207. apiPullRequest.Action = api.HookIssueClosed
  208. } else {
  209. apiPullRequest.Action = api.HookIssueReOpened
  210. }
  211. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, apiPullRequest)
  212. } else {
  213. apiIssue := &api.IssuePayload{
  214. Index: issue.Index,
  215. Issue: issue.APIFormat(),
  216. Repository: issue.Repo.APIFormat(mode),
  217. Sender: doer.APIFormat(),
  218. }
  219. if isClosed {
  220. apiIssue.Action = api.HookIssueClosed
  221. } else {
  222. apiIssue.Action = api.HookIssueReOpened
  223. }
  224. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, apiIssue)
  225. }
  226. if err != nil {
  227. log.Error("PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
  228. }
  229. }
  230. func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
  231. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  232. if err := webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  233. Action: api.HookIssueOpened,
  234. Index: issue.Index,
  235. Issue: issue.APIFormat(),
  236. Repository: issue.Repo.APIFormat(mode),
  237. Sender: issue.Poster.APIFormat(),
  238. }); err != nil {
  239. log.Error("PrepareWebhooks: %v", err)
  240. }
  241. }
  242. func (m *webhookNotifier) NotifyIssueChangeContent(doer *models.User, issue *models.Issue, oldContent string) {
  243. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  244. var err error
  245. if issue.IsPull {
  246. issue.PullRequest.Issue = issue
  247. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  248. Action: api.HookIssueEdited,
  249. Index: issue.Index,
  250. Changes: &api.ChangesPayload{
  251. Body: &api.ChangesFromPayload{
  252. From: oldContent,
  253. },
  254. },
  255. PullRequest: issue.PullRequest.APIFormat(),
  256. Repository: issue.Repo.APIFormat(mode),
  257. Sender: doer.APIFormat(),
  258. })
  259. } else {
  260. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  261. Action: api.HookIssueEdited,
  262. Index: issue.Index,
  263. Changes: &api.ChangesPayload{
  264. Body: &api.ChangesFromPayload{
  265. From: oldContent,
  266. },
  267. },
  268. Issue: issue.APIFormat(),
  269. Repository: issue.Repo.APIFormat(mode),
  270. Sender: doer.APIFormat(),
  271. })
  272. }
  273. if err != nil {
  274. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  275. }
  276. }
  277. func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) {
  278. if err := c.LoadPoster(); err != nil {
  279. log.Error("LoadPoster: %v", err)
  280. return
  281. }
  282. if err := c.LoadIssue(); err != nil {
  283. log.Error("LoadIssue: %v", err)
  284. return
  285. }
  286. if err := c.Issue.LoadAttributes(); err != nil {
  287. log.Error("LoadAttributes: %v", err)
  288. return
  289. }
  290. mode, _ := models.AccessLevel(doer, c.Issue.Repo)
  291. if err := webhook_module.PrepareWebhooks(c.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  292. Action: api.HookIssueCommentEdited,
  293. Issue: c.Issue.APIFormat(),
  294. Comment: c.APIFormat(),
  295. Changes: &api.ChangesPayload{
  296. Body: &api.ChangesFromPayload{
  297. From: oldContent,
  298. },
  299. },
  300. Repository: c.Issue.Repo.APIFormat(mode),
  301. Sender: doer.APIFormat(),
  302. IsPull: c.Issue.IsPull,
  303. }); err != nil {
  304. log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
  305. }
  306. }
  307. func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
  308. issue *models.Issue, comment *models.Comment) {
  309. mode, _ := models.AccessLevel(doer, repo)
  310. if err := webhook_module.PrepareWebhooks(repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  311. Action: api.HookIssueCommentCreated,
  312. Issue: issue.APIFormat(),
  313. Comment: comment.APIFormat(),
  314. Repository: repo.APIFormat(mode),
  315. Sender: doer.APIFormat(),
  316. IsPull: issue.IsPull,
  317. }); err != nil {
  318. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  319. }
  320. }
  321. func (m *webhookNotifier) NotifyDeleteComment(doer *models.User, comment *models.Comment) {
  322. if err := comment.LoadPoster(); err != nil {
  323. log.Error("LoadPoster: %v", err)
  324. return
  325. }
  326. if err := comment.LoadIssue(); err != nil {
  327. log.Error("LoadIssue: %v", err)
  328. return
  329. }
  330. if err := comment.Issue.LoadAttributes(); err != nil {
  331. log.Error("LoadAttributes: %v", err)
  332. return
  333. }
  334. mode, _ := models.AccessLevel(doer, comment.Issue.Repo)
  335. if err := webhook_module.PrepareWebhooks(comment.Issue.Repo, models.HookEventIssueComment, &api.IssueCommentPayload{
  336. Action: api.HookIssueCommentDeleted,
  337. Issue: comment.Issue.APIFormat(),
  338. Comment: comment.APIFormat(),
  339. Repository: comment.Issue.Repo.APIFormat(mode),
  340. Sender: doer.APIFormat(),
  341. IsPull: comment.Issue.IsPull,
  342. }); err != nil {
  343. log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
  344. }
  345. }
  346. func (m *webhookNotifier) NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
  347. addedLabels []*models.Label, removedLabels []*models.Label) {
  348. var err error
  349. if err = issue.LoadRepo(); err != nil {
  350. log.Error("LoadRepo: %v", err)
  351. return
  352. }
  353. if err = issue.LoadPoster(); err != nil {
  354. log.Error("LoadPoster: %v", err)
  355. return
  356. }
  357. mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
  358. if issue.IsPull {
  359. if err = issue.LoadPullRequest(); err != nil {
  360. log.Error("loadPullRequest: %v", err)
  361. return
  362. }
  363. if err = issue.PullRequest.LoadIssue(); err != nil {
  364. log.Error("LoadIssue: %v", err)
  365. return
  366. }
  367. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  368. Action: api.HookIssueLabelUpdated,
  369. Index: issue.Index,
  370. PullRequest: issue.PullRequest.APIFormat(),
  371. Repository: issue.Repo.APIFormat(models.AccessModeNone),
  372. Sender: doer.APIFormat(),
  373. })
  374. } else {
  375. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  376. Action: api.HookIssueLabelUpdated,
  377. Index: issue.Index,
  378. Issue: issue.APIFormat(),
  379. Repository: issue.Repo.APIFormat(mode),
  380. Sender: doer.APIFormat(),
  381. })
  382. }
  383. if err != nil {
  384. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  385. }
  386. }
  387. func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *models.User, issue *models.Issue, oldMilestoneID int64) {
  388. var hookAction api.HookIssueAction
  389. var err error
  390. if issue.MilestoneID > 0 {
  391. hookAction = api.HookIssueMilestoned
  392. } else {
  393. hookAction = api.HookIssueDemilestoned
  394. }
  395. if err = issue.LoadAttributes(); err != nil {
  396. log.Error("issue.LoadAttributes failed: %v", err)
  397. return
  398. }
  399. mode, _ := models.AccessLevel(doer, issue.Repo)
  400. if issue.IsPull {
  401. err = issue.PullRequest.LoadIssue()
  402. if err != nil {
  403. log.Error("LoadIssue: %v", err)
  404. return
  405. }
  406. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
  407. Action: hookAction,
  408. Index: issue.Index,
  409. PullRequest: issue.PullRequest.APIFormat(),
  410. Repository: issue.Repo.APIFormat(mode),
  411. Sender: doer.APIFormat(),
  412. })
  413. } else {
  414. err = webhook_module.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{
  415. Action: hookAction,
  416. Index: issue.Index,
  417. Issue: issue.APIFormat(),
  418. Repository: issue.Repo.APIFormat(mode),
  419. Sender: doer.APIFormat(),
  420. })
  421. }
  422. if err != nil {
  423. log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
  424. }
  425. }