membership.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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 BN from 'bn.js'
  7. import { MemberId, BuyMembershipParameters, InviteMembershipParameters } from '@joystream/types/augment/all'
  8. import { MembershipMetadata } from '@joystream/metadata-protobuf'
  9. import { bytesToString, createEvent, deserializeMetadata, getOrCreateBlock } from './common'
  10. import {
  11. Membership,
  12. EventType,
  13. MembershipEntryMethod,
  14. MembershipSystemSnapshot,
  15. MemberMetadata,
  16. MembershipBoughtEvent,
  17. MemberProfileUpdatedEvent,
  18. MemberAccountsUpdatedEvent,
  19. MemberInvitedEvent,
  20. MemberVerificationStatusUpdatedEvent,
  21. InvitesTransferredEvent,
  22. StakingAccountConfirmedEvent,
  23. StakingAccountRemovedEvent,
  24. InitialInvitationCountUpdatedEvent,
  25. MembershipPriceUpdatedEvent,
  26. ReferralCutUpdatedEvent,
  27. InitialInvitationBalanceUpdatedEvent,
  28. StakingAccountAddedEvent,
  29. LeaderInvitationQuotaUpdatedEvent,
  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. relations: ['snapshotBlock'],
  42. })
  43. if (!membershipSystem) {
  44. throw new Error(`Membership system snapshot not found! Forgot to run "yarn workspace query-node-root db:init"?`)
  45. }
  46. return membershipSystem
  47. }
  48. async function getOrCreateMembershipSnapshot(db: DatabaseManager, event_: SubstrateEvent) {
  49. const latestSnapshot = await getLatestMembershipSystemSnapshot(db)
  50. const eventTime = new Date(event_.blockTimestamp)
  51. return latestSnapshot.snapshotBlock.number === event_.blockNumber
  52. ? latestSnapshot
  53. : new MembershipSystemSnapshot({
  54. ...latestSnapshot,
  55. createdAt: eventTime,
  56. updatedAt: eventTime,
  57. id: undefined,
  58. snapshotBlock: await getOrCreateBlock(db, event_),
  59. snapshotTime: new Date(new BN(event_.blockTimestamp).toNumber()),
  60. })
  61. }
  62. async function newMembershipFromParams(
  63. db: DatabaseManager,
  64. event_: SubstrateEvent,
  65. memberId: MemberId,
  66. entryMethod: MembershipEntryMethod,
  67. params: BuyMembershipParameters | InviteMembershipParameters
  68. ): Promise<Membership> {
  69. const { defaultInviteCount } = await getLatestMembershipSystemSnapshot(db)
  70. const { root_account: rootAccount, controller_account: controllerAccount, handle, metadata: metatadaBytes } = params
  71. const metadata = deserializeMetadata(MembershipMetadata, metatadaBytes)
  72. const eventTime = new Date(event_.blockTimestamp)
  73. const metadataEntity = new MemberMetadata({
  74. createdAt: eventTime,
  75. updatedAt: eventTime,
  76. name: metadata?.name || undefined,
  77. about: metadata?.about || undefined,
  78. // TODO: avatar
  79. })
  80. const member = new Membership({
  81. createdAt: eventTime,
  82. updatedAt: eventTime,
  83. id: memberId.toString(),
  84. rootAccount: rootAccount.toString(),
  85. controllerAccount: controllerAccount.toString(),
  86. handle: handle.unwrap().toString(),
  87. metadata: metadataEntity,
  88. registeredAtBlock: await getOrCreateBlock(db, event_),
  89. registeredAtTime: new Date(event_.blockTimestamp),
  90. entry: entryMethod,
  91. referredBy:
  92. entryMethod === MembershipEntryMethod.PAID && (params as BuyMembershipParameters).referrer_id.isSome
  93. ? new Membership({ id: (params as BuyMembershipParameters).referrer_id.unwrap().toString() })
  94. : undefined,
  95. isVerified: false,
  96. inviteCount: defaultInviteCount,
  97. boundAccounts: [],
  98. invitees: [],
  99. referredMembers: [],
  100. invitedBy:
  101. entryMethod === MembershipEntryMethod.INVITED
  102. ? new Membership({ id: (params as InviteMembershipParameters).inviting_member_id.toString() })
  103. : undefined,
  104. isFoundingMember: false,
  105. })
  106. await db.save<MemberMetadata>(member.metadata)
  107. await db.save<Membership>(member)
  108. return member
  109. }
  110. export async function members_MembershipBought(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  111. const [memberId, buyMembershipParameters] = new Members.MembershipBoughtEvent(event_).params
  112. const eventTime = new Date(event_.blockTimestamp)
  113. const member = await newMembershipFromParams(
  114. db,
  115. event_,
  116. memberId,
  117. MembershipEntryMethod.PAID,
  118. buyMembershipParameters
  119. )
  120. const membershipBoughtEvent = new MembershipBoughtEvent({
  121. createdAt: eventTime,
  122. updatedAt: eventTime,
  123. event: await createEvent(db, event_, EventType.MembershipBought),
  124. newMember: member,
  125. controllerAccount: member.controllerAccount,
  126. rootAccount: member.rootAccount,
  127. handle: member.handle,
  128. metadata: new MemberMetadata({
  129. ...member.metadata,
  130. id: undefined,
  131. }),
  132. referrer: member.referredBy,
  133. })
  134. await db.save<MemberMetadata>(membershipBoughtEvent.metadata)
  135. await db.save<MembershipBoughtEvent>(membershipBoughtEvent)
  136. }
  137. export async function members_MemberProfileUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  138. const [memberId] = new Members.MemberProfileUpdatedEvent(event_).params
  139. const { metadata: metadataBytesOpt, handle } = new Members.UpdateProfileCall(event_).args
  140. const metadata = metadataBytesOpt.isSome
  141. ? deserializeMetadata(MembershipMetadata, metadataBytesOpt.unwrap())
  142. : undefined
  143. const member = await getMemberById(db, memberId)
  144. const eventTime = new Date(event_.blockTimestamp)
  145. if (typeof metadata?.name === 'string') {
  146. member.metadata.name = metadata.name || undefined
  147. member.metadata.updatedAt = eventTime
  148. }
  149. if (typeof metadata?.about === 'string') {
  150. member.metadata.about = metadata.about || undefined
  151. member.metadata.updatedAt = eventTime
  152. }
  153. // TODO: avatar
  154. if (handle.isSome) {
  155. member.handle = bytesToString(handle.unwrap())
  156. member.updatedAt = eventTime
  157. }
  158. await db.save<MemberMetadata>(member.metadata)
  159. await db.save<Membership>(member)
  160. const memberProfileUpdatedEvent = new MemberProfileUpdatedEvent({
  161. createdAt: eventTime,
  162. updatedAt: eventTime,
  163. event: await createEvent(db, event_, EventType.MemberProfileUpdated),
  164. member: member,
  165. newHandle: member.handle,
  166. newMetadata: new MemberMetadata({
  167. ...member.metadata,
  168. id: undefined,
  169. }),
  170. })
  171. await db.save<MemberMetadata>(memberProfileUpdatedEvent.newMetadata)
  172. await db.save<MemberProfileUpdatedEvent>(memberProfileUpdatedEvent)
  173. }
  174. export async function members_MemberAccountsUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  175. const [memberId] = new Members.MemberAccountsUpdatedEvent(event_).params
  176. const { newRootAccount, newControllerAccount } = new Members.UpdateAccountsCall(event_).args
  177. const member = await getMemberById(db, memberId)
  178. const eventTime = new Date(event_.blockTimestamp)
  179. if (newControllerAccount.isSome) {
  180. member.controllerAccount = newControllerAccount.unwrap().toString()
  181. }
  182. if (newRootAccount.isSome) {
  183. member.rootAccount = newRootAccount.unwrap().toString()
  184. }
  185. member.updatedAt = eventTime
  186. await db.save<Membership>(member)
  187. const memberAccountsUpdatedEvent = new MemberAccountsUpdatedEvent({
  188. createdAt: eventTime,
  189. updatedAt: eventTime,
  190. event: await createEvent(db, event_, EventType.MemberAccountsUpdated),
  191. member: member,
  192. newRootAccount: member.rootAccount,
  193. newControllerAccount: member.controllerAccount,
  194. })
  195. await db.save<MemberAccountsUpdatedEvent>(memberAccountsUpdatedEvent)
  196. }
  197. export async function members_MemberVerificationStatusUpdated(
  198. db: DatabaseManager,
  199. event_: SubstrateEvent
  200. ): Promise<void> {
  201. const [memberId, verificationStatus] = new Members.MemberVerificationStatusUpdatedEvent(event_).params
  202. const member = await getMemberById(db, memberId)
  203. const eventTime = new Date(event_.blockTimestamp)
  204. member.isVerified = verificationStatus.valueOf()
  205. member.updatedAt = eventTime
  206. await db.save<Membership>(member)
  207. const memberVerificationStatusUpdatedEvent = new MemberVerificationStatusUpdatedEvent({
  208. createdAt: eventTime,
  209. updatedAt: eventTime,
  210. event: await createEvent(db, event_, EventType.MemberVerificationStatusUpdated),
  211. member: member,
  212. isVerified: member.isVerified,
  213. })
  214. await db.save<MemberVerificationStatusUpdatedEvent>(memberVerificationStatusUpdatedEvent)
  215. }
  216. export async function members_InvitesTransferred(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  217. const [sourceMemberId, targetMemberId, numberOfInvites] = new Members.InvitesTransferredEvent(event_).params
  218. const sourceMember = await getMemberById(db, sourceMemberId)
  219. const targetMember = await getMemberById(db, targetMemberId)
  220. const eventTime = new Date(event_.blockTimestamp)
  221. sourceMember.inviteCount -= numberOfInvites.toNumber()
  222. sourceMember.updatedAt = eventTime
  223. targetMember.inviteCount += numberOfInvites.toNumber()
  224. targetMember.updatedAt = eventTime
  225. await db.save<Membership>(sourceMember)
  226. await db.save<Membership>(targetMember)
  227. const invitesTransferredEvent = new InvitesTransferredEvent({
  228. createdAt: eventTime,
  229. updatedAt: eventTime,
  230. event: await createEvent(db, event_, EventType.InvitesTransferred),
  231. sourceMember,
  232. targetMember,
  233. numberOfInvites: numberOfInvites.toNumber(),
  234. })
  235. await db.save<InvitesTransferredEvent>(invitesTransferredEvent)
  236. }
  237. export async function members_MemberInvited(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  238. const [memberId, inviteMembershipParameters] = new Members.MemberInvitedEvent(event_).params
  239. const eventTime = new Date(event_.blockTimestamp)
  240. const invitedMember = await newMembershipFromParams(
  241. db,
  242. event_,
  243. memberId,
  244. MembershipEntryMethod.INVITED,
  245. inviteMembershipParameters
  246. )
  247. // Decrease invite count of inviting member
  248. const invitingMember = await getMemberById(db, inviteMembershipParameters.inviting_member_id)
  249. invitingMember.inviteCount -= 1
  250. invitedMember.updatedAt = eventTime
  251. await db.save<Membership>(invitingMember)
  252. const memberInvitedEvent = new MemberInvitedEvent({
  253. createdAt: eventTime,
  254. updatedAt: eventTime,
  255. event: await createEvent(db, event_, EventType.MemberInvited),
  256. invitingMember,
  257. newMember: invitedMember,
  258. handle: invitedMember.handle,
  259. rootAccount: invitedMember.rootAccount,
  260. controllerAccount: invitedMember.controllerAccount,
  261. metadata: new MemberMetadata({
  262. ...invitedMember.metadata,
  263. id: undefined,
  264. }),
  265. })
  266. await db.save<MemberMetadata>(memberInvitedEvent.metadata)
  267. await db.save<MemberInvitedEvent>(memberInvitedEvent)
  268. }
  269. export async function members_StakingAccountAdded(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  270. const [accountId, memberId] = new Members.StakingAccountAddedEvent(event_).params
  271. const eventTime = new Date(event_.blockTimestamp)
  272. const stakingAccountAddedEvent = new StakingAccountAddedEvent({
  273. createdAt: eventTime,
  274. updatedAt: eventTime,
  275. event: await createEvent(db, event_, EventType.StakingAccountAddedEvent),
  276. member: new Membership({ id: memberId.toString() }),
  277. account: accountId.toString(),
  278. })
  279. await db.save<StakingAccountAddedEvent>(stakingAccountAddedEvent)
  280. }
  281. export async function members_StakingAccountConfirmed(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  282. const [accountId, memberId] = new Members.StakingAccountConfirmedEvent(event_).params
  283. const member = await getMemberById(db, memberId)
  284. const eventTime = new Date(event_.blockTimestamp)
  285. member.boundAccounts.push(accountId.toString())
  286. member.updatedAt = eventTime
  287. await db.save<Membership>(member)
  288. const stakingAccountConfirmedEvent = new StakingAccountConfirmedEvent({
  289. createdAt: eventTime,
  290. updatedAt: eventTime,
  291. event: await createEvent(db, event_, EventType.StakingAccountConfirmed),
  292. member,
  293. account: accountId.toString(),
  294. })
  295. await db.save<StakingAccountConfirmedEvent>(stakingAccountConfirmedEvent)
  296. }
  297. export async function members_StakingAccountRemoved(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  298. const [accountId, memberId] = new Members.StakingAccountRemovedEvent(event_).params
  299. const eventTime = new Date(event_.blockTimestamp)
  300. const member = await getMemberById(db, memberId)
  301. member.boundAccounts.splice(
  302. member.boundAccounts.findIndex((a) => a === accountId.toString()),
  303. 1
  304. )
  305. member.updatedAt = eventTime
  306. await db.save<Membership>(member)
  307. const stakingAccountRemovedEvent = new StakingAccountRemovedEvent({
  308. createdAt: eventTime,
  309. updatedAt: eventTime,
  310. event: await createEvent(db, event_, EventType.StakingAccountRemoved),
  311. member,
  312. account: accountId.toString(),
  313. })
  314. await db.save<StakingAccountRemovedEvent>(stakingAccountRemovedEvent)
  315. }
  316. export async function members_InitialInvitationCountUpdated(
  317. db: DatabaseManager,
  318. event_: SubstrateEvent
  319. ): Promise<void> {
  320. const [newDefaultInviteCount] = new Members.InitialInvitationCountUpdatedEvent(event_).params
  321. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  322. const eventTime = new Date(event_.blockTimestamp)
  323. membershipSystemSnapshot.defaultInviteCount = newDefaultInviteCount.toNumber()
  324. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  325. const initialInvitationCountUpdatedEvent = new InitialInvitationCountUpdatedEvent({
  326. createdAt: eventTime,
  327. updatedAt: eventTime,
  328. event: await createEvent(db, event_, EventType.InitialInvitationCountUpdated),
  329. newInitialInvitationCount: newDefaultInviteCount.toNumber(),
  330. })
  331. await db.save<InitialInvitationCountUpdatedEvent>(initialInvitationCountUpdatedEvent)
  332. }
  333. export async function members_MembershipPriceUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  334. const [newMembershipPrice] = new Members.MembershipPriceUpdatedEvent(event_).params
  335. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  336. const eventTime = new Date(event_.blockTimestamp)
  337. membershipSystemSnapshot.membershipPrice = newMembershipPrice
  338. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  339. const membershipPriceUpdatedEvent = new MembershipPriceUpdatedEvent({
  340. createdAt: eventTime,
  341. updatedAt: eventTime,
  342. event: await createEvent(db, event_, EventType.MembershipPriceUpdated),
  343. newPrice: newMembershipPrice,
  344. })
  345. await db.save<MembershipPriceUpdatedEvent>(membershipPriceUpdatedEvent)
  346. }
  347. export async function members_ReferralCutUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  348. const [newReferralCut] = new Members.ReferralCutUpdatedEvent(event_).params
  349. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  350. const eventTime = new Date(event_.blockTimestamp)
  351. membershipSystemSnapshot.referralCut = newReferralCut.toNumber()
  352. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  353. const referralCutUpdatedEvent = new ReferralCutUpdatedEvent({
  354. createdAt: eventTime,
  355. updatedAt: eventTime,
  356. event: await createEvent(db, event_, EventType.ReferralCutUpdated),
  357. newValue: newReferralCut.toNumber(),
  358. })
  359. await db.save<ReferralCutUpdatedEvent>(referralCutUpdatedEvent)
  360. }
  361. export async function members_InitialInvitationBalanceUpdated(
  362. db: DatabaseManager,
  363. event_: SubstrateEvent
  364. ): Promise<void> {
  365. const [newInvitedInitialBalance] = new Members.InitialInvitationBalanceUpdatedEvent(event_).params
  366. const membershipSystemSnapshot = await getOrCreateMembershipSnapshot(db, event_)
  367. const eventTime = new Date(event_.blockTimestamp)
  368. membershipSystemSnapshot.invitedInitialBalance = newInvitedInitialBalance
  369. await db.save<MembershipSystemSnapshot>(membershipSystemSnapshot)
  370. const initialInvitationBalanceUpdatedEvent = new InitialInvitationBalanceUpdatedEvent({
  371. createdAt: eventTime,
  372. updatedAt: eventTime,
  373. event: await createEvent(db, event_, EventType.InitialInvitationBalanceUpdated),
  374. newInitialBalance: newInvitedInitialBalance,
  375. })
  376. await db.save<InitialInvitationBalanceUpdatedEvent>(initialInvitationBalanceUpdatedEvent)
  377. }
  378. export async function members_LeaderInvitationQuotaUpdated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  379. const [newQuota] = new Members.LeaderInvitationQuotaUpdatedEvent(event_).params
  380. const eventTime = new Date(event_.blockTimestamp)
  381. const leaderInvitationQuotaUpdatedEvent = new LeaderInvitationQuotaUpdatedEvent({
  382. createdAt: eventTime,
  383. updatedAt: eventTime,
  384. event: await createEvent(db, event_, EventType.LeaderInvitationQuotaUpdated),
  385. newInvitationQuota: newQuota.toNumber(),
  386. })
  387. await db.save<LeaderInvitationQuotaUpdatedEvent>(leaderInvitationQuotaUpdatedEvent)
  388. }