membership.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { EventContext, StoreContext, DatabaseManager, SubstrateEvent } from '@dzlzv/hydra-common'
  5. import { Members } from './generated/types'
  6. import { MemberId, BuyMembershipParameters, InviteMembershipParameters } from '@joystream/types/augment/all'
  7. import { MembershipMetadata } from '@joystream/metadata-protobuf'
  8. import { bytesToString, deserializeMetadata, genericEventFields } from './common'
  9. import {
  10. Membership,
  11. MembershipEntryMethod,
  12. MembershipSystemSnapshot,
  13. MemberMetadata,
  14. MembershipBoughtEvent,
  15. MemberProfileUpdatedEvent,
  16. MemberAccountsUpdatedEvent,
  17. MemberInvitedEvent,
  18. MemberVerificationStatusUpdatedEvent,
  19. InvitesTransferredEvent,
  20. StakingAccountConfirmedEvent,
  21. StakingAccountRemovedEvent,
  22. InitialInvitationCountUpdatedEvent,
  23. MembershipPriceUpdatedEvent,
  24. ReferralCutUpdatedEvent,
  25. InitialInvitationBalanceUpdatedEvent,
  26. StakingAccountAddedEvent,
  27. LeaderInvitationQuotaUpdatedEvent,
  28. MembershipEntryPaid,
  29. MembershipEntryInvited,
  30. } from 'query-node/dist/model'
  31. async function getMemberById(store: DatabaseManager, id: MemberId): Promise<Membership> {
  32. const member = await store.get(Membership, { where: { id: id.toString() }, relations: ['metadata'] })
  33. if (!member) {
  34. throw new Error(`Member(${id}) not found`)
  35. }
  36. return member
  37. }
  38. async function getLatestMembershipSystemSnapshot(store: DatabaseManager): Promise<MembershipSystemSnapshot> {
  39. const membershipSystem = await store.get(MembershipSystemSnapshot, {
  40. order: { snapshotBlock: 'DESC' },
  41. })
  42. if (!membershipSystem) {
  43. throw new Error(`Membership system snapshot not found! Forgot to run "yarn workspace query-node-root store:init"?`)
  44. }
  45. return membershipSystem
  46. }
  47. async function getOrCreateMembershipSnapshot({ store, event }: EventContext & StoreContext) {
  48. const latestSnapshot = await getLatestMembershipSystemSnapshot(store)
  49. const eventTime = new Date(event.blockTimestamp)
  50. return latestSnapshot.snapshotBlock === event.blockNumber
  51. ? latestSnapshot
  52. : new MembershipSystemSnapshot({
  53. ...latestSnapshot,
  54. createdAt: eventTime,
  55. updatedAt: eventTime,
  56. id: undefined,
  57. snapshotBlock: event.blockNumber,
  58. })
  59. }
  60. async function createNewMemberFromParams(
  61. store: DatabaseManager,
  62. event: SubstrateEvent,
  63. memberId: MemberId,
  64. entryMethod: typeof MembershipEntryMethod,
  65. params: BuyMembershipParameters | InviteMembershipParameters
  66. ): Promise<Membership> {
  67. const { defaultInviteCount } = await getLatestMembershipSystemSnapshot(store)
  68. const { root_account: rootAccount, controller_account: controllerAccount, handle, metadata: metatadaBytes } = params
  69. const metadata = deserializeMetadata(MembershipMetadata, metatadaBytes)
  70. const eventTime = new Date(event.blockTimestamp)
  71. const metadataEntity = new MemberMetadata({
  72. createdAt: eventTime,
  73. updatedAt: eventTime,
  74. name: metadata?.name || undefined,
  75. about: metadata?.about || undefined,
  76. // TODO: avatar
  77. })
  78. const member = new Membership({
  79. createdAt: eventTime,
  80. updatedAt: eventTime,
  81. id: memberId.toString(),
  82. rootAccount: rootAccount.toString(),
  83. controllerAccount: controllerAccount.toString(),
  84. handle: handle.unwrap().toString(),
  85. metadata: metadataEntity,
  86. entry: entryMethod,
  87. referredBy:
  88. entryMethod.isTypeOf === 'MembershipEntryPaid' && (params as BuyMembershipParameters).referrer_id.isSome
  89. ? new Membership({ id: (params as BuyMembershipParameters).referrer_id.unwrap().toString() })
  90. : undefined,
  91. isVerified: false,
  92. inviteCount: defaultInviteCount,
  93. boundAccounts: [],
  94. invitees: [],
  95. referredMembers: [],
  96. invitedBy:
  97. entryMethod.isTypeOf === 'MembershipEntryInvited'
  98. ? new Membership({ id: (params as InviteMembershipParameters).inviting_member_id.toString() })
  99. : undefined,
  100. isFoundingMember: false,
  101. isCouncilMember: false,
  102. councilMembers: [],
  103. referendumStageRevealingOptionResults: [],
  104. })
  105. await store.save<MemberMetadata>(member.metadata)
  106. await store.save<Membership>(member)
  107. return member
  108. }
  109. export async function members_MembershipBought({ store, event }: EventContext & StoreContext): Promise<void> {
  110. const [memberId, buyMembershipParameters] = new Members.MembershipBoughtEvent(event).params
  111. const memberEntry = new MembershipEntryPaid()
  112. const member = await createNewMemberFromParams(store, event, memberId, memberEntry, buyMembershipParameters)
  113. const membershipBoughtEvent = new MembershipBoughtEvent({
  114. ...genericEventFields(event),
  115. newMember: member,
  116. controllerAccount: member.controllerAccount,
  117. rootAccount: member.rootAccount,
  118. handle: member.handle,
  119. metadata: new MemberMetadata({
  120. ...member.metadata,
  121. id: undefined,
  122. }),
  123. referrer: member.referredBy,
  124. })
  125. await store.save<MemberMetadata>(membershipBoughtEvent.metadata)
  126. await store.save<MembershipBoughtEvent>(membershipBoughtEvent)
  127. // Update the other side of event<->membership relation
  128. memberEntry.membershipBoughtEventId = membershipBoughtEvent.id
  129. await store.save<Membership>(member)
  130. }
  131. export async function members_MemberProfileUpdated({ store, event }: EventContext & StoreContext): Promise<void> {
  132. const [memberId, newHandle, newMetadata] = new Members.MemberProfileUpdatedEvent(event).params
  133. const metadata = newMetadata.isSome ? deserializeMetadata(MembershipMetadata, newMetadata.unwrap()) : undefined
  134. const member = await getMemberById(store, memberId)
  135. const eventTime = new Date(event.blockTimestamp)
  136. // FIXME: https://github.com/Joystream/hydra/issues/435
  137. if (typeof metadata?.name === 'string') {
  138. member.metadata.name = (metadata.name || null) as string | undefined
  139. member.metadata.updatedAt = eventTime
  140. }
  141. if (typeof metadata?.about === 'string') {
  142. member.metadata.about = (metadata.about || null) as string | undefined
  143. member.metadata.updatedAt = eventTime
  144. }
  145. // TODO: avatar
  146. if (newHandle.isSome) {
  147. member.handle = bytesToString(newHandle.unwrap())
  148. member.updatedAt = eventTime
  149. }
  150. await store.save<MemberMetadata>(member.metadata)
  151. await store.save<Membership>(member)
  152. const memberProfileUpdatedEvent = new MemberProfileUpdatedEvent({
  153. ...genericEventFields(event),
  154. member: member,
  155. newHandle: member.handle,
  156. newMetadata: new MemberMetadata({
  157. ...member.metadata,
  158. id: undefined,
  159. }),
  160. })
  161. await store.save<MemberMetadata>(memberProfileUpdatedEvent.newMetadata)
  162. await store.save<MemberProfileUpdatedEvent>(memberProfileUpdatedEvent)
  163. }
  164. export async function members_MemberAccountsUpdated({ store, event }: EventContext & StoreContext): Promise<void> {
  165. const [memberId, newRootAccount, newControllerAccount] = new Members.MemberAccountsUpdatedEvent(event).params
  166. const member = await getMemberById(store, memberId)
  167. const eventTime = new Date(event.blockTimestamp)
  168. if (newControllerAccount.isSome) {
  169. member.controllerAccount = newControllerAccount.unwrap().toString()
  170. }
  171. if (newRootAccount.isSome) {
  172. member.rootAccount = newRootAccount.unwrap().toString()
  173. }
  174. member.updatedAt = eventTime
  175. await store.save<Membership>(member)
  176. const memberAccountsUpdatedEvent = new MemberAccountsUpdatedEvent({
  177. ...genericEventFields(event),
  178. member: member,
  179. newRootAccount: member.rootAccount,
  180. newControllerAccount: member.controllerAccount,
  181. })
  182. await store.save<MemberAccountsUpdatedEvent>(memberAccountsUpdatedEvent)
  183. }
  184. export async function members_MemberVerificationStatusUpdated(
  185. store: DatabaseManager,
  186. event: SubstrateEvent
  187. ): Promise<void> {
  188. const [memberId, verificationStatus] = new Members.MemberVerificationStatusUpdatedEvent(event).params
  189. const member = await getMemberById(store, memberId)
  190. const eventTime = new Date(event.blockTimestamp)
  191. member.isVerified = verificationStatus.valueOf()
  192. member.updatedAt = eventTime
  193. await store.save<Membership>(member)
  194. const memberVerificationStatusUpdatedEvent = new MemberVerificationStatusUpdatedEvent({
  195. ...genericEventFields(event),
  196. member: member,
  197. isVerified: member.isVerified,
  198. })
  199. await store.save<MemberVerificationStatusUpdatedEvent>(memberVerificationStatusUpdatedEvent)
  200. }
  201. export async function members_InvitesTransferred({ store, event }: EventContext & StoreContext): Promise<void> {
  202. const [sourceMemberId, targetMemberId, numberOfInvites] = new Members.InvitesTransferredEvent(event).params
  203. const sourceMember = await getMemberById(store, sourceMemberId)
  204. const targetMember = await getMemberById(store, targetMemberId)
  205. const eventTime = new Date(event.blockTimestamp)
  206. sourceMember.inviteCount -= numberOfInvites.toNumber()
  207. sourceMember.updatedAt = eventTime
  208. targetMember.inviteCount += numberOfInvites.toNumber()
  209. targetMember.updatedAt = eventTime
  210. await store.save<Membership>(sourceMember)
  211. await store.save<Membership>(targetMember)
  212. const invitesTransferredEvent = new InvitesTransferredEvent({
  213. ...genericEventFields(event),
  214. sourceMember,
  215. targetMember,
  216. numberOfInvites: numberOfInvites.toNumber(),
  217. })
  218. await store.save<InvitesTransferredEvent>(invitesTransferredEvent)
  219. }
  220. export async function members_MemberInvited({ store, event }: EventContext & StoreContext): Promise<void> {
  221. const [memberId, inviteMembershipParameters] = new Members.MemberInvitedEvent(event).params
  222. const eventTime = new Date(event.blockTimestamp)
  223. const entryMethod = new MembershipEntryInvited()
  224. const invitedMember = await createNewMemberFromParams(store, event, memberId, entryMethod, inviteMembershipParameters)
  225. // Decrease invite count of inviting member
  226. const invitingMember = await getMemberById(store, inviteMembershipParameters.inviting_member_id)
  227. invitingMember.inviteCount -= 1
  228. invitingMember.updatedAt = eventTime
  229. await store.save<Membership>(invitingMember)
  230. const memberInvitedEvent = new MemberInvitedEvent({
  231. ...genericEventFields(event),
  232. invitingMember,
  233. newMember: invitedMember,
  234. handle: invitedMember.handle,
  235. rootAccount: invitedMember.rootAccount,
  236. controllerAccount: invitedMember.controllerAccount,
  237. metadata: new MemberMetadata({
  238. ...invitedMember.metadata,
  239. id: undefined,
  240. }),
  241. })
  242. await store.save<MemberMetadata>(memberInvitedEvent.metadata)
  243. await store.save<MemberInvitedEvent>(memberInvitedEvent)
  244. // Update the other side of event<->member relationship
  245. entryMethod.memberInvitedEventId = memberInvitedEvent.id
  246. await store.save<Membership>(invitedMember)
  247. }
  248. export async function members_StakingAccountAdded({ store, event }: EventContext & StoreContext): Promise<void> {
  249. const [accountId, memberId] = new Members.StakingAccountAddedEvent(event).params
  250. const stakingAccountAddedEvent = new StakingAccountAddedEvent({
  251. ...genericEventFields(event),
  252. member: new Membership({ id: memberId.toString() }),
  253. account: accountId.toString(),
  254. })
  255. await store.save<StakingAccountAddedEvent>(stakingAccountAddedEvent)
  256. }
  257. export async function members_StakingAccountConfirmed({ store, event }: EventContext & StoreContext): Promise<void> {
  258. const [accountId, memberId] = new Members.StakingAccountConfirmedEvent(event).params
  259. const member = await getMemberById(store, memberId)
  260. const eventTime = new Date(event.blockTimestamp)
  261. member.boundAccounts.push(accountId.toString())
  262. member.updatedAt = eventTime
  263. await store.save<Membership>(member)
  264. const stakingAccountConfirmedEvent = new StakingAccountConfirmedEvent({
  265. ...genericEventFields(event),
  266. member,
  267. account: accountId.toString(),
  268. })
  269. await store.save<StakingAccountConfirmedEvent>(stakingAccountConfirmedEvent)
  270. }
  271. export async function members_StakingAccountRemoved({ store, event }: EventContext & StoreContext): Promise<void> {
  272. const [accountId, memberId] = new Members.StakingAccountRemovedEvent(event).params
  273. const eventTime = new Date(event.blockTimestamp)
  274. const member = await getMemberById(store, memberId)
  275. member.boundAccounts.splice(
  276. member.boundAccounts.findIndex((a) => a === accountId.toString()),
  277. 1
  278. )
  279. member.updatedAt = eventTime
  280. await store.save<Membership>(member)
  281. const stakingAccountRemovedEvent = new StakingAccountRemovedEvent({
  282. ...genericEventFields(event),
  283. member,
  284. account: accountId.toString(),
  285. })
  286. await store.save<StakingAccountRemovedEvent>(stakingAccountRemovedEvent)
  287. }
  288. export async function members_InitialInvitationCountUpdated(ctx: EventContext & StoreContext): Promise<void> {
  289. const { event, store } = ctx
  290. const [newDefaultInviteCount] = new Members.InitialInvitationCountUpdatedEvent(event).params
  291. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(ctx)
  292. membershipSystemSnapshot.defaultInviteCount = newDefaultInviteCount.toNumber()
  293. await store.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  294. const initialInvitationCountUpdatedEvent = new InitialInvitationCountUpdatedEvent({
  295. ...genericEventFields(event),
  296. newInitialInvitationCount: newDefaultInviteCount.toNumber(),
  297. })
  298. await store.save<InitialInvitationCountUpdatedEvent>(initialInvitationCountUpdatedEvent)
  299. }
  300. export async function members_MembershipPriceUpdated(ctx: EventContext & StoreContext): Promise<void> {
  301. const { event, store } = ctx
  302. const [newMembershipPrice] = new Members.MembershipPriceUpdatedEvent(event).params
  303. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(ctx)
  304. membershipSystemSnapshot.membershipPrice = newMembershipPrice
  305. await store.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  306. const membershipPriceUpdatedEvent = new MembershipPriceUpdatedEvent({
  307. ...genericEventFields(event),
  308. newPrice: newMembershipPrice,
  309. })
  310. await store.save<MembershipPriceUpdatedEvent>(membershipPriceUpdatedEvent)
  311. }
  312. export async function members_ReferralCutUpdated(ctx: EventContext & StoreContext): Promise<void> {
  313. const { event, store } = ctx
  314. const [newReferralCut] = new Members.ReferralCutUpdatedEvent(event).params
  315. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(ctx)
  316. membershipSystemSnapshot.referralCut = newReferralCut.toNumber()
  317. await store.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  318. const referralCutUpdatedEvent = new ReferralCutUpdatedEvent({
  319. ...genericEventFields(event),
  320. newValue: newReferralCut.toNumber(),
  321. })
  322. await store.save<ReferralCutUpdatedEvent>(referralCutUpdatedEvent)
  323. }
  324. export async function members_InitialInvitationBalanceUpdated(ctx: EventContext & StoreContext): Promise<void> {
  325. const { event, store } = ctx
  326. const [newInvitedInitialBalance] = new Members.InitialInvitationBalanceUpdatedEvent(event).params
  327. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(ctx)
  328. membershipSystemSnapshot.invitedInitialBalance = newInvitedInitialBalance
  329. await store.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  330. const initialInvitationBalanceUpdatedEvent = new InitialInvitationBalanceUpdatedEvent({
  331. ...genericEventFields(event),
  332. newInitialBalance: newInvitedInitialBalance,
  333. })
  334. await store.save<InitialInvitationBalanceUpdatedEvent>(initialInvitationBalanceUpdatedEvent)
  335. }
  336. export async function members_LeaderInvitationQuotaUpdated({
  337. store,
  338. event,
  339. }: EventContext & StoreContext): Promise<void> {
  340. const [newQuota] = new Members.LeaderInvitationQuotaUpdatedEvent(event).params
  341. const leaderInvitationQuotaUpdatedEvent = new LeaderInvitationQuotaUpdatedEvent({
  342. ...genericEventFields(event),
  343. newInvitationQuota: newQuota.toNumber(),
  344. })
  345. await store.save<LeaderInvitationQuotaUpdatedEvent>(leaderInvitationQuotaUpdatedEvent)
  346. }