membership.ts 8.6 KB

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