membership.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { SubstrateEvent, DatabaseManager } 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(db: DatabaseManager, id: MemberId): Promise<Membership> {
  32. const member = await db.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(db: DatabaseManager): Promise<MembershipSystemSnapshot> {
  39. const membershipSystem = await db.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 db:init"?`)
  44. }
  45. return membershipSystem
  46. }
  47. async function getOrCreateMembershipSnapshot(db: DatabaseManager, event_: SubstrateEvent) {
  48. const latestSnapshot = await getLatestMembershipSystemSnapshot(db)
  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. db: DatabaseManager,
  62. event_: SubstrateEvent,
  63. memberId: MemberId,
  64. entryMethod: typeof MembershipEntryMethod,
  65. params: BuyMembershipParameters | InviteMembershipParameters
  66. ): Promise<Membership> {
  67. const { defaultInviteCount } = await getLatestMembershipSystemSnapshot(db)
  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. })
  102. await db.save<MemberMetadata>(member.metadata)
  103. await db.save<Membership>(member)
  104. return member
  105. }
  106. export async function members_MembershipBought(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  107. const [memberId, buyMembershipParameters] = new Members.MembershipBoughtEvent(event_).params
  108. const memberEntry = new MembershipEntryPaid()
  109. const member = await createNewMemberFromParams(db, event_, memberId, memberEntry, buyMembershipParameters)
  110. const membershipBoughtEvent = new MembershipBoughtEvent({
  111. ...genericEventFields(event_),
  112. newMember: member,
  113. controllerAccount: member.controllerAccount,
  114. rootAccount: member.rootAccount,
  115. handle: member.handle,
  116. metadata: new MemberMetadata({
  117. ...member.metadata,
  118. id: undefined,
  119. }),
  120. referrer: member.referredBy,
  121. })
  122. await db.save<MemberMetadata>(membershipBoughtEvent.metadata)
  123. await db.save<MembershipBoughtEvent>(membershipBoughtEvent)
  124. // Update the other side of event<->membership relation
  125. memberEntry.membershipBoughtEventId = membershipBoughtEvent.id
  126. await db.save<Membership>(member)
  127. }
  128. export async function members_MemberProfileUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  129. const [memberId] = new Members.MemberProfileUpdatedEvent(event_).params
  130. const { metadata: metadataBytesOpt, handle } = new Members.UpdateProfileCall(event_).args
  131. const metadata = metadataBytesOpt.isSome
  132. ? deserializeMetadata(MembershipMetadata, metadataBytesOpt.unwrap())
  133. : undefined
  134. const member = await getMemberById(db, memberId)
  135. const eventTime = new Date(event_.blockTimestamp)
  136. if (typeof metadata?.name === 'string') {
  137. member.metadata.name = metadata.name || undefined
  138. member.metadata.updatedAt = eventTime
  139. }
  140. if (typeof metadata?.about === 'string') {
  141. member.metadata.about = metadata.about || undefined
  142. member.metadata.updatedAt = eventTime
  143. }
  144. // TODO: avatar
  145. if (handle.isSome) {
  146. member.handle = bytesToString(handle.unwrap())
  147. member.updatedAt = eventTime
  148. }
  149. await db.save<MemberMetadata>(member.metadata)
  150. await db.save<Membership>(member)
  151. const memberProfileUpdatedEvent = new MemberProfileUpdatedEvent({
  152. ...genericEventFields(event_),
  153. member: member,
  154. newHandle: member.handle,
  155. newMetadata: new MemberMetadata({
  156. ...member.metadata,
  157. id: undefined,
  158. }),
  159. })
  160. await db.save<MemberMetadata>(memberProfileUpdatedEvent.newMetadata)
  161. await db.save<MemberProfileUpdatedEvent>(memberProfileUpdatedEvent)
  162. }
  163. export async function members_MemberAccountsUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  164. const [memberId] = new Members.MemberAccountsUpdatedEvent(event_).params
  165. const { newRootAccount, newControllerAccount } = new Members.UpdateAccountsCall(event_).args
  166. const member = await getMemberById(db, 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 db.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 db.save<MemberAccountsUpdatedEvent>(memberAccountsUpdatedEvent)
  183. }
  184. export async function members_MemberVerificationStatusUpdated(
  185. db: DatabaseManager,
  186. event_: SubstrateEvent
  187. ): Promise<void> {
  188. const [memberId, verificationStatus] = new Members.MemberVerificationStatusUpdatedEvent(event_).params
  189. const member = await getMemberById(db, memberId)
  190. const eventTime = new Date(event_.blockTimestamp)
  191. member.isVerified = verificationStatus.valueOf()
  192. member.updatedAt = eventTime
  193. await db.save<Membership>(member)
  194. const memberVerificationStatusUpdatedEvent = new MemberVerificationStatusUpdatedEvent({
  195. ...genericEventFields(event_),
  196. member: member,
  197. isVerified: member.isVerified,
  198. })
  199. await db.save<MemberVerificationStatusUpdatedEvent>(memberVerificationStatusUpdatedEvent)
  200. }
  201. export async function members_InvitesTransferred(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  202. const [sourceMemberId, targetMemberId, numberOfInvites] = new Members.InvitesTransferredEvent(event_).params
  203. const sourceMember = await getMemberById(db, sourceMemberId)
  204. const targetMember = await getMemberById(db, 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 db.save<Membership>(sourceMember)
  211. await db.save<Membership>(targetMember)
  212. const invitesTransferredEvent = new InvitesTransferredEvent({
  213. ...genericEventFields(event_),
  214. sourceMember,
  215. targetMember,
  216. numberOfInvites: numberOfInvites.toNumber(),
  217. })
  218. await db.save<InvitesTransferredEvent>(invitesTransferredEvent)
  219. }
  220. export async function members_MemberInvited(db: DatabaseManager, event_: SubstrateEvent): 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(db, event_, memberId, entryMethod, inviteMembershipParameters)
  225. // Decrease invite count of inviting member
  226. const invitingMember = await getMemberById(db, inviteMembershipParameters.inviting_member_id)
  227. invitingMember.inviteCount -= 1
  228. invitingMember.updatedAt = eventTime
  229. await db.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 db.save<MemberMetadata>(memberInvitedEvent.metadata)
  243. await db.save<MemberInvitedEvent>(memberInvitedEvent)
  244. // Update the other side of event<->member relationship
  245. entryMethod.memberInvitedEventId = memberInvitedEvent.id
  246. await db.save<Membership>(invitedMember)
  247. }
  248. export async function members_StakingAccountAdded(db: DatabaseManager, event_: SubstrateEvent): 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 db.save<StakingAccountAddedEvent>(stakingAccountAddedEvent)
  256. }
  257. export async function members_StakingAccountConfirmed(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  258. const [accountId, memberId] = new Members.StakingAccountConfirmedEvent(event_).params
  259. const member = await getMemberById(db, memberId)
  260. const eventTime = new Date(event_.blockTimestamp)
  261. member.boundAccounts.push(accountId.toString())
  262. member.updatedAt = eventTime
  263. await db.save<Membership>(member)
  264. const stakingAccountConfirmedEvent = new StakingAccountConfirmedEvent({
  265. ...genericEventFields(event_),
  266. member,
  267. account: accountId.toString(),
  268. })
  269. await db.save<StakingAccountConfirmedEvent>(stakingAccountConfirmedEvent)
  270. }
  271. export async function members_StakingAccountRemoved(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  272. const [accountId, memberId] = new Members.StakingAccountRemovedEvent(event_).params
  273. const eventTime = new Date(event_.blockTimestamp)
  274. const member = await getMemberById(db, memberId)
  275. member.boundAccounts.splice(
  276. member.boundAccounts.findIndex((a) => a === accountId.toString()),
  277. 1
  278. )
  279. member.updatedAt = eventTime
  280. await db.save<Membership>(member)
  281. const stakingAccountRemovedEvent = new StakingAccountRemovedEvent({
  282. ...genericEventFields(event_),
  283. member,
  284. account: accountId.toString(),
  285. })
  286. await db.save<StakingAccountRemovedEvent>(stakingAccountRemovedEvent)
  287. }
  288. export async function members_InitialInvitationCountUpdated(
  289. db: DatabaseManager,
  290. event_: SubstrateEvent
  291. ): Promise<void> {
  292. const [newDefaultInviteCount] = new Members.InitialInvitationCountUpdatedEvent(event_).params
  293. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  294. membershipSystemSnapshot.defaultInviteCount = newDefaultInviteCount.toNumber()
  295. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  296. const initialInvitationCountUpdatedEvent = new InitialInvitationCountUpdatedEvent({
  297. ...genericEventFields(event_),
  298. newInitialInvitationCount: newDefaultInviteCount.toNumber(),
  299. })
  300. await db.save<InitialInvitationCountUpdatedEvent>(initialInvitationCountUpdatedEvent)
  301. }
  302. export async function members_MembershipPriceUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  303. const [newMembershipPrice] = new Members.MembershipPriceUpdatedEvent(event_).params
  304. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  305. membershipSystemSnapshot.membershipPrice = newMembershipPrice
  306. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  307. const membershipPriceUpdatedEvent = new MembershipPriceUpdatedEvent({
  308. ...genericEventFields(event_),
  309. newPrice: newMembershipPrice,
  310. })
  311. await db.save<MembershipPriceUpdatedEvent>(membershipPriceUpdatedEvent)
  312. }
  313. export async function members_ReferralCutUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  314. const [newReferralCut] = new Members.ReferralCutUpdatedEvent(event_).params
  315. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  316. membershipSystemSnapshot.referralCut = newReferralCut.toNumber()
  317. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  318. const referralCutUpdatedEvent = new ReferralCutUpdatedEvent({
  319. ...genericEventFields(event_),
  320. newValue: newReferralCut.toNumber(),
  321. })
  322. await db.save<ReferralCutUpdatedEvent>(referralCutUpdatedEvent)
  323. }
  324. export async function members_InitialInvitationBalanceUpdated(
  325. db: DatabaseManager,
  326. event_: SubstrateEvent
  327. ): Promise<void> {
  328. const [newInvitedInitialBalance] = new Members.InitialInvitationBalanceUpdatedEvent(event_).params
  329. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  330. membershipSystemSnapshot.invitedInitialBalance = newInvitedInitialBalance
  331. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  332. const initialInvitationBalanceUpdatedEvent = new InitialInvitationBalanceUpdatedEvent({
  333. ...genericEventFields(event_),
  334. newInitialBalance: newInvitedInitialBalance,
  335. })
  336. await db.save<InitialInvitationBalanceUpdatedEvent>(initialInvitationBalanceUpdatedEvent)
  337. }
  338. export async function members_LeaderInvitationQuotaUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  339. const [newQuota] = new Members.LeaderInvitationQuotaUpdatedEvent(event_).params
  340. const leaderInvitationQuotaUpdatedEvent = new LeaderInvitationQuotaUpdatedEvent({
  341. ...genericEventFields(event_),
  342. newInvitationQuota: newQuota.toNumber(),
  343. })
  344. await db.save<LeaderInvitationQuotaUpdatedEvent>(leaderInvitationQuotaUpdatedEvent)
  345. }