membership.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import { Bytes } from '@polkadot/types'
  2. import { MemberId } from '@joystream/types/members'
  3. import { SubstrateEvent, EventContext, StoreContext } from '@joystream/hydra-common'
  4. import { bytesToString, inconsistentState, logger, extractExtrinsicArgs, extractSudoCallParameters } from './common'
  5. import { Members } from './generated/types'
  6. import { MembershipEntryMethod, Membership } from 'query-node/dist/model'
  7. import { EntryMethod } from '@joystream/types/augment'
  8. // eslint-disable-next-line @typescript-eslint/naming-convention
  9. export async function members_MemberRegistered({ event, store }: EventContext & StoreContext): Promise<void> {
  10. // read event data
  11. const [memberId, accountId, entryMethod] = new Members.MemberRegisteredEvent(event).params
  12. const { avatarUri, about, handle } = extractExtrinsicArgs(event, Members.BuyMembershipCall, {
  13. handle: 1,
  14. avatarUri: 2,
  15. about: 3,
  16. })
  17. // create new membership
  18. const member = new Membership({
  19. // main data
  20. id: memberId.toString(),
  21. rootAccount: accountId.toString(),
  22. controllerAccount: accountId.toString(),
  23. // Handle is required by the runtime during registration. Lack of it will throw an error
  24. handle: bytesToString(handle.unwrap()),
  25. about: about.isSome ? bytesToString(about.unwrap()) : undefined,
  26. avatarUri: avatarUri.isSome ? bytesToString(avatarUri.unwrap()) : undefined,
  27. createdInBlock: event.blockNumber,
  28. entry: convertEntryMethod(entryMethod),
  29. // fill in auto-generated fields
  30. createdAt: new Date(event.blockTimestamp),
  31. updatedAt: new Date(event.blockTimestamp),
  32. })
  33. // save membership
  34. await store.save<Membership>(member)
  35. // emit log event
  36. logger.info('Member has been registered', { ids: memberId })
  37. }
  38. // eslint-disable-next-line @typescript-eslint/naming-convention
  39. export async function members_MemberUpdatedAboutText({ event, store }: EventContext & StoreContext): Promise<void> {
  40. // read event data
  41. const { text, memberId } = isUpdateMembershipExtrinsic(event)
  42. ? unpackUpdateMembershipOptions(
  43. extractExtrinsicArgs(event, Members.UpdateMembershipCall, { memberId: 0, about: 3 })
  44. )
  45. : extractExtrinsicArgs(event, Members.ChangeMemberAboutTextCall, { memberId: 0, text: 1 })
  46. // load member
  47. const member = await store.get(Membership, { where: { id: memberId.toString() } })
  48. // ensure member exists
  49. if (!member) {
  50. return inconsistentState(`Non-existing member about text update requested`, memberId)
  51. }
  52. // update member
  53. member.about = bytesToString(text)
  54. // set last update time
  55. member.updatedAt = new Date(event.blockTimestamp)
  56. // save member
  57. await store.save<Membership>(member)
  58. // emit log event
  59. logger.info("Member's about text has been updated", { ids: memberId })
  60. }
  61. // eslint-disable-next-line @typescript-eslint/naming-convention
  62. export async function members_MemberUpdatedAvatar({ event, store }: EventContext & StoreContext): Promise<void> {
  63. // read event data
  64. const { uri, memberId } = isUpdateMembershipExtrinsic(event)
  65. ? unpackUpdateMembershipOptions(
  66. extractExtrinsicArgs(event, Members.UpdateMembershipCall, { memberId: 0, avatarUri: 2 })
  67. )
  68. : extractExtrinsicArgs(event, Members.ChangeMemberAvatarCall, { memberId: 0, uri: 1 })
  69. // load member
  70. const member = await store.get(Membership, { where: { id: memberId.toString() } })
  71. // ensure member exists
  72. if (!member) {
  73. return inconsistentState(`Non-existing member avatar update requested`, memberId)
  74. }
  75. // update member
  76. member.avatarUri = bytesToString(uri)
  77. // set last update time
  78. member.updatedAt = new Date(event.blockTimestamp)
  79. // save member
  80. await store.save<Membership>(member)
  81. // emit log event
  82. logger.info("Member's avatar has been updated", { ids: memberId })
  83. }
  84. // eslint-disable-next-line @typescript-eslint/naming-convention
  85. export async function members_MemberUpdatedHandle({ event, store }: EventContext & StoreContext): Promise<void> {
  86. // read event data
  87. const { handle, memberId } = isUpdateMembershipExtrinsic(event)
  88. ? unpackUpdateMembershipOptions(
  89. extractExtrinsicArgs(event, Members.UpdateMembershipCall, { memberId: 0, handle: 1 })
  90. )
  91. : extractExtrinsicArgs(event, Members.ChangeMemberHandleCall, { memberId: 0, handle: 1 })
  92. // load member
  93. const member = await store.get(Membership, { where: { id: memberId.toString() } })
  94. // ensure member exists
  95. if (!member) {
  96. return inconsistentState(`Non-existing member handle update requested`, memberId)
  97. }
  98. // update member
  99. member.handle = bytesToString(handle)
  100. // set last update time
  101. member.updatedAt = new Date(event.blockTimestamp)
  102. // save member
  103. await store.save<Membership>(member)
  104. // emit log event
  105. logger.info("Member's avatar has been updated", { ids: memberId })
  106. }
  107. // eslint-disable-next-line @typescript-eslint/naming-convention
  108. export async function members_MemberSetRootAccount({ event, store }: EventContext & StoreContext): Promise<void> {
  109. // read event data
  110. const { newRootAccount, memberId } = extractExtrinsicArgs(event, Members.SetRootAccountCall, {
  111. memberId: 0,
  112. newRootAccount: 1,
  113. })
  114. // load member
  115. const member = await store.get(Membership, { where: { id: memberId.toString() } })
  116. // ensure member exists
  117. if (!member) {
  118. return inconsistentState(`Non-existing member root account update requested`, memberId)
  119. }
  120. // update member
  121. member.rootAccount = newRootAccount.toString()
  122. // set last update time
  123. member.updatedAt = new Date(event.blockTimestamp)
  124. // save member
  125. await store.save<Membership>(member)
  126. // emit log event
  127. logger.info("Member's root has been updated", { ids: memberId })
  128. }
  129. // eslint-disable-next-line @typescript-eslint/naming-convention
  130. export async function members_MemberSetControllerAccount({ event, store }: EventContext & StoreContext): Promise<void> {
  131. // read event data
  132. const { newControllerAccount, memberId } = extractExtrinsicArgs(event, Members.SetControllerAccountCall, {
  133. memberId: 0,
  134. newControllerAccount: 1,
  135. })
  136. // load member
  137. const member = await store.get(Membership, { where: { id: memberId.toString() } })
  138. // ensure member exists
  139. if (!member) {
  140. return inconsistentState(`Non-existing member controller account update requested`, memberId)
  141. }
  142. // update member
  143. member.controllerAccount = newControllerAccount.toString()
  144. // set last update time
  145. member.updatedAt = new Date(event.blockTimestamp)
  146. // save member
  147. await store.save<Membership>(member)
  148. // emit log event
  149. logger.info("Member's controller has been updated", { ids: memberId })
  150. }
  151. /// ///////////////// Helpers ////////////////////////////////////////////////////
  152. function convertEntryMethod(entryMethod: EntryMethod): MembershipEntryMethod {
  153. // paid membership?
  154. if (entryMethod.isPaid) {
  155. return MembershipEntryMethod.PAID
  156. }
  157. // paid membership?
  158. if (entryMethod.isScreening) {
  159. return MembershipEntryMethod.SCREENING
  160. }
  161. // paid membership?
  162. if (entryMethod.isGenesis) {
  163. return MembershipEntryMethod.GENESIS
  164. }
  165. // should never happen
  166. logger.error('Not implemented entry method', { entryMethod: entryMethod.toString() })
  167. throw new Error('Not implemented entry method')
  168. }
  169. /*
  170. Returns true if event is emitted inside of `update_membership` extrinsic.
  171. */
  172. function isUpdateMembershipExtrinsic(event: SubstrateEvent): boolean {
  173. if (!event.extrinsic) {
  174. // this should never happen
  175. return false
  176. }
  177. if (event.extrinsic.method === 'updateMembership') {
  178. return true
  179. }
  180. // no sudo was used to update membership -> this is not updateMembership
  181. if (event.extrinsic.section !== 'sudo') {
  182. return false
  183. }
  184. const sudoCallParameters = extractSudoCallParameters<unknown[]>(event)
  185. // very trivial check if update_membership extrinsic was used
  186. return sudoCallParameters.args.length === 4 // memberId, handle, avatarUri, about
  187. }
  188. interface IUnpackedUpdateMembershipOptions {
  189. memberId: MemberId
  190. handle: Bytes
  191. uri: Bytes
  192. text: Bytes
  193. }
  194. /*
  195. Returns unwrapped data + unite naming of uri/avatarUri and about/text
  196. */
  197. function unpackUpdateMembershipOptions(args: Members.UpdateMembershipCall['args']): IUnpackedUpdateMembershipOptions {
  198. return {
  199. memberId: args.memberId,
  200. handle: args.handle.unwrapOrDefault(),
  201. uri: args.avatarUri.unwrapOrDefault(),
  202. text: args.about.unwrapOrDefault(),
  203. }
  204. }