storage.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. eslint-disable @typescript-eslint/naming-convention
  3. */
  4. import { EventContext, StoreContext, DatabaseManager } from '@joystream/hydra-common'
  5. import { FindConditions, In, Raw } from 'typeorm'
  6. import {
  7. createDataObject,
  8. getWorker,
  9. getWorkingGroupModuleName,
  10. inconsistentState,
  11. logger,
  12. unexpectedData,
  13. } from './common'
  14. import { DataDirectory } from './generated/types'
  15. import { ContentId, StorageObjectOwner } from '@joystream/types/augment'
  16. import { ContentId as Custom_ContentId } from '@joystream/types/storage'
  17. import { registry } from '@joystream/types'
  18. import {
  19. Channel,
  20. Video,
  21. DataObject,
  22. DataObjectOwner,
  23. DataObjectOwnerMember,
  24. DataObjectOwnerChannel,
  25. DataObjectOwnerDao,
  26. DataObjectOwnerCouncil,
  27. DataObjectOwnerWorkingGroup,
  28. LiaisonJudgement,
  29. AssetJoystreamStorage,
  30. } from 'query-node/dist/model'
  31. export async function dataDirectory_ContentAdded(ctx: EventContext & StoreContext): Promise<void> {
  32. const { event } = ctx
  33. // read event data
  34. const [contentParameters, storageObjectOwner] = new DataDirectory.ContentAddedEvent(event).params
  35. // save all content objects
  36. for (const parameters of contentParameters) {
  37. const owner = convertStorageObjectOwner(storageObjectOwner)
  38. await createDataObject(ctx, parameters, owner)
  39. }
  40. // emit log event
  41. logger.info('Storage content has beed added', {
  42. ids: contentParameters.map((item) => encodeContentId(item.content_id)),
  43. })
  44. }
  45. export async function dataDirectory_ContentRemoved({ store, event }: EventContext & StoreContext): Promise<void> {
  46. // read event data
  47. const [contentIds] = new DataDirectory.ContentRemovedEvent(event).params
  48. // load assets
  49. const dataObjects = await store.getMany(DataObject, {
  50. where: {
  51. joystreamContentId: In(contentIds.map((item) => encodeContentId(item))),
  52. } as FindConditions<DataObject>,
  53. })
  54. // store dataObject ids before they are deleted (for logging purposes)
  55. const dataObjectIds = dataObjects.map((item) => item.id)
  56. // remove assets from database
  57. for (const item of dataObjects) {
  58. // ensure dataObject is nowhere used to prevent db constraint error
  59. await unsetDataObjectRelations(store, item)
  60. // remove data object
  61. await store.remove<DataObject>(item)
  62. }
  63. // emit log event
  64. logger.info('Storage content have been removed', { id: contentIds, dataObjectIds })
  65. }
  66. export async function dataDirectory_ContentAccepted({ store, event }: EventContext & StoreContext): Promise<void> {
  67. // read event data
  68. const [contentId, storageProviderId] = new DataDirectory.ContentAcceptedEvent(event).params
  69. const encodedContentId = encodeContentId(contentId)
  70. // load asset
  71. const dataObject = await store.get(DataObject, {
  72. where: { joystreamContentId: encodedContentId } as FindConditions<DataObject>,
  73. })
  74. // ensure object exists
  75. if (!dataObject) {
  76. return inconsistentState('Non-existing content acceptation requested', encodedContentId)
  77. }
  78. // load storage provider
  79. const worker = await getWorker(store, 'storageWorkingGroup', storageProviderId)
  80. // update object
  81. dataObject.liaison = worker
  82. dataObject.liaisonJudgement = LiaisonJudgement.ACCEPTED
  83. // set last update time
  84. dataObject.updatedAt = new Date(event.blockTimestamp)
  85. // save object
  86. await store.save<DataObject>(dataObject)
  87. // emit log event
  88. logger.info('Storage content has been accepted', { id: encodedContentId })
  89. }
  90. // TODO: use ON DELETE SET null on database/typeorm level instead?
  91. async function unsetDataObjectRelations(store: DatabaseManager, dataObject: DataObject) {
  92. const channelAssets = ['avatarPhoto', 'coverPhoto'] as const
  93. const videoAssets = ['thumbnailPhoto', 'media'] as const
  94. // TODO: FIXME: Queries to be verified!
  95. // NOTE: we don't need to retrieve multiple channels/videos via `store.getMany()` because dataObject
  96. // is allowed to be associated only with one channel/video in runtime
  97. const channel = await store.get(Channel, {
  98. where: channelAssets.map((assetName) => ({
  99. [assetName]: Raw((alias) => `${alias}::json->'dataObjectId' = :id`, {
  100. id: dataObject.id,
  101. }),
  102. })),
  103. })
  104. const video = await store.get(Video, {
  105. where: videoAssets.map((assetName) => ({
  106. [assetName]: Raw((alias) => `${alias}::json->'dataObjectId' = :id`, {
  107. id: dataObject.id,
  108. }),
  109. })),
  110. })
  111. if (channel) {
  112. channelAssets.forEach((assetName) => {
  113. if (channel[assetName] && (channel[assetName] as AssetJoystreamStorage).dataObjectId === dataObject.id) {
  114. channel[assetName] = undefined
  115. }
  116. })
  117. await store.save<Channel>(channel)
  118. // emit log event
  119. logger.info('Content has been disconnected from Channel', {
  120. channelId: channel.id.toString(),
  121. joystreamContentId: dataObject.joystreamContentId,
  122. })
  123. } else if (video) {
  124. videoAssets.forEach((assetName) => {
  125. if (video[assetName] && (video[assetName] as AssetJoystreamStorage).dataObjectId === dataObject.id) {
  126. video[assetName] = undefined
  127. }
  128. })
  129. await store.save<Video>(video)
  130. // emit log event
  131. logger.info('Content has been disconnected from Video', {
  132. videoId: video.id.toString(),
  133. joystreamContentId: dataObject.joystreamContentId,
  134. })
  135. }
  136. }
  137. /// //////////////// Helpers ////////////////////////////////////////////////////
  138. function convertStorageObjectOwner(objectOwner: StorageObjectOwner): typeof DataObjectOwner {
  139. if (objectOwner.isMember) {
  140. const owner = new DataObjectOwnerMember()
  141. owner.memberId = objectOwner.asMember.toString()
  142. return owner
  143. }
  144. if (objectOwner.isChannel) {
  145. const owner = new DataObjectOwnerChannel()
  146. owner.channelId = objectOwner.asChannel.toString()
  147. return owner
  148. }
  149. if (objectOwner.isDao) {
  150. const owner = new DataObjectOwnerDao()
  151. owner.dao = objectOwner.asDao.toNumber()
  152. return owner
  153. }
  154. if (objectOwner.isCouncil) {
  155. return new DataObjectOwnerCouncil()
  156. }
  157. if (objectOwner.isWorkingGroup) {
  158. const owner = new DataObjectOwnerWorkingGroup()
  159. owner.workingGroupId = getWorkingGroupModuleName(objectOwner.asWorkingGroup)
  160. return owner
  161. }
  162. unexpectedData('Not implemented StorageObjectOwner type', objectOwner.toString())
  163. }
  164. function encodeContentId(contentId: ContentId) {
  165. const customContentId = new Custom_ContentId(registry, contentId)
  166. return customContentId.encode()
  167. }