proposalsDiscussion.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { EventContext, StoreContext, DatabaseManager } from '@joystream/hydra-common'
  5. import {
  6. Membership,
  7. ProposalDiscussionPostStatusActive,
  8. ProposalDiscussionPostStatusLocked,
  9. ProposalDiscussionPost,
  10. ProposalDiscussionThread,
  11. ProposalDiscussionPostCreatedEvent,
  12. ProposalDiscussionPostUpdatedEvent,
  13. ProposalDiscussionThreadModeClosed,
  14. ProposalDiscussionWhitelist,
  15. ProposalDiscussionThreadModeOpen,
  16. ProposalDiscussionThreadModeChangedEvent,
  17. ProposalDiscussionPostDeletedEvent,
  18. ProposalDiscussionPostStatusRemoved,
  19. } from 'query-node/dist/model'
  20. import { bytesToString, deserializeMetadata, genericEventFields, MemoryCache } from './common'
  21. import { ProposalsDiscussion } from '../generated/types'
  22. import { ProposalsDiscussionPostMetadata } from '@joystream/metadata-protobuf'
  23. import { In } from 'typeorm'
  24. async function getPost(store: DatabaseManager, id: string) {
  25. const post = await store.get(ProposalDiscussionPost, { where: { id } })
  26. if (!post) {
  27. throw new Error(`Proposal discussion post not found by id: ${id}`)
  28. }
  29. return post
  30. }
  31. async function getThread(store: DatabaseManager, id: string) {
  32. const thread = await store.get(ProposalDiscussionThread, { where: { id } })
  33. if (!thread) {
  34. throw new Error(`Proposal discussion thread not found by id: ${id}`)
  35. }
  36. return thread
  37. }
  38. export async function proposalsDiscussion_ThreadCreated({ event }: EventContext & StoreContext): Promise<void> {
  39. const [threadId] = new ProposalsDiscussion.ThreadCreatedEvent(event).params
  40. MemoryCache.lastCreatedProposalThreadId = threadId
  41. }
  42. export async function proposalsDiscussion_PostCreated({ event, store }: EventContext & StoreContext): Promise<void> {
  43. // FIXME: extremely ugly and insecure workaround for `batch` and `sudo` calls support.
  44. // Ideally this data would be part of the event data
  45. let editable: boolean
  46. if (!event.extrinsic) {
  47. throw new Error('Missing extrinsic for proposalsDiscussion.PostCreated event!')
  48. } else if (event.extrinsic.section === 'utility' && event.extrinsic.method === 'batch') {
  49. // We cannot use new Utility.BatchCall(event).args, because createTypeUnsafe fails on Call
  50. // First (and only) argument of utility.batch is "calls"
  51. const calls = event.extrinsic.args[0].value as any[]
  52. // proposalsDiscussion.addPost call index is currently 0x1f00
  53. const call = calls.find((c) => c.callIndex === '0x1f00')
  54. if (!call) {
  55. throw new Error('Could not find proposalsDiscussion.addPostCall in a batch!')
  56. }
  57. editable = call.args.editable
  58. } else if (
  59. event.extrinsic.section === 'sudo' &&
  60. (event.extrinsic.method === 'sudo' || event.extrinsic.method === 'sudoAs')
  61. ) {
  62. // Extract call arg
  63. editable = (event.extrinsic.args[0].value as any).args.editable
  64. } else {
  65. editable = new ProposalsDiscussion.AddPostCall(event).args.editable.valueOf()
  66. }
  67. const [postId, memberId, threadId, metadataBytes] = new ProposalsDiscussion.PostCreatedEvent(event).params
  68. const eventTime = new Date(event.blockTimestamp)
  69. const metadata = deserializeMetadata(ProposalsDiscussionPostMetadata, metadataBytes)
  70. const repliesTo =
  71. typeof metadata?.repliesTo === 'number'
  72. ? await store.get(ProposalDiscussionPost, { where: { id: metadata.repliesTo.toString() } })
  73. : undefined
  74. const text = typeof metadata?.text === 'string' ? metadata.text : bytesToString(metadataBytes)
  75. const post = new ProposalDiscussionPost({
  76. id: postId.toString(),
  77. createdAt: eventTime,
  78. updatedAt: eventTime,
  79. author: new Membership({ id: memberId.toString() }),
  80. status: editable ? new ProposalDiscussionPostStatusActive() : new ProposalDiscussionPostStatusLocked(),
  81. isVisible: true,
  82. text,
  83. repliesTo,
  84. discussionThread: new ProposalDiscussionThread({ id: threadId.toString() }),
  85. })
  86. await store.save<ProposalDiscussionPost>(post)
  87. const postCreatedEvent = new ProposalDiscussionPostCreatedEvent({
  88. ...genericEventFields(event),
  89. post: post,
  90. text,
  91. })
  92. await store.save<ProposalDiscussionPostCreatedEvent>(postCreatedEvent)
  93. }
  94. export async function proposalsDiscussion_PostUpdated({ event, store }: EventContext & StoreContext): Promise<void> {
  95. const [postId, , , newTextBytes] = new ProposalsDiscussion.PostUpdatedEvent(event).params
  96. const post = await getPost(store, postId.toString())
  97. const newText = bytesToString(newTextBytes)
  98. post.text = newText
  99. post.updatedAt = new Date(event.blockTimestamp)
  100. await store.save<ProposalDiscussionPost>(post)
  101. const postUpdatedEvent = new ProposalDiscussionPostUpdatedEvent({
  102. ...genericEventFields(event),
  103. post,
  104. text: newText,
  105. })
  106. await store.save<ProposalDiscussionPostUpdatedEvent>(postUpdatedEvent)
  107. }
  108. export async function proposalsDiscussion_ThreadModeChanged({
  109. event,
  110. store,
  111. }: EventContext & StoreContext): Promise<void> {
  112. const [threadId, threadMode, memberId] = new ProposalsDiscussion.ThreadModeChangedEvent(event).params
  113. const eventTime = new Date(event.blockTimestamp)
  114. const thread = await getThread(store, threadId.toString())
  115. if (threadMode.isClosed) {
  116. const newMode = new ProposalDiscussionThreadModeClosed()
  117. const whitelistMemberIds = threadMode.asClosed
  118. const members = await store.getMany(Membership, {
  119. where: { id: In(whitelistMemberIds.map((id) => id.toString())) },
  120. })
  121. const whitelist = new ProposalDiscussionWhitelist({
  122. createdAt: eventTime,
  123. updatedAt: eventTime,
  124. members,
  125. })
  126. await store.save<ProposalDiscussionWhitelist>(whitelist)
  127. newMode.whitelistId = whitelist.id
  128. thread.mode = newMode
  129. } else if (threadMode.isOpen) {
  130. const newMode = new ProposalDiscussionThreadModeOpen()
  131. thread.mode = newMode
  132. } else {
  133. throw new Error(`Unrecognized proposal thread mode: ${threadMode.type}`)
  134. }
  135. thread.updatedAt = eventTime
  136. await store.save<ProposalDiscussionThread>(thread)
  137. const threadModeChangedEvent = new ProposalDiscussionThreadModeChangedEvent({
  138. ...genericEventFields(event),
  139. actor: new Membership({ id: memberId.toString() }),
  140. newMode: thread.mode,
  141. thread: thread,
  142. })
  143. await store.save<ProposalDiscussionThreadModeChangedEvent>(threadModeChangedEvent)
  144. }
  145. export async function proposalsDiscussion_PostDeleted({ event, store }: EventContext & StoreContext): Promise<void> {
  146. const [memberId, , postId, hide] = new ProposalsDiscussion.PostDeletedEvent(event).params
  147. const eventTime = new Date(event.blockTimestamp)
  148. const post = await getPost(store, postId.toString())
  149. const postDeletedEvent = new ProposalDiscussionPostDeletedEvent({
  150. ...genericEventFields(event),
  151. post,
  152. actor: new Membership({ id: memberId.toString() }),
  153. })
  154. await store.save<ProposalDiscussionPostDeletedEvent>(postDeletedEvent)
  155. const newStatus = hide.isTrue ? new ProposalDiscussionPostStatusRemoved() : new ProposalDiscussionPostStatusLocked()
  156. newStatus.deletedInEventId = postDeletedEvent.id
  157. post.isVisible = hide.isFalse
  158. post.status = newStatus
  159. post.updatedAt = eventTime
  160. await store.save<ProposalDiscussionPost>(post)
  161. }