notification.py 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559
  1. """
  2. ForgeFed plugin for Pagure.
  3. Copyright (C) 2020-2021 zPlus <zplus@peers.community>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with this program; if not, see <https://www.gnu.org/licenses/>.
  14. SPDX-FileCopyrightText: 2020-2021 zPlus <zplus@peers.community>
  15. SPDX-License-Identifier: GPL-2.0-only
  16. """
  17. import celery
  18. import json
  19. import os
  20. import pagure
  21. from .. import APP_URL
  22. from .. import activitypub
  23. from .. import feeds
  24. from .. import model
  25. from .. import settings
  26. from . import broker
  27. from . import broker_url
  28. from . import database_session
  29. log = celery.utils.log.get_task_logger(__name__)
  30. log.setLevel(settings.LOG_LEVEL)
  31. # The following is a decorator that accepts a Pagure notification ID as input,
  32. # for example "issue.new", and adds it to _USER_ACTIONS_ together with the
  33. # decorated function. _USER_ACTIONS_ is a dictionary mapping Pagure notification
  34. # IDs to a function to be executed when a new notification is received.
  35. _USER_ACTIONS_ = {}
  36. def action(notification_id):
  37. def closure(func):
  38. def decorator(*args, **kwargs):
  39. func(*args, **kwargs)
  40. global _USER_ACTIONS_
  41. _USER_ACTIONS_[notification_id] = decorator
  42. return decorator
  43. return closure
  44. @broker.task
  45. def handle_pagure_signal(notification_id, message, forgefed_worker):
  46. """
  47. This task receives notifications from Pagure about events that happen on
  48. the instance, creates a new activity, and schedules their delivery.
  49. :param forgefed_worker: The value of envvar FORGEFED_WORKER of the process
  50. that scheduled the task.
  51. """
  52. if forgefed_worker.upper() == 'TRUE':
  53. log.debug('Ignoring notification {} in ForgeFed worker thread.'.format(notification_id))
  54. return
  55. if notification_id not in _USER_ACTIONS_:
  56. log.debug('Unhandled user action {}.'.format(notification_id))
  57. return
  58. log.debug('New Pagure notification: {}\n{}'.format(
  59. notification_id, json.dumps(message, indent=4, sort_keys=True)))
  60. with database_session() as database:
  61. # Handle the user action
  62. return _USER_ACTIONS_[notification_id](database, message)
  63. # Git
  64. # -----------------------------------------------------------------------------
  65. @action('git.receive')
  66. def git_commit(database, message):
  67. """
  68. A user has committed something to a Repository.
  69. """
  70. # This is the person that has pushed the commits, not the author
  71. person = database \
  72. .query(model.Person) \
  73. .filter(model.Person.user == message['agent']) \
  74. .one_or_none()
  75. project = database \
  76. .query(model.Project) \
  77. .filter(model.Project.id == message['repo']['id']) \
  78. .one_or_none()
  79. repository = database \
  80. .query(model.Repository) \
  81. .filter(model.Repository.id == project.id) \
  82. .one_or_none()
  83. # This should never raise an error otherwise there's a bug in Pagure
  84. assert person and project and repository
  85. if repository.is_remote:
  86. log.debug('Local users should not push to remote repositories. '
  87. 'Open a MergeRequest instead. If you have commit access to a '
  88. 'remote repository, you should push to its URL.')
  89. return
  90. log.debug('Sending Push notification to Repository {} followers.'.format(repository.uri))
  91. repository_jsonld = activitypub.fetch(repository.local_uri)
  92. # TODO The Pagure notification only gives us the start_commit and end_commit.
  93. # We cannot rely on fetching from the repo all the commits between these
  94. # two, because there could be other commits in between, pushed earlier.
  95. # We've got to modify the Pagure notification (in the Pagure codebase)
  96. # to return the entire list of commits instead of only 2.
  97. commits_uri = []
  98. for hash in [ message['start_commit'], message['end_commit'] ]:
  99. commits_uri.append('{}/c/{}'.format(project.uri, hash))
  100. activitypub.Activity(
  101. type = 'Push',
  102. actor = person.uri,
  103. object = {
  104. 'type': 'OrderedCollection',
  105. 'totalItems': message['total_commits'],
  106. 'items': list(set(commits_uri))
  107. },
  108. to = repository_jsonld['followers'],
  109. target = activitypub.Branch(project, repository, message['branch'])['id'],
  110. context = repository.uri
  111. ).distribute()
  112. @action('git.tag.creation')
  113. def git_tag(database, message):
  114. """
  115. A user has tagged a Repository.
  116. """
  117. project = database \
  118. .query(model.Project) \
  119. .filter(model.Project.id == message['repo']['id']) \
  120. .one_or_none()
  121. repository = database \
  122. .query(model.Repository) \
  123. .filter(model.Repository.id == project.id) \
  124. .one_or_none()
  125. person = database \
  126. .query(model.Person) \
  127. .filter(model.Person.user == message['agent']) \
  128. .one_or_none()
  129. assert project and repository and person
  130. if repository.is_remote:
  131. log.debug('Local users should not push to remote repositories. '
  132. 'If you have commit access to a remote repository, you should '
  133. 'push to its URL.')
  134. return
  135. log.debug('Sending Create(Tag) notification to Repository {} followers.'.format(repository.uri))
  136. ref = activitypub.TagRef(repository, 'refs/tags/{}'.format(message['tag']))
  137. repository_jsonld = activitypub.fetch(repository.local_uri)
  138. activitypub.Activity(
  139. type = 'Create',
  140. actor = person.uri,
  141. object = ref['id'],
  142. to = repository_jsonld['followers']
  143. ).distribute()
  144. # Actor
  145. # -----------------------------------------------------------------------------
  146. @action('forgefed.follow')
  147. def follow(database, message):
  148. """
  149. An Actor has followed another Actor
  150. """
  151. follower = model.Person.test_or_set(database, message['follower']['id'])
  152. followed = model.Person.test_or_set(database, message['followed']['id'])
  153. if follower.is_remote:
  154. log.debug('Follower Actor cannot be remote.')
  155. return
  156. activitypub.Activity(
  157. type = 'Follow',
  158. actor = follower.uri,
  159. object = followed.uri,
  160. to = followed.uri
  161. ).distribute()
  162. # Ticket
  163. # -----------------------------------------------------------------------------
  164. @action('issue.new')
  165. def new_issue(database, message):
  166. """
  167. A user has created a new Issue.
  168. """
  169. person = database \
  170. .query(model.Person) \
  171. .filter(model.Person.user == message['issue']['user']['name']) \
  172. .one_or_none()
  173. project = database \
  174. .query(model.Project) \
  175. .filter(model.Project.id == message['project']['id']) \
  176. .one_or_none()
  177. tickettracker = database \
  178. .query(model.TicketTracker) \
  179. .filter(model.TicketTracker.id == project.id) \
  180. .one_or_none()
  181. ticket = database \
  182. .query(model.Ticket) \
  183. .filter(model.Ticket.id == message['issue']['id'],
  184. model.Ticket.project_id == message['project']['id']) \
  185. .one_or_none()
  186. # This should never raise an error otherwise there's a bug in Pagure
  187. assert person and project and tickettracker and ticket
  188. if tickettracker.is_remote:
  189. log.debug('Sending new Ticket to remote tracker.')
  190. # Get the JSONLD of the Ticket
  191. ticket_jsonld = activitypub.fetch(ticket.local_uri)
  192. activitypub.Activity(
  193. type = 'Create',
  194. actor = person.uri,
  195. to = tickettracker.remote_uri,
  196. object = ticket_jsonld
  197. ).distribute()
  198. else:
  199. log.debug('Sending new Ticket to TicketTracker followers.')
  200. # Get the JSONLD of the TicketTracker
  201. tickettracker_jsonld = activitypub.fetch(tickettracker.local_uri)
  202. activitypub.Activity(
  203. type = 'Create',
  204. actor = person.uri,
  205. to = tickettracker_jsonld['followers'],
  206. object = ticket.local_uri
  207. ).distribute()
  208. @action('issue.comment.added')
  209. def new_issue_comment(database, message):
  210. """
  211. A user has commented on an issue
  212. """
  213. # The Pagure notification contains *all* the comments of the issue
  214. # in an ordered list, so we need to extract the last one from the
  215. # list of comments.
  216. comment = database \
  217. .query(model.TicketComment) \
  218. .filter(model.TicketComment.id == message['issue']['comments'][-1]['id']) \
  219. .one_or_none()
  220. person = database \
  221. .query(model.Person) \
  222. .filter(model.Person.id == comment.user.id) \
  223. .one_or_none()
  224. project = database \
  225. .query(model.Project) \
  226. .filter(model.Project.id == message['project']['id']) \
  227. .one_or_none()
  228. tickettracker = database \
  229. .query(model.TicketTracker) \
  230. .filter(model.TicketTracker.id == project.id) \
  231. .one_or_none()
  232. # Our local ticket
  233. ticket = database \
  234. .query(model.Ticket) \
  235. .filter(model.Ticket.id == message['issue']['id'],
  236. model.Ticket.project_id == project.id) \
  237. .one_or_none()
  238. if tickettracker.is_remote:
  239. if not person.is_remote:
  240. log.debug('Sending new comment to remote tracker...')
  241. activitypub.Activity(
  242. type = 'Create',
  243. actor = person.uri,
  244. object = comment.uri,
  245. to = tickettracker.uri
  246. ).distribute()
  247. else:
  248. log.debug('Sending new comment to TicketTracker followers.')
  249. # Retrieve the pagure "watchlist"
  250. watchlist = pagure.lib.query.get_watch_list(database, ticket)
  251. actors = []
  252. for username in watchlist:
  253. actor = database \
  254. .query(model.Person) \
  255. .filter(model.Person.user == username) \
  256. .one_or_none()
  257. actors.append(actor.uri)
  258. # Send the Activity
  259. activitypub.Activity(
  260. type = 'Create',
  261. actor = person.uri,
  262. object = comment.uri,
  263. to = actors
  264. ).distribute()
  265. @action('issue.edit')
  266. def edit_issue(database, message):
  267. """
  268. A Pagure issue (Ticket) has been edited.
  269. """
  270. project = database \
  271. .query(model.Project) \
  272. .filter(model.Project.id == message['project']['id']) \
  273. .one_or_none()
  274. tickettracker = database \
  275. .query(model.TicketTracker) \
  276. .filter(model.TicketTracker.id == project.id) \
  277. .one_or_none()
  278. ticket = database \
  279. .query(model.Ticket) \
  280. .filter(model.Ticket.id == message['issue']['id'],
  281. model.Ticket.project_id == project.id) \
  282. .one_or_none()
  283. # The user that edited the Ticket
  284. person = database \
  285. .query(model.Person) \
  286. .filter(model.Person.user == message['agent']) \
  287. .one_or_none()
  288. assert project and tickettracker and ticket and person
  289. if 'status' in message['fields'] \
  290. and message['issue']['status'].upper() == 'CLOSED':
  291. """
  292. A user has closed an issue
  293. """
  294. # If the user has closed the Ticket of a remote tracker, we just
  295. # send the Activity to the tracker
  296. if ticket.is_remote:
  297. log.debug('Local user has closed the remote Ticket {}'.format(ticket.uri))
  298. activitypub.Activity(
  299. type = 'Resolve',
  300. actor = person.uri,
  301. object = ticket.remote_uri
  302. ).distribute()
  303. # otherwise we simply send the Activity to the Ticket's watchlist
  304. else:
  305. log.debug('Local user has closed the local Ticket {}'.format(ticket.uri))
  306. # Retrieve the pagure "watchlist"
  307. watchlist = pagure.lib.query.get_watch_list(database, ticket)
  308. actors = []
  309. for username in watchlist:
  310. actor = database \
  311. .query(model.Person) \
  312. .filter(model.Person.user == username) \
  313. .one_or_none()
  314. actors.append(actor.uri)
  315. activitypub.Activity(
  316. type = 'Resolve',
  317. actor = person.uri,
  318. object = ticket.local_uri,
  319. to = actors
  320. ).distribute()
  321. if 'status' in message['fields'] \
  322. and message['issue']['status'].upper() == 'OPEN':
  323. """
  324. A user has reopened an issue
  325. """
  326. # If the user has opened the Ticket of a remote tracker, we just
  327. # send the Activity to the tracker
  328. if ticket.is_remote:
  329. log.debug('Local user has reopened the remote Ticket {}'.format(ticket.uri))
  330. activitypub.Activity(
  331. type = 'Reopen',
  332. actor = person.uri,
  333. object = ticket.remote_uri
  334. ).distribute()
  335. # otherwise we simply send the Activity to the Ticket's watchlist
  336. else:
  337. log.debug('Local user has reopened the local Ticket {}'.format(ticket.uri))
  338. # Retrieve the pagure "watchlist"
  339. watchlist = pagure.lib.query.get_watch_list(database, ticket)
  340. actors = []
  341. for username in watchlist:
  342. actor = database \
  343. .query(model.Person) \
  344. .filter(model.Person.user == username) \
  345. .one_or_none()
  346. actors.append(actor.uri)
  347. activitypub.Activity(
  348. type = 'Reopen',
  349. actor = person.uri,
  350. object = ticket.local_uri,
  351. to = actors
  352. ).distribute()
  353. if 'milestone' in message['fields']:
  354. """
  355. A user has changed an issue's milestone
  356. """
  357. if ticket.is_remote:
  358. log.debug('User {} has changed milestone of the remote Ticket {}'.format(
  359. person.uri, ticket.uri))
  360. activitypub.Activity(
  361. type = 'Milestone',
  362. actor = person.uri,
  363. object = ticket.remote_uri,
  364. milestone = [ message['issue']['milestone'] ],
  365. to = tickettracker.uri
  366. ).distribute()
  367. else:
  368. log.debug('User {} has changed milestone of the local Ticket {}'.format(
  369. person.uri, ticket.uri))
  370. tickettracker_jsonld = activitypub.fetch(tickettracker.uri)
  371. activitypub.Activity(
  372. type = 'Milestone',
  373. actor = person.uri,
  374. object = ticket.local_uri,
  375. milestone = [ message['issue']['milestone'] ],
  376. to = tickettracker_jsonld['followers']
  377. ).distribute()
  378. if any(field in message['fields'] for field in ['title', 'content']):
  379. """
  380. A user has edited the content of the issue.
  381. """
  382. result = {}
  383. if 'title' in message['fields']: result['summary'] = message['issue']['title']
  384. if 'content' in message['fields']: result['content'] = message['issue']['content']
  385. if ticket.is_remote:
  386. log.debug('Local user has edited the remote Ticket {}'.format(ticket.uri))
  387. activitypub.Activity(
  388. type = 'Update',
  389. actor = person.uri,
  390. object = ticket.remote_uri,
  391. to = tickettracker.remote_uri,
  392. result = result
  393. ).distribute()
  394. else:
  395. log.debug('Local user has edited the local Ticket {}'.format(ticket.uri))
  396. tickettracker_jsonld = activitypub.fetch(tickettracker.local_uri)
  397. if not tickettracker_jsonld:
  398. raise Exception('Cannot fetch TicketTracker {}'.format(ticket.local_uri))
  399. activitypub.Activity(
  400. type = 'Update',
  401. actor = person.uri,
  402. object = ticket.local_uri,
  403. to = tickettracker_jsonld['followers'],
  404. result = result
  405. ).distribute()
  406. @action('issue.assigned.added')
  407. def assign_issue(database, message):
  408. """
  409. A Pagure issue (Ticket) has a new assigned Person.
  410. """
  411. project = database \
  412. .query(model.Project) \
  413. .filter(model.Project.id == message['project']['id']) \
  414. .one_or_none()
  415. tickettracker = database \
  416. .query(model.TicketTracker) \
  417. .filter(model.TicketTracker.id == project.id) \
  418. .one_or_none()
  419. ticket = database \
  420. .query(model.Ticket) \
  421. .filter(model.Ticket.id == message['issue']['id'],
  422. model.Ticket.project_id == project.id) \
  423. .one_or_none()
  424. # The user that edited the Ticket
  425. person = database \
  426. .query(model.Person) \
  427. .filter(model.Person.user == message['agent']) \
  428. .one_or_none()
  429. assignee = database \
  430. .query(model.Person) \
  431. .filter(model.Person.user == message['issue']['assignee']['name']) \
  432. .one_or_none()
  433. assert project and tickettracker and ticket and person and assignee
  434. if ticket.is_remote:
  435. log.debug('User {} has assigned {} to remote Ticket {}'.format(
  436. person.uri, assignee.uri, ticket.uri))
  437. activitypub.Activity(
  438. type = 'Assign',
  439. actor = person.uri,
  440. object = assignee.uri,
  441. target = ticket.remote_uri,
  442. ).distribute()
  443. else:
  444. log.debug('User {} has assigned {} to local Ticket {}'.format(
  445. person.uri, assignee.uri, ticket.uri))
  446. activitypub.Activity(
  447. type = 'Assign',
  448. actor = person.uri,
  449. object = assignee.uri,
  450. target = ticket.local_uri,
  451. ).distribute()
  452. @action('issue.assigned.reset')
  453. def unassign_issue(database, message):
  454. """
  455. A Pagure issue (Ticket) has removed the assigned Person.
  456. """
  457. log.debug('Not implemented. Have to change notification in Pagure first.')
  458. return
  459. project = database \
  460. .query(model.Project) \
  461. .filter(model.Project.id == message['project']['id']) \
  462. .one_or_none()
  463. tickettracker = database \
  464. .query(model.TicketTracker) \
  465. .filter(model.TicketTracker.id == project.id) \
  466. .one_or_none()
  467. ticket = database \
  468. .query(model.Ticket) \
  469. .filter(model.Ticket.id == message['issue']['id'],
  470. model.Ticket.project_id == project.id) \
  471. .one_or_none()
  472. # The user that edited the Ticket
  473. person = database \
  474. .query(model.Person) \
  475. .filter(model.Person.user == message['agent']) \
  476. .one_or_none()
  477. assert project and tickettracker and ticket and person
  478. if ticket.is_remote:
  479. log.debug('User {} has removed assignee from remote Ticket {}'.format(
  480. person.uri, ticket.uri))
  481. activitypub.Activity(
  482. type = 'Assign',
  483. actor = person.uri,
  484. object = None,
  485. target = ticket.remote_uri,
  486. ).distribute()
  487. else:
  488. log.debug('User {} has removed assignee from local Ticket {}'.format(
  489. person.uri, ticket.uri))
  490. activitypub.Activity(
  491. type = 'Assign',
  492. actor = person.uri,
  493. object = None,
  494. target = ticket.local_uri,
  495. ).distribute()
  496. @action('issue.tag.added')
  497. def tag_issue(database, message):
  498. """
  499. A Pagure issue (Ticket) has new tags.
  500. """
  501. project = database \
  502. .query(model.Project) \
  503. .filter(model.Project.id == message['project']['id']) \
  504. .one_or_none()
  505. tickettracker = database \
  506. .query(model.TicketTracker) \
  507. .filter(model.TicketTracker.id == project.id) \
  508. .one_or_none()
  509. ticket = database \
  510. .query(model.Ticket) \
  511. .filter(model.Ticket.id == message['issue']['id'],
  512. model.Ticket.project_id == project.id) \
  513. .one_or_none()
  514. # The user that edited the Ticket
  515. person = database \
  516. .query(model.Person) \
  517. .filter(model.Person.user == message['agent']) \
  518. .one_or_none()
  519. assert project and tickettracker and ticket and person
  520. tags = []
  521. for tag in message['tags']:
  522. tag = database \
  523. .query(model.Tag) \
  524. .filter(model.Tag.project_id == project.id,
  525. model.Tag.tag == tag) \
  526. .one_or_none()
  527. if tag:
  528. tags.append(tag)
  529. for tag in tags:
  530. if ticket.is_remote:
  531. log.debug('User {} has added tags of remote Ticket {}'.format(person.uri, ticket.uri))
  532. activitypub.Activity(
  533. type = 'Add',
  534. actor = person.uri,
  535. object = tag.uri,
  536. target = ticket.remote_uri,
  537. ).distribute()
  538. else:
  539. log.debug('User {} has added tags to local Ticket {}'.format(person.uri, ticket.uri))
  540. activitypub.Activity(
  541. type = 'Add',
  542. actor = person.uri,
  543. object = tag.uri,
  544. target = ticket.local_uri
  545. ).distribute()
  546. @action('issue.dependency.added')
  547. def depend_issue(database, message):
  548. """
  549. A Pagure issue (Ticket) has new dependency.
  550. """
  551. project = database \
  552. .query(model.Project) \
  553. .filter(model.Project.id == message['project']['id']) \
  554. .one_or_none()
  555. tickettracker = database \
  556. .query(model.TicketTracker) \
  557. .filter(model.TicketTracker.id == project.id) \
  558. .one_or_none()
  559. ticket = database \
  560. .query(model.Ticket) \
  561. .filter(model.Ticket.project_id == project.id,
  562. model.Ticket.id == message['added_dependency']) \
  563. .one_or_none()
  564. dependency = database \
  565. .query(model.Ticket) \
  566. .filter(model.Ticket.project_id == project.id,
  567. model.Ticket.id == message['issue']['id']) \
  568. .one_or_none()
  569. # The user that edited the Ticket
  570. person = database \
  571. .query(model.Person) \
  572. .filter(model.Person.user == message['agent']) \
  573. .one_or_none()
  574. assert project and tickettracker and ticket and person and dependency
  575. if ticket.is_remote:
  576. log.debug('User {} has added dependency {} to remote Ticket {}'.format(
  577. person.uri, dependency.uri, ticket.uri))
  578. activitypub.Activity(
  579. type = 'Depend',
  580. actor = person.uri,
  581. object = ticket.remote_uri,
  582. target = dependency.uri
  583. ).distribute()
  584. else:
  585. log.debug('User {} has added dependency {} to local Ticket {}'.format(
  586. person.uri, dependency.uri, ticket.uri))
  587. activitypub.Activity(
  588. type = 'Depend',
  589. actor = person.uri,
  590. object = ticket.local_uri,
  591. target = dependency.uri
  592. ).distribute()
  593. @action('issue.drop')
  594. def drop_issue(database, message):
  595. """
  596. A Pagure issue (Ticket) was deleted.
  597. """
  598. tickettracker = database \
  599. .query(model.TicketTracker) \
  600. .filter(model.TicketTracker.id == message['project']['id']) \
  601. .one_or_none()
  602. # We cannot query the database because the Issue has been deleted
  603. ticket = message['issue']
  604. # The user that edited the Ticket
  605. person = database \
  606. .query(model.Person) \
  607. .filter(model.Person.user == message['agent']) \
  608. .one_or_none()
  609. assert tickettracker and ticket and person
  610. if person.is_remote:
  611. log.debug('Ignoring notification triggered by remote action.')
  612. return
  613. # Is this a local or remote Ticket?
  614. if ticket['full_url'].startswith(APP_URL):
  615. tickettracker_jsonld = activitypub.fetch(tickettracker.uri)
  616. activitypub.Activity(
  617. type = 'Delete',
  618. actor = person.uri,
  619. object = ticket['full_url'],
  620. origin = tickettracker.uri,
  621. to = tickettracker_jsonld['followers']
  622. ).distribute()
  623. else:
  624. activitypub.Activity(
  625. type = 'Delete',
  626. actor = person.uri,
  627. object = ticket['full_url'],
  628. origin = tickettracker.uri,
  629. to = tickettracker.uri
  630. ).distribute()
  631. # MergeRequest
  632. # -----------------------------------------------------------------------------
  633. @action('pull-request.new')
  634. def new_merge_request(database, message):
  635. """
  636. A user has created a Pull Request in Pagure.
  637. """
  638. person = database \
  639. .query(model.Person) \
  640. .filter(model.Person.user == message['pullrequest']['user']['name']) \
  641. .one_or_none()
  642. project = database \
  643. .query(model.Project) \
  644. .filter(model.Project.id == message['pullrequest']['project']['id']) \
  645. .one_or_none()
  646. repository = database \
  647. .query(model.Repository) \
  648. .filter(model.Repository.id == project.id) \
  649. .one_or_none()
  650. mergerequest = database \
  651. .query(model.MergeRequest) \
  652. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  653. model.MergeRequest.project_id == project.id) \
  654. .one_or_none()
  655. # This should never raise an error otherwise there's a bug in Pagure
  656. assert person and project and repository and mergerequest
  657. if repository.is_remote:
  658. log.debug('Sending new MergeRequest to remote repository...')
  659. activitypub.Activity(
  660. type = 'Create',
  661. actor = person.uri,
  662. to = repository.remote_uri,
  663. object = mergerequest.local_uri
  664. ).distribute()
  665. @action('pull-request.comment.added')
  666. def new_merge_request_comment(database, message):
  667. """
  668. A user has created a new comment for a Pull Request.
  669. """
  670. # The Pagure notification contains *all* the comments of the pull
  671. # request in an ordered list, so we need to extract the last one from
  672. # the list of comments.
  673. comment = database \
  674. .query(model.MergeRequestComment) \
  675. .filter(model.MergeRequestComment.id == message['pullrequest']['comments'][-1]['id']) \
  676. .one_or_none()
  677. person = database \
  678. .query(model.Person) \
  679. .filter(model.Person.id == comment.user.id) \
  680. .one_or_none()
  681. project = database \
  682. .query(model.Project) \
  683. .filter(model.Project.id == message['pullrequest']['project']['id']) \
  684. .one_or_none()
  685. repository = database \
  686. .query(model.Repository) \
  687. .filter(model.Repository.id == project.id) \
  688. .one_or_none()
  689. repository_jsonld = activitypub.fetch(repository.uri)
  690. # Our local pull-request
  691. mergerequest = database \
  692. .query(model.MergeRequest) \
  693. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  694. model.MergeRequest.project_id == project.id) \
  695. .one_or_none()
  696. assert comment and person and project and repository and mergerequest
  697. if repository.is_remote:
  698. if not person.is_remote:
  699. log.debug('Sending new comment to remote repository...')
  700. activitypub.Activity(
  701. type = 'Create',
  702. actor = person.uri,
  703. object = comment.uri,
  704. to = repository.uri
  705. ).distribute()
  706. else:
  707. log.debug('Sending new comment to Repository followers.')
  708. """
  709. # Retrieve the pagure "watchlist"
  710. watchlist = pagure.lib.query.get_watch_list(database, mergerequest)
  711. actors = []
  712. for username in watchlist:
  713. actor = database \
  714. .query(model.Person) \
  715. .filter(model.Person.user == username) \
  716. .one_or_none()
  717. actors.append(actor.uri)
  718. """
  719. # Send the Activity
  720. activitypub.Activity(
  721. type = 'Create',
  722. actor = person.uri,
  723. object = comment.uri,
  724. to = repository_jsonld['followers']
  725. ).distribute()
  726. @action('pull-request.initial_comment.edited')
  727. def edit_merge_request(database, message):
  728. """
  729. A user has edited the content of a MergeRequest.
  730. """
  731. project = database \
  732. .query(model.Project) \
  733. .filter(model.Project.id == message['pullrequest']['project']['id']) \
  734. .one_or_none()
  735. repository = database \
  736. .query(model.Repository) \
  737. .filter(model.Repository.id == project.id) \
  738. .one_or_none()
  739. # Our local pull-request
  740. mergerequest = database \
  741. .query(model.MergeRequest) \
  742. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  743. model.MergeRequest.project_id == project.id) \
  744. .one_or_none()
  745. person = database \
  746. .query(model.Person) \
  747. .filter(model.Person.user == message['agent']) \
  748. .one_or_none()
  749. assert project and repository and mergerequest and person
  750. result = {
  751. 'summary': message['pullrequest']['title'],
  752. 'content': message['pullrequest']['initial_comment']
  753. }
  754. if mergerequest.is_remote:
  755. log.debug('Local user has edited the remote MergeRequest {}'.format(mergerequest.uri))
  756. activitypub.Activity(
  757. type = 'Update',
  758. actor = person.uri,
  759. object = mergerequest.remote_uri,
  760. to = repository.remote_uri,
  761. result = result
  762. ).distribute()
  763. else:
  764. log.debug('Local user has edited the local MergeRequest {}'.format(mergerequest.uri))
  765. repository_jsonld = activitypub.fetch(repository.local_uri)
  766. if not repository_jsonld:
  767. raise Exception('Cannot fetch TicketTracker {}'.format(ticket.local_uri))
  768. activitypub.Activity(
  769. type = 'Update',
  770. actor = person.uri,
  771. object = mergerequest.local_uri,
  772. to = repository_jsonld['followers'],
  773. result = result
  774. ).distribute()
  775. @action('pull-request.assigned.added')
  776. def assign_pullrequest(database, message):
  777. """
  778. A Pagure pull request has a new assigned Person.
  779. """
  780. project = database \
  781. .query(model.Project) \
  782. .filter(model.Project.id == message['project']['id']) \
  783. .one_or_none()
  784. repository = database \
  785. .query(model.Repository) \
  786. .filter(model.Repository.id == project.id) \
  787. .one_or_none()
  788. mergerequest = database \
  789. .query(model.MergeRequest) \
  790. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  791. model.MergeRequest.project_id == project.id) \
  792. .one_or_none()
  793. # The user that edited the pull request
  794. person = database \
  795. .query(model.Person) \
  796. .filter(model.Person.user == message['agent']) \
  797. .one_or_none()
  798. assignee = database \
  799. .query(model.Person) \
  800. .filter(model.Person.user == message['pullrequest']['assignee']['name']) \
  801. .one_or_none()
  802. assert project and repository and mergerequest and person and assignee
  803. if mergerequest.is_remote:
  804. pass
  805. else:
  806. log.debug('User {} has assigned {} to MergeRequest {}'.format(
  807. person.uri, assignee.uri, mergerequest.uri))
  808. activitypub.Activity(
  809. type = 'Assign',
  810. actor = person.uri,
  811. object = assignee.uri,
  812. target = mergerequest.local_uri,
  813. ).distribute()
  814. @action('pull-request.assigned.reset')
  815. def unassign_pullrequest(database, message):
  816. """
  817. A Pagure issue (Ticket) has removed the assigned Person.
  818. """
  819. log.debug('Not implemented. Have to change notification in Pagure first.')
  820. return
  821. project = database \
  822. .query(model.Project) \
  823. .filter(model.Project.id == message['project']['id']) \
  824. .one_or_none()
  825. tickettracker = database \
  826. .query(model.TicketTracker) \
  827. .filter(model.TicketTracker.id == project.id) \
  828. .one_or_none()
  829. ticket = database \
  830. .query(model.Ticket) \
  831. .filter(model.Ticket.id == message['issue']['id'],
  832. model.Ticket.project_id == project.id) \
  833. .one_or_none()
  834. # The user that edited the Ticket
  835. person = database \
  836. .query(model.Person) \
  837. .filter(model.Person.user == message['agent']) \
  838. .one_or_none()
  839. assert project and tickettracker and ticket and person
  840. if ticket.is_remote:
  841. log.debug('User {} has removed assignee from remote Ticket {}'.format(
  842. person.uri, ticket.uri))
  843. activitypub.Activity(
  844. type = 'Assign',
  845. actor = person.uri,
  846. object = None,
  847. target = ticket.remote_uri,
  848. ).distribute()
  849. else:
  850. log.debug('User {} has removed assignee from local Ticket {}'.format(
  851. person.uri, ticket.uri))
  852. activitypub.Activity(
  853. type = 'Assign',
  854. actor = person.uri,
  855. object = None,
  856. target = ticket.local_uri,
  857. ).distribute()
  858. @action('pull-request.tag.added')
  859. def tag_mergerequest(database, message):
  860. """
  861. A Pagure pull request has new tags.
  862. """
  863. project = database \
  864. .query(model.Project) \
  865. .filter(model.Project.id == message['project']['id']) \
  866. .one_or_none()
  867. repository = database \
  868. .query(model.Repository) \
  869. .filter(model.Repository.id == project.id) \
  870. .one_or_none()
  871. mergerequest = database \
  872. .query(model.MergeRequest) \
  873. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  874. model.MergeRequest.project_id == project.id) \
  875. .one_or_none()
  876. # The user that edited the pull request
  877. person = database \
  878. .query(model.Person) \
  879. .filter(model.Person.user == message['agent']) \
  880. .one_or_none()
  881. assert project and repository and mergerequest and person
  882. tags = []
  883. for tag in message['tags']:
  884. tag = database \
  885. .query(model.Tag) \
  886. .filter(model.Tag.project_id == project.id,
  887. model.Tag.tag == tag) \
  888. .one_or_none()
  889. if tag:
  890. tags.append(tag)
  891. for tag in tags:
  892. if mergerequest.is_remote:
  893. log.debug('User {} has added tag {} to remote MergeRequest {}'.format(
  894. person.uri, tag.tag, mergerequest.uri))
  895. activitypub.Activity(
  896. type = 'Add',
  897. actor = person.uri,
  898. object = tag.uri,
  899. target = mergerequest.remote_uri,
  900. ).distribute()
  901. else:
  902. log.debug('User {} has added tag {} to local Ticket {}'.format(
  903. person.uri, tag.tag, mergerequest.uri))
  904. activitypub.Activity(
  905. type = 'Add',
  906. actor = person.uri,
  907. object = tag.uri,
  908. target = mergerequest.local_uri
  909. ).distribute()
  910. @action('pull-request.closed')
  911. def close_merge_request(database, message):
  912. """
  913. A user has closed a Pull Request. This notification does not distinguish between
  914. "Merged" and "Closed" (without merge). It's the same notification for both events
  915. so we have to look at the MergeRequest status.
  916. """
  917. project = database \
  918. .query(model.Project) \
  919. .filter(model.Project.id == message['pullrequest']['project']['id']) \
  920. .one_or_none()
  921. repository = database \
  922. .query(model.Repository) \
  923. .filter(model.Repository.id == project.id) \
  924. .one_or_none()
  925. # Our local pull-request
  926. mergerequest = database \
  927. .query(model.MergeRequest) \
  928. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  929. model.MergeRequest.project_id == project.id) \
  930. .one_or_none()
  931. person = database \
  932. .query(model.Person) \
  933. .filter(model.Person.user == message['agent']) \
  934. .one_or_none()
  935. assert project and repository and mergerequest and person
  936. if message['merged'] == True and mergerequest.is_remote:
  937. log.debug('Local user cannot merge the remote MergeRequest {}'.format(mergerequest.uri))
  938. if message['merged'] == False and mergerequest.is_remote:
  939. log.debug('Local user has closed the remote MergeRequest {}'.format(mergerequest.uri))
  940. activitypub.Activity(
  941. type = 'Close',
  942. actor = person.uri,
  943. object = mergerequest.local_uri,
  944. to = repository.remote_uri
  945. ).distribute()
  946. if message['merged'] == True and not mergerequest.is_remote:
  947. log.debug('Local user has merged the local MergeRequest {}'.format(mergerequest.uri))
  948. repository_jsonld = activitypub.fetch(repository.local_uri)
  949. activitypub.Activity(
  950. type = 'Resolve',
  951. actor = person.uri,
  952. object = mergerequest.local_uri,
  953. to = repository_jsonld['followers']
  954. ).distribute()
  955. if message['merged'] == False and not mergerequest.is_remote:
  956. log.debug('Local user has closed the local MergeRequest {}'.format(mergerequest.uri))
  957. repository_jsonld = activitypub.fetch(repository.local_uri)
  958. activitypub.Activity(
  959. type = 'Close',
  960. actor = person.uri,
  961. object = mergerequest.local_uri,
  962. to = repository_jsonld['followers']
  963. ).distribute()
  964. @action('pull-request.reopened')
  965. def reopen_merge_request(database, message):
  966. """
  967. A user has reopened a Pull Request.
  968. """
  969. project = database \
  970. .query(model.Project) \
  971. .filter(model.Project.id == message['pullrequest']['project']['id']) \
  972. .one_or_none()
  973. repository = database \
  974. .query(model.Repository) \
  975. .filter(model.Repository.id == project.id) \
  976. .one_or_none()
  977. # Our local pull-request
  978. mergerequest = database \
  979. .query(model.MergeRequest) \
  980. .filter(model.MergeRequest.id == message['pullrequest']['id'],
  981. model.MergeRequest.project_id == project.id) \
  982. .one_or_none()
  983. person = database \
  984. .query(model.Person) \
  985. .filter(model.Person.user == message['agent']) \
  986. .one_or_none()
  987. assert project and repository and mergerequest and person
  988. if person.is_remote:
  989. log.debug('Remote user has reopened the MergeRequest {}'.format(mergerequest.uri))
  990. return
  991. if mergerequest.is_remote:
  992. log.debug('Local user has reopened the remote MergeRequest {}'.format(mergerequest.uri))
  993. activitypub.Activity(
  994. type = 'Reopen',
  995. actor = person.uri,
  996. object = mergerequest.remote_uri,
  997. to = repository.remote_uri
  998. ).distribute()
  999. if not mergerequest.is_remote:
  1000. log.debug('Local user has reopened the local MergeRequest {}'.format(mergerequest.uri))
  1001. repository_jsonld = activitypub.fetch(repository.local_uri)
  1002. activitypub.Activity(
  1003. type = 'Reopen',
  1004. actor = person.uri,
  1005. object = mergerequest.local_uri,
  1006. to = repository_jsonld['followers']
  1007. ).distribute()
  1008. # Groups and Roles
  1009. # -----------------------------------------------------------------------------
  1010. @action('project.user.added')
  1011. def add_user(database, message):
  1012. """
  1013. A user has been added to a Pagure project.
  1014. """
  1015. person = database \
  1016. .query(model.Person) \
  1017. .filter(model.Person.user == message['agent']) \
  1018. .one_or_none()
  1019. new_member = database \
  1020. .query(model.Person) \
  1021. .filter(model.Person.user == message['new_user']) \
  1022. .one_or_none()
  1023. project = database \
  1024. .query(model.Project) \
  1025. .filter(model.Project.id == message['project']['id']) \
  1026. .one_or_none()
  1027. role = database \
  1028. .query(model.Role) \
  1029. .filter(model.Role.project_id == project.id,
  1030. model.Role.user_id == new_member.id,
  1031. model.Role.access == message['access']) \
  1032. .one_or_none()
  1033. # This should never raise an error otherwise there's a bug in Pagure
  1034. assert person and project and new_member and role
  1035. if project.is_local:
  1036. log.debug('{} has added user {} to role {}'.format(person.uri, new_member.uri, role.uri))
  1037. activitypub.Activity(
  1038. type = 'Add',
  1039. actor = person.uri,
  1040. object = new_member.uri,
  1041. target = role.uri,
  1042. context = project.uri
  1043. ).distribute()
  1044. @action('project.user.access.updated')
  1045. def update_user_access(database, message):
  1046. """
  1047. A user access level has been updated.
  1048. """
  1049. person = database \
  1050. .query(model.Person) \
  1051. .filter(model.Person.user == message['agent']) \
  1052. .one_or_none()
  1053. member = database \
  1054. .query(model.Person) \
  1055. .filter(model.Person.user == message['new_user']) \
  1056. .one_or_none()
  1057. project = database \
  1058. .query(model.Project) \
  1059. .filter(model.Project.id == message['project']['id']) \
  1060. .one_or_none()
  1061. role = database \
  1062. .query(model.Role) \
  1063. .filter(model.Role.project_id == project.id,
  1064. model.Role.user_id == member.id,
  1065. model.Role.access == message['new_access']) \
  1066. .one_or_none()
  1067. # This should never raise an error otherwise there's a bug in Pagure
  1068. assert person and project and member and role
  1069. if project.is_local:
  1070. log.debug('{} has updated user {} to role {}'.format(person.uri, member.uri, role.uri))
  1071. activitypub.Activity(
  1072. type = 'Update',
  1073. actor = person.uri,
  1074. object = member.uri,
  1075. result = { 'roles': [ role.uri for role in member.roles ] }
  1076. ).distribute()
  1077. @action('project.user.removed')
  1078. def remove_user(database, message):
  1079. """
  1080. A user has been removed from a Pagure project.
  1081. """
  1082. log.debug('Waiting for https://pagure.io/pagure/pull-request/5156 to be '
  1083. 'merged before letting this through.')
  1084. return
  1085. person = database \
  1086. .query(model.Person) \
  1087. .filter(model.Person.user == message['agent']) \
  1088. .one_or_none()
  1089. new_member = database \
  1090. .query(model.Person) \
  1091. .filter(model.Person.user == message['removed_user']) \
  1092. .one_or_none()
  1093. project = database \
  1094. .query(model.Project) \
  1095. .filter(model.Project.id == message['project']['id']) \
  1096. .one_or_none()
  1097. role = database \
  1098. .query(model.Role) \
  1099. .filter(model.Role.project_id == project.id,
  1100. model.Role.user_id == new_member.id,
  1101. model.Role.access == message['access']) \
  1102. .one_or_none()
  1103. # This should never raise an error otherwise there's a bug in Pagure
  1104. assert person and project and role
  1105. if project.is_local:
  1106. log.debug('{} has removed user {} from role {}'.format(person.uri, new_member.uri, role.uri))
  1107. activitypub.Activity(
  1108. type = 'Remove',
  1109. actor = person.uri,
  1110. object = new_member.uri,
  1111. target = role.uri,
  1112. context = project.uri
  1113. ).distribute()
  1114. @action('project.group.added')
  1115. def add_group(database, message):
  1116. """
  1117. A group has been added to a Pagure project.
  1118. """
  1119. person = database \
  1120. .query(model.Person) \
  1121. .filter(model.Person.user == message['agent']) \
  1122. .one_or_none()
  1123. new_group = database \
  1124. .query(model.Group) \
  1125. .filter(model.Group.group_name == message['new_group']) \
  1126. .one_or_none()
  1127. project = database \
  1128. .query(model.Project) \
  1129. .filter(model.Project.id == message['project']['id']) \
  1130. .one_or_none()
  1131. role = database \
  1132. .query(model.ProjectRole) \
  1133. .filter(model.ProjectRole.project_id == project.id,
  1134. model.ProjectRole.group_id == new_group.id,
  1135. model.ProjectRole.access == message['access']) \
  1136. .one_or_none()
  1137. # This should never raise an error otherwise there's a bug in Pagure
  1138. assert person and project and new_group and role
  1139. if project.is_local:
  1140. log.debug('{} has added group {} to role {}'.format(person.uri, new_group.uri, role.uri))
  1141. activitypub.Activity(
  1142. type = 'Add',
  1143. actor = person.uri,
  1144. object = new_group.uri,
  1145. target = role.uri,
  1146. context = project.uri
  1147. ).distribute()
  1148. @action('project.group.access.updated')
  1149. def update_group_access(database, message):
  1150. """
  1151. A group access level has been updated.
  1152. """
  1153. person = database \
  1154. .query(model.Person) \
  1155. .filter(model.Person.user == message['agent']) \
  1156. .one_or_none()
  1157. group = database \
  1158. .query(model.Group) \
  1159. .filter(model.Group.group_name == message['new_group']) \
  1160. .one_or_none()
  1161. project = database \
  1162. .query(model.Project) \
  1163. .filter(model.Project.id == message['project']['id']) \
  1164. .one_or_none()
  1165. role = database \
  1166. .query(model.ProjectRole) \
  1167. .filter(model.ProjectRole.project_id == project.id,
  1168. model.ProjectRole.group_id == group.id,
  1169. model.ProjectRole.access == message['new_access']) \
  1170. .one_or_none()
  1171. # This should never raise an error otherwise there's a bug in Pagure
  1172. assert person and group and project and role
  1173. if project.is_local:
  1174. log.debug('{} has updated group {} to role {}'.format(person.uri, group.uri, role.uri))
  1175. activitypub.Activity(
  1176. type = 'Update',
  1177. actor = person.uri,
  1178. object = group.uri,
  1179. result = { 'roles': [ role.uri for role in group.roles ] }
  1180. ).distribute()
  1181. @action('project.group.removed')
  1182. def remove_group(database, message):
  1183. """
  1184. A user has been removed from a Pagure project.
  1185. """
  1186. log.debug('Waiting for https://pagure.io/pagure/pull-request/5156 to be '
  1187. 'merged before letting this through.')
  1188. return
  1189. person = database \
  1190. .query(model.Person) \
  1191. .filter(model.Person.user == message['agent']) \
  1192. .one_or_none()
  1193. new_member = database \
  1194. .query(model.Person) \
  1195. .filter(model.Person.user == message['removed_user']) \
  1196. .one_or_none()
  1197. project = database \
  1198. .query(model.Project) \
  1199. .filter(model.Project.id == message['project']['id']) \
  1200. .one_or_none()
  1201. role = database \
  1202. .query(model.Role) \
  1203. .filter(model.Role.project_id == project.id,
  1204. model.Role.user_id == new_member.id,
  1205. model.Role.access == message['access']) \
  1206. .one_or_none()
  1207. # This should never raise an error otherwise there's a bug in Pagure
  1208. assert person and project and role
  1209. if project.is_local:
  1210. log.debug('{} has removed user {} from role {}'.format(person.uri, new_member.uri, role.uri))
  1211. activitypub.Activity(
  1212. type = 'Remove',
  1213. actor = person.uri,
  1214. object = new_member.uri,
  1215. target = role.uri,
  1216. context = project.uri
  1217. ).distribute()