membership.ts 18 KB

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