workingGroups.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  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 { StorageWorkingGroup as WorkingGroups } from './generated/types'
  7. import {
  8. AddUpcomingOpening,
  9. ApplicationMetadata,
  10. OpeningMetadata,
  11. RemoveUpcomingOpening,
  12. SetGroupMetadata,
  13. WorkingGroupMetadataAction,
  14. } from '@joystream/metadata-protobuf'
  15. import { Bytes } from '@polkadot/types'
  16. import { createEvent, deserializeMetadata, getOrCreateBlock } from './common'
  17. import BN from 'bn.js'
  18. import {
  19. WorkingGroupOpening,
  20. OpeningAddedEvent,
  21. WorkingGroup,
  22. WorkingGroupOpeningMetadata,
  23. ApplicationFormQuestion,
  24. ApplicationFormQuestionType,
  25. OpeningStatusOpen,
  26. WorkingGroupOpeningType,
  27. EventType,
  28. WorkingGroupApplication,
  29. ApplicationFormQuestionAnswer,
  30. AppliedOnOpeningEvent,
  31. Membership,
  32. ApplicationStatusPending,
  33. ApplicationStatusAccepted,
  34. ApplicationStatusRejected,
  35. Worker,
  36. WorkerStatusActive,
  37. OpeningFilledEvent,
  38. OpeningStatusFilled,
  39. // LeaderSetEvent,
  40. OpeningCanceledEvent,
  41. OpeningStatusCancelled,
  42. ApplicationStatusCancelled,
  43. ApplicationWithdrawnEvent,
  44. ApplicationStatusWithdrawn,
  45. UpcomingWorkingGroupOpening,
  46. StatusTextChangedEvent,
  47. WorkingGroupMetadata,
  48. WorkingGroupMetadataSet,
  49. UpcomingOpeningRemoved,
  50. InvalidActionMetadata,
  51. WorkingGroupMetadataActionResult,
  52. UpcomingOpeningAdded,
  53. WorkerRoleAccountUpdatedEvent,
  54. WorkerRewardAccountUpdatedEvent,
  55. StakeIncreasedEvent,
  56. RewardPaidEvent,
  57. RewardPaymentType,
  58. NewMissedRewardLevelReached,
  59. } from 'query-node/dist/model'
  60. import { createType } from '@joystream/types'
  61. import _ from 'lodash'
  62. // Shortcuts
  63. type InputTypeMap = OpeningMetadata.ApplicationFormQuestion.InputTypeMap
  64. const InputType = OpeningMetadata.ApplicationFormQuestion.InputType
  65. // Reusable functions
  66. async function getWorkingGroup(
  67. db: DatabaseManager,
  68. event_: SubstrateEvent,
  69. relations: string[] = []
  70. ): Promise<WorkingGroup> {
  71. const [groupName] = event_.name.split('.')
  72. const group = await db.get(WorkingGroup, { where: { name: groupName }, relations })
  73. if (!group) {
  74. throw new Error(`Working group ${groupName} not found!`)
  75. }
  76. return group
  77. }
  78. async function getOpening(
  79. db: DatabaseManager,
  80. openingDbId: string,
  81. relations: string[] = []
  82. ): Promise<WorkingGroupOpening> {
  83. const opening = await db.get(WorkingGroupOpening, { where: { id: openingDbId }, relations })
  84. if (!opening) {
  85. throw new Error(`Opening not found by id ${openingDbId}`)
  86. }
  87. return opening
  88. }
  89. async function getApplication(db: DatabaseManager, applicationDbId: string): Promise<WorkingGroupApplication> {
  90. const application = await db.get(WorkingGroupApplication, { where: { id: applicationDbId } })
  91. if (!application) {
  92. throw new Error(`Application not found by id ${applicationDbId}`)
  93. }
  94. return application
  95. }
  96. async function getWorker(db: DatabaseManager, workerDbId: string): Promise<Worker> {
  97. const worker = await db.get(Worker, { where: { id: workerDbId } })
  98. if (!worker) {
  99. throw new Error(`Worker not found by id ${workerDbId}`)
  100. }
  101. return worker
  102. }
  103. async function getApplicationFormQuestions(
  104. db: DatabaseManager,
  105. openingDbId: string
  106. ): Promise<ApplicationFormQuestion[]> {
  107. const openingWithQuestions = await getOpening(db, openingDbId, ['metadata', 'metadata.applicationFormQuestions'])
  108. if (!openingWithQuestions) {
  109. throw new Error(`Opening not found by id: ${openingDbId}`)
  110. }
  111. if (!openingWithQuestions.metadata.applicationFormQuestions) {
  112. throw new Error(`Application form questions not found for opening: ${openingDbId}`)
  113. }
  114. return openingWithQuestions.metadata.applicationFormQuestions
  115. }
  116. function parseQuestionInputType(type: InputTypeMap[keyof InputTypeMap]) {
  117. if (type === InputType.TEXTAREA) {
  118. return ApplicationFormQuestionType.TEXTAREA
  119. }
  120. return ApplicationFormQuestionType.TEXT
  121. }
  122. function getDefaultOpeningMetadata(group: WorkingGroup, openingType: WorkingGroupOpeningType): OpeningMetadata {
  123. const metadata = new OpeningMetadata()
  124. metadata.setShortDescription(
  125. `${_.startCase(group.name)} ${openingType === WorkingGroupOpeningType.REGULAR ? 'worker' : 'leader'} opening`
  126. )
  127. metadata.setDescription(
  128. `Apply to this opening in order to be considered for ${_.startCase(group.name)} ${
  129. openingType === WorkingGroupOpeningType.REGULAR ? 'worker' : 'leader'
  130. } role!`
  131. )
  132. metadata.setApplicationDetails(`- Fill the application form`)
  133. const applicationFormQuestion = new OpeningMetadata.ApplicationFormQuestion()
  134. applicationFormQuestion.setQuestion('What makes you a good candidate?')
  135. applicationFormQuestion.setType(OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA)
  136. metadata.addApplicationFormQuestions(applicationFormQuestion)
  137. return metadata
  138. }
  139. async function createOpeningMeta(
  140. db: DatabaseManager,
  141. event_: SubstrateEvent,
  142. group: WorkingGroup,
  143. openingType: WorkingGroupOpeningType,
  144. originalMeta: Bytes | OpeningMetadata
  145. ): Promise<WorkingGroupOpeningMetadata> {
  146. let originallyValid: boolean
  147. let metadata: OpeningMetadata
  148. if (originalMeta instanceof Bytes) {
  149. const deserializedMetadata = await deserializeMetadata(OpeningMetadata, originalMeta)
  150. metadata = deserializedMetadata || (await getDefaultOpeningMetadata(group, openingType))
  151. originallyValid = !!deserializedMetadata
  152. } else {
  153. metadata = originalMeta
  154. originallyValid = true
  155. }
  156. const eventTime = new Date(event_.blockTimestamp.toNumber())
  157. const {
  158. applicationFormQuestionsList,
  159. applicationDetails,
  160. description,
  161. expectedEndingTimestamp,
  162. hiringLimit,
  163. shortDescription,
  164. } = metadata.toObject()
  165. const openingMetadata = new WorkingGroupOpeningMetadata({
  166. createdAt: eventTime,
  167. updatedAt: eventTime,
  168. originallyValid,
  169. applicationDetails,
  170. description,
  171. shortDescription,
  172. hiringLimit,
  173. expectedEnding: new Date(expectedEndingTimestamp!),
  174. applicationFormQuestions: [],
  175. })
  176. await db.save<WorkingGroupOpeningMetadata>(openingMetadata)
  177. await Promise.all(
  178. applicationFormQuestionsList.map(async ({ question, type }, index) => {
  179. const applicationFormQuestion = new ApplicationFormQuestion({
  180. createdAt: eventTime,
  181. updatedAt: eventTime,
  182. question,
  183. type: parseQuestionInputType(type!),
  184. index,
  185. openingMetadata,
  186. })
  187. await db.save<ApplicationFormQuestion>(applicationFormQuestion)
  188. return applicationFormQuestion
  189. })
  190. )
  191. return openingMetadata
  192. }
  193. async function createApplicationQuestionAnswers(
  194. db: DatabaseManager,
  195. application: WorkingGroupApplication,
  196. metadataBytes: Bytes
  197. ) {
  198. const metadata = deserializeMetadata(ApplicationMetadata, metadataBytes)
  199. if (!metadata) {
  200. return
  201. }
  202. const questions = await getApplicationFormQuestions(db, application.opening.id)
  203. const { answersList } = metadata.toObject()
  204. await Promise.all(
  205. answersList.slice(0, questions.length).map(async (answer, index) => {
  206. const applicationFormQuestionAnswer = new ApplicationFormQuestionAnswer({
  207. createdAt: application.createdAt,
  208. updatedAt: application.updatedAt,
  209. application,
  210. question: questions[index],
  211. answer,
  212. })
  213. await db.save<ApplicationFormQuestionAnswer>(applicationFormQuestionAnswer)
  214. return applicationFormQuestionAnswer
  215. })
  216. )
  217. }
  218. async function handleAddUpcomingOpeningAction(
  219. db: DatabaseManager,
  220. event_: SubstrateEvent,
  221. statusChangedEvent: StatusTextChangedEvent,
  222. action: AddUpcomingOpening
  223. ): Promise<UpcomingOpeningAdded> {
  224. const upcomingOpeningMeta = action.getMetadata().toObject()
  225. const group = await getWorkingGroup(db, event_)
  226. const eventTime = new Date(event_.blockTimestamp.toNumber())
  227. const openingMeta = await createOpeningMeta(
  228. db,
  229. event_,
  230. group,
  231. WorkingGroupOpeningType.REGULAR,
  232. action.getMetadata().getMetadata()
  233. )
  234. const upcomingOpening = new UpcomingWorkingGroupOpening({
  235. createdAt: eventTime,
  236. updatedAt: eventTime,
  237. metadata: openingMeta,
  238. group,
  239. rewardPerBlock: new BN(upcomingOpeningMeta.rewardPerBlock!),
  240. expectedStart: new Date(upcomingOpeningMeta.expectedStart!),
  241. stakeAmount: new BN(upcomingOpeningMeta.minApplicationStake!),
  242. createdInEvent: statusChangedEvent,
  243. createdAtBlock: await getOrCreateBlock(db, event_),
  244. })
  245. await db.save<UpcomingWorkingGroupOpening>(upcomingOpening)
  246. const result = new UpcomingOpeningAdded()
  247. result.upcomingOpeningId = upcomingOpening.id
  248. return result
  249. }
  250. async function handleRemoveUpcomingOpeningAction(
  251. db: DatabaseManager,
  252. action: RemoveUpcomingOpening
  253. ): Promise<UpcomingOpeningRemoved | InvalidActionMetadata> {
  254. const id = action.getId()
  255. const upcomingOpening = await db.get(UpcomingWorkingGroupOpening, { where: { id } })
  256. let result: UpcomingOpeningRemoved | InvalidActionMetadata
  257. if (upcomingOpening) {
  258. result = new UpcomingOpeningRemoved()
  259. result.upcomingOpeningId = upcomingOpening.id
  260. await db.remove<UpcomingWorkingGroupOpening>(upcomingOpening)
  261. } else {
  262. const error = `Cannot remove upcoming opening: Entity by id ${id} not found!`
  263. console.error(error)
  264. result = new InvalidActionMetadata()
  265. result.reason = error
  266. }
  267. return result
  268. }
  269. async function handleSetWorkingGroupMetadataAction(
  270. db: DatabaseManager,
  271. event_: SubstrateEvent,
  272. statusChangedEvent: StatusTextChangedEvent,
  273. action: SetGroupMetadata
  274. ): Promise<WorkingGroupMetadataSet> {
  275. const { newMetadata } = action.toObject()
  276. const group = await getWorkingGroup(db, event_, ['metadata'])
  277. const groupMetadata = group.metadata
  278. const eventTime = new Date(event_.blockTimestamp.toNumber())
  279. const newGroupMetadata = new WorkingGroupMetadata({
  280. ..._.merge(groupMetadata, newMetadata),
  281. id: undefined,
  282. createdAt: eventTime,
  283. updatedAt: eventTime,
  284. setAtBlock: await getOrCreateBlock(db, event_),
  285. setInEvent: statusChangedEvent,
  286. group,
  287. })
  288. await db.save<WorkingGroupMetadata>(newGroupMetadata)
  289. group.metadata = newGroupMetadata
  290. group.updatedAt = eventTime
  291. await db.save<WorkingGroup>(group)
  292. const result = new WorkingGroupMetadataSet()
  293. result.metadataId = newGroupMetadata.id
  294. return result
  295. }
  296. async function handleWorkingGroupMetadataAction(
  297. db: DatabaseManager,
  298. event_: SubstrateEvent,
  299. statusChangedEvent: StatusTextChangedEvent,
  300. action: WorkingGroupMetadataAction
  301. ): Promise<typeof WorkingGroupMetadataActionResult> {
  302. switch (action.getActionCase()) {
  303. case WorkingGroupMetadataAction.ActionCase.ADD_UPCOMING_OPENING: {
  304. return handleAddUpcomingOpeningAction(db, event_, statusChangedEvent, action.getAddUpcomingOpening()!)
  305. }
  306. case WorkingGroupMetadataAction.ActionCase.REMOVE_UPCOMING_OPENING: {
  307. return handleRemoveUpcomingOpeningAction(db, action.getRemoveUpcomingOpening()!)
  308. }
  309. case WorkingGroupMetadataAction.ActionCase.SET_GROUP_METADATA: {
  310. return handleSetWorkingGroupMetadataAction(db, event_, statusChangedEvent, action.getSetGroupMetadata()!)
  311. }
  312. }
  313. const result = new InvalidActionMetadata()
  314. result.reason = 'Unexpected action type'
  315. return result
  316. }
  317. // Mapping functions
  318. export async function workingGroups_OpeningAdded(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  319. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  320. const {
  321. balance: rewardPerBlock,
  322. bytes: metadataBytes,
  323. openingId: openingRuntimeId,
  324. openingType,
  325. stakePolicy,
  326. } = new WorkingGroups.OpeningAddedEvent(event_).data
  327. const group = await getWorkingGroup(db, event_)
  328. const eventTime = new Date(event_.blockTimestamp.toNumber())
  329. const opening = new WorkingGroupOpening({
  330. createdAt: eventTime,
  331. updatedAt: eventTime,
  332. createdAtBlock: await getOrCreateBlock(db, event_),
  333. id: `${group.name}-${openingRuntimeId.toString()}`,
  334. runtimeId: openingRuntimeId.toNumber(),
  335. applications: [],
  336. group,
  337. rewardPerBlock: rewardPerBlock.unwrapOr(new BN(0)),
  338. stakeAmount: stakePolicy.stake_amount,
  339. unstakingPeriod: stakePolicy.leaving_unstaking_period.toNumber(),
  340. status: new OpeningStatusOpen(),
  341. type: openingType.isLeader ? WorkingGroupOpeningType.LEADER : WorkingGroupOpeningType.REGULAR,
  342. })
  343. const metadata = await createOpeningMeta(db, event_, group, opening.type, metadataBytes)
  344. opening.metadata = metadata
  345. await db.save<WorkingGroupOpening>(opening)
  346. const event = await createEvent(db, event_, EventType.OpeningAdded)
  347. const openingAddedEvent = new OpeningAddedEvent({
  348. createdAt: eventTime,
  349. updatedAt: eventTime,
  350. event,
  351. group,
  352. opening,
  353. })
  354. await db.save<OpeningAddedEvent>(openingAddedEvent)
  355. }
  356. export async function workingGroups_AppliedOnOpening(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  357. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  358. const eventTime = new Date(event_.blockTimestamp.toNumber())
  359. const {
  360. applicationId: applicationRuntimeId,
  361. applyOnOpeningParameters: {
  362. opening_id: openingRuntimeId,
  363. description: metadataBytes,
  364. member_id: memberId,
  365. reward_account_id: rewardAccount,
  366. role_account_id: roleAccout,
  367. stake_parameters: { stake, staking_account_id: stakingAccount },
  368. },
  369. } = new WorkingGroups.AppliedOnOpeningEvent(event_).data
  370. const group = await getWorkingGroup(db, event_)
  371. const openingDbId = `${group.name}-${openingRuntimeId.toString()}`
  372. const application = new WorkingGroupApplication({
  373. createdAt: eventTime,
  374. updatedAt: eventTime,
  375. createdAtBlock: await getOrCreateBlock(db, event_),
  376. id: `${group.name}-${applicationRuntimeId.toString()}`,
  377. runtimeId: applicationRuntimeId.toNumber(),
  378. opening: new WorkingGroupOpening({ id: openingDbId }),
  379. applicant: new Membership({ id: memberId.toString() }),
  380. rewardAccount: rewardAccount.toString(),
  381. roleAccount: roleAccout.toString(),
  382. stakingAccount: stakingAccount.toString(),
  383. status: new ApplicationStatusPending(),
  384. answers: [],
  385. stake,
  386. })
  387. await db.save<WorkingGroupApplication>(application)
  388. await createApplicationQuestionAnswers(db, application, metadataBytes)
  389. const event = await createEvent(db, event_, EventType.AppliedOnOpening)
  390. const appliedOnOpeningEvent = new AppliedOnOpeningEvent({
  391. createdAt: eventTime,
  392. updatedAt: eventTime,
  393. event,
  394. group,
  395. opening: new WorkingGroupOpening({ id: openingDbId }),
  396. application,
  397. })
  398. await db.save<AppliedOnOpeningEvent>(appliedOnOpeningEvent)
  399. }
  400. export async function workingGroups_OpeningFilled(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  401. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  402. const eventTime = new Date(event_.blockTimestamp.toNumber())
  403. const {
  404. openingId: openingRuntimeId,
  405. applicationId: applicationIdsSet,
  406. applicationIdToWorkerIdMap,
  407. } = new WorkingGroups.OpeningFilledEvent(event_).data
  408. const group = await getWorkingGroup(db, event_)
  409. const opening = await getOpening(db, `${group.name}-${openingRuntimeId.toString()}`, [
  410. 'applications',
  411. 'applications.applicant',
  412. ])
  413. const acceptedApplicationIds = createType('Vec<ApplicationId>', applicationIdsSet.toHex() as any)
  414. // Save the event
  415. const event = await createEvent(db, event_, EventType.OpeningFilled)
  416. const openingFilledEvent = new OpeningFilledEvent({
  417. createdAt: eventTime,
  418. updatedAt: eventTime,
  419. event,
  420. group,
  421. opening,
  422. })
  423. await db.save<OpeningFilledEvent>(openingFilledEvent)
  424. const hiredWorkers: Worker[] = []
  425. // Update applications and create new workers
  426. await Promise.all(
  427. (opening.applications || [])
  428. // Skip withdrawn applications
  429. .filter((application) => application.status.isTypeOf !== 'ApplicationStatusWithdrawn')
  430. .map(async (application) => {
  431. const isAccepted = acceptedApplicationIds.some((runtimeId) => runtimeId.toNumber() === application.runtimeId)
  432. const applicationStatus = isAccepted ? new ApplicationStatusAccepted() : new ApplicationStatusRejected()
  433. applicationStatus.openingFilledEventId = openingFilledEvent.id
  434. application.status = applicationStatus
  435. application.updatedAt = eventTime
  436. if (isAccepted) {
  437. // Cannot use "applicationIdToWorkerIdMap.get" here,
  438. // it only works if the passed instance is identical to BTreeMap key instance (=== instead of .eq)
  439. const [, workerRuntimeId] =
  440. Array.from(applicationIdToWorkerIdMap.entries()).find(
  441. ([applicationRuntimeId]) => applicationRuntimeId.toNumber() === application.runtimeId
  442. ) || []
  443. if (!workerRuntimeId) {
  444. throw new Error(
  445. `Fatal: No worker id found by accepted application id ${application.id} when handling OpeningFilled event!`
  446. )
  447. }
  448. const worker = new Worker({
  449. createdAt: eventTime,
  450. updatedAt: eventTime,
  451. id: `${group.name}-${workerRuntimeId.toString()}`,
  452. runtimeId: workerRuntimeId.toNumber(),
  453. hiredAtBlock: await getOrCreateBlock(db, event_),
  454. hiredAtTime: new Date(event_.blockTimestamp.toNumber()),
  455. application,
  456. group,
  457. isLead: opening.type === WorkingGroupOpeningType.LEADER,
  458. membership: application.applicant,
  459. stake: application.stake,
  460. roleAccount: application.roleAccount,
  461. rewardAccount: application.rewardAccount,
  462. stakeAccount: application.stakingAccount,
  463. payouts: [],
  464. status: new WorkerStatusActive(),
  465. entry: openingFilledEvent,
  466. })
  467. await db.save<Worker>(worker)
  468. hiredWorkers.push(worker)
  469. }
  470. await db.save<WorkingGroupApplication>(application)
  471. })
  472. )
  473. // Set opening status
  474. const openingFilled = new OpeningStatusFilled()
  475. openingFilled.openingFilledEventId = openingFilledEvent.id
  476. opening.status = openingFilled
  477. opening.updatedAt = eventTime
  478. await db.save<WorkingGroupOpening>(opening)
  479. // Update working group if necessary
  480. if (opening.type === WorkingGroupOpeningType.LEADER && hiredWorkers.length) {
  481. group.leader = hiredWorkers[0]
  482. group.updatedAt = eventTime
  483. await db.save<WorkingGroup>(group)
  484. }
  485. }
  486. // FIXME: Currently this event cannot be handled directly, because the worker does not yet exist at the time when it is emitted
  487. // export async function workingGroups_LeaderSet(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  488. // event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  489. // const { workerId: workerRuntimeId } = new WorkingGroups.LeaderSetEvent(event_).data
  490. // const group = await getWorkingGroup(db, event_)
  491. // const workerDbId = `${group.name}-${workerRuntimeId.toString()}`
  492. // const worker = new Worker({ id: workerDbId })
  493. // const eventTime = new Date(event_.blockTimestamp.toNumber())
  494. // // Create and save event
  495. // const event = createEvent(event_, EventType.LeaderSet)
  496. // const leaderSetEvent = new LeaderSetEvent({
  497. // createdAt: eventTime,
  498. // updatedAt: eventTime,
  499. // event,
  500. // group,
  501. // worker,
  502. // })
  503. // await db.save<Event>(event)
  504. // await db.save<LeaderSetEvent>(leaderSetEvent)
  505. // }
  506. export async function workingGroups_OpeningCanceled(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  507. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  508. const { openingId: openingRuntimeId } = new WorkingGroups.OpeningCanceledEvent(event_).data
  509. const group = await getWorkingGroup(db, event_)
  510. const opening = await getOpening(db, `${group.name}-${openingRuntimeId.toString()}`, ['applications'])
  511. const eventTime = new Date(event_.blockTimestamp.toNumber())
  512. // Create and save event
  513. const event = await createEvent(db, event_, EventType.OpeningCanceled)
  514. const openingCanceledEvent = new OpeningCanceledEvent({
  515. createdAt: eventTime,
  516. updatedAt: eventTime,
  517. event,
  518. group,
  519. opening,
  520. })
  521. await db.save<OpeningCanceledEvent>(openingCanceledEvent)
  522. // Set opening status
  523. const openingCancelled = new OpeningStatusCancelled()
  524. openingCancelled.openingCancelledEventId = openingCanceledEvent.id
  525. opening.status = openingCancelled
  526. opening.updatedAt = eventTime
  527. await db.save<WorkingGroupOpening>(opening)
  528. // Set applications status
  529. const applicationCancelled = new ApplicationStatusCancelled()
  530. applicationCancelled.openingCancelledEventId = openingCanceledEvent.id
  531. await Promise.all(
  532. (opening.applications || [])
  533. // Skip withdrawn applications
  534. .filter((application) => application.status.isTypeOf !== 'ApplicationStatusWithdrawn')
  535. .map(async (application) => {
  536. application.status = applicationCancelled
  537. application.updatedAt = eventTime
  538. await db.save<WorkingGroupApplication>(application)
  539. })
  540. )
  541. }
  542. export async function workingGroups_ApplicationWithdrawn(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  543. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  544. const { applicationId: applicationRuntimeId } = new WorkingGroups.ApplicationWithdrawnEvent(event_).data
  545. const group = await getWorkingGroup(db, event_)
  546. const application = await getApplication(db, `${group.name}-${applicationRuntimeId.toString()}`)
  547. const eventTime = new Date(event_.blockTimestamp.toNumber())
  548. // Create and save event
  549. const event = await createEvent(db, event_, EventType.ApplicationWithdrawn)
  550. const applicationWithdrawnEvent = new ApplicationWithdrawnEvent({
  551. createdAt: eventTime,
  552. updatedAt: eventTime,
  553. event,
  554. group,
  555. application,
  556. })
  557. await db.save<ApplicationWithdrawnEvent>(applicationWithdrawnEvent)
  558. // Set application status
  559. const statusWithdrawn = new ApplicationStatusWithdrawn()
  560. statusWithdrawn.applicationWithdrawnEventId = applicationWithdrawnEvent.id
  561. application.status = statusWithdrawn
  562. application.updatedAt = eventTime
  563. await db.save<WorkingGroupApplication>(application)
  564. }
  565. export async function workingGroups_StatusTextChanged(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  566. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  567. const { optBytes } = new WorkingGroups.StatusTextChangedEvent(event_).data
  568. const group = await getWorkingGroup(db, event_)
  569. const eventTime = new Date(event_.blockTimestamp.toNumber())
  570. // Since result cannot be empty at this point, but we already need to have an existing StatusTextChangedEvent
  571. // in order to be able to create UpcomingOpening.createdInEvent relation, we use a temporary "mock" result
  572. const mockResult = new InvalidActionMetadata()
  573. mockResult.reason = 'Metadata not yet processed'
  574. const statusTextChangedEvent = new StatusTextChangedEvent({
  575. createdAt: eventTime,
  576. updatedAt: eventTime,
  577. group,
  578. event: await createEvent(db, event_, EventType.StatusTextChanged),
  579. metadata: optBytes.isSome ? optBytes.unwrap().toString() : undefined,
  580. result: mockResult,
  581. })
  582. await db.save<StatusTextChangedEvent>(statusTextChangedEvent)
  583. let result: typeof WorkingGroupMetadataActionResult
  584. if (optBytes.isSome) {
  585. const metadata = deserializeMetadata(WorkingGroupMetadataAction, optBytes.unwrap())
  586. if (metadata) {
  587. result = await handleWorkingGroupMetadataAction(db, event_, statusTextChangedEvent, metadata)
  588. } else {
  589. result = new InvalidActionMetadata()
  590. result.reason = 'Invalid metadata: Cannot deserialize metadata binary'
  591. }
  592. } else {
  593. const error = 'No encoded metadata was provided'
  594. console.error(`StatusTextChanged event: ${error}`)
  595. result = new InvalidActionMetadata()
  596. result.reason = error
  597. }
  598. // Now we can set the "real" result
  599. statusTextChangedEvent.result = result
  600. await db.save<StatusTextChangedEvent>(statusTextChangedEvent)
  601. }
  602. export async function workingGroups_WorkerRoleAccountUpdated(
  603. db: DatabaseManager,
  604. event_: SubstrateEvent
  605. ): Promise<void> {
  606. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  607. const { workerId, accountId } = new WorkingGroups.WorkerRoleAccountUpdatedEvent(event_).data
  608. const group = await getWorkingGroup(db, event_)
  609. const worker = await getWorker(db, `${group.name}-${workerId.toString()}`)
  610. const eventTime = new Date(event_.blockTimestamp.toNumber())
  611. const workerRoleAccountUpdatedEvent = new WorkerRoleAccountUpdatedEvent({
  612. createdAt: eventTime,
  613. updatedAt: eventTime,
  614. group,
  615. event: await createEvent(db, event_, EventType.WorkerRoleAccountUpdated),
  616. worker,
  617. newRoleAccount: accountId.toString(),
  618. })
  619. await db.save<WorkerRoleAccountUpdatedEvent>(workerRoleAccountUpdatedEvent)
  620. worker.roleAccount = accountId.toString()
  621. worker.updatedAt = eventTime
  622. await db.save<Worker>(worker)
  623. }
  624. export async function workingGroups_WorkerRewardAccountUpdated(
  625. db: DatabaseManager,
  626. event_: SubstrateEvent
  627. ): Promise<void> {
  628. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  629. const { workerId, accountId } = new WorkingGroups.WorkerRewardAccountUpdatedEvent(event_).data
  630. const group = await getWorkingGroup(db, event_)
  631. const worker = await getWorker(db, `${group.name}-${workerId.toString()}`)
  632. const eventTime = new Date(event_.blockTimestamp.toNumber())
  633. const workerRewardAccountUpdatedEvent = new WorkerRewardAccountUpdatedEvent({
  634. createdAt: eventTime,
  635. updatedAt: eventTime,
  636. group,
  637. event: await createEvent(db, event_, EventType.WorkerRewardAccountUpdated),
  638. worker,
  639. newRewardAccount: accountId.toString(),
  640. })
  641. await db.save<WorkerRoleAccountUpdatedEvent>(workerRewardAccountUpdatedEvent)
  642. worker.rewardAccount = accountId.toString()
  643. worker.updatedAt = eventTime
  644. await db.save<Worker>(worker)
  645. }
  646. export async function workingGroups_StakeIncreased(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  647. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  648. const { workerId, balance: increaseAmount } = new WorkingGroups.StakeIncreasedEvent(event_).data
  649. const group = await getWorkingGroup(db, event_)
  650. const worker = await getWorker(db, `${group.name}-${workerId.toString()}`)
  651. const eventTime = new Date(event_.blockTimestamp.toNumber())
  652. const stakeIncreasedEvent = new StakeIncreasedEvent({
  653. createdAt: eventTime,
  654. updatedAt: eventTime,
  655. group,
  656. event: await createEvent(db, event_, EventType.StakeIncreased),
  657. worker,
  658. amount: increaseAmount,
  659. })
  660. await db.save<StakeIncreasedEvent>(stakeIncreasedEvent)
  661. worker.stake = worker.stake.add(increaseAmount)
  662. worker.updatedAt = eventTime
  663. await db.save<Worker>(worker)
  664. }
  665. export async function workingGroups_RewardPaid(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  666. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  667. const {
  668. workerId,
  669. accountId: rewardAccountId,
  670. balance: amount,
  671. rewardPaymentType,
  672. } = new WorkingGroups.RewardPaidEvent(event_).data
  673. const group = await getWorkingGroup(db, event_)
  674. const worker = await getWorker(db, `${group.name}-${workerId.toString()}`)
  675. const eventTime = new Date(event_.blockTimestamp.toNumber())
  676. const rewardPaidEvent = new RewardPaidEvent({
  677. createdAt: eventTime,
  678. updatedAt: eventTime,
  679. group,
  680. event: await createEvent(db, event_, EventType.RewardPaid),
  681. worker,
  682. amount,
  683. rewardAccount: rewardAccountId.toString(),
  684. type: rewardPaymentType.isRegularReward ? RewardPaymentType.REGULAR : RewardPaymentType.MISSED,
  685. })
  686. await db.save<RewardPaidEvent>(rewardPaidEvent)
  687. }
  688. export async function workingGroups_NewMissedRewardLevelReached(
  689. db: DatabaseManager,
  690. event_: SubstrateEvent
  691. ): Promise<void> {
  692. event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
  693. const { workerId, balance: newMissedRewardAmountOpt } = new WorkingGroups.NewMissedRewardLevelReachedEvent(
  694. event_
  695. ).data
  696. const group = await getWorkingGroup(db, event_)
  697. const worker = await getWorker(db, `${group.name}-${workerId.toString()}`)
  698. const eventTime = new Date(event_.blockTimestamp.toNumber())
  699. const newMissedRewardLevelReachedEvent = new NewMissedRewardLevelReached({
  700. createdAt: eventTime,
  701. updatedAt: eventTime,
  702. group,
  703. event: await createEvent(db, event_, EventType.NewMissedRewardLevelReached),
  704. worker,
  705. newMissedRewardAmount: newMissedRewardAmountOpt.unwrapOr(new BN(0)),
  706. })
  707. await db.save<NewMissedRewardLevelReached>(newMissedRewardLevelReachedEvent)
  708. }
  709. export async function workingGroups_LeaderUnset(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  710. // TBD
  711. }
  712. export async function workingGroups_WorkerExited(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  713. // TBD
  714. }
  715. export async function workingGroups_TerminatedWorker(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  716. // TBD
  717. }
  718. export async function workingGroups_TerminatedLeader(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  719. // TBD
  720. }
  721. export async function workingGroups_StakeSlashed(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  722. // TBD
  723. }
  724. export async function workingGroups_StakeDecreased(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  725. // TBD
  726. }
  727. export async function workingGroups_BudgetSet(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  728. // TBD
  729. }
  730. export async function workingGroups_WorkerRewardAmountUpdated(
  731. db: DatabaseManager,
  732. event_: SubstrateEvent
  733. ): Promise<void> {
  734. // TBD
  735. }
  736. export async function workingGroups_BudgetSpending(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
  737. // TBD
  738. }