storage.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import { fixBlockTimestamp } from './eventFix'
  2. import { SubstrateEvent } from '@dzlzv/hydra-common'
  3. import { DatabaseManager } from '@dzlzv/hydra-db-utils'
  4. import { FindConditions, In } from 'typeorm'
  5. import { inconsistentState, logger, prepareDataObject } from './common'
  6. import { DataDirectory } from '../../generated/types'
  7. import { ContentId, ContentParameters, StorageObjectOwner } from '@joystream/types/augment'
  8. import { ContentId as Custom_ContentId, ContentParameters as Custom_ContentParameters } from '@joystream/types/storage'
  9. import { registry } from '@joystream/types'
  10. import {
  11. Channel,
  12. Video,
  13. AssetAvailability,
  14. DataObject,
  15. DataObjectOwner,
  16. DataObjectOwnerMember,
  17. DataObjectOwnerChannel,
  18. DataObjectOwnerDao,
  19. DataObjectOwnerCouncil,
  20. DataObjectOwnerWorkingGroup,
  21. LiaisonJudgement,
  22. Worker,
  23. WorkerType,
  24. } from 'query-node'
  25. export async function dataDirectory_ContentAdded(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
  26. // read event data
  27. const { contentParameters, storageObjectOwner } = new DataDirectory.ContentAddedEvent(event).data
  28. // save all content objects
  29. for (const parameters of contentParameters) {
  30. const owner = convertStorageObjectOwner(storageObjectOwner)
  31. const dataObject = await prepareDataObject(db, parameters, event, owner)
  32. // fill in auto-generated fields
  33. dataObject.createdAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
  34. dataObject.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
  35. await db.save<DataObject>(dataObject)
  36. }
  37. // emit log event
  38. logger.info('Storage content has beed added', {
  39. ids: contentParameters.map((item) => encodeContentId(item.content_id)),
  40. })
  41. }
  42. export async function dataDirectory_ContentRemoved(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
  43. // read event data
  44. const { contentId: contentIds } = new DataDirectory.ContentRemovedEvent(event).data
  45. // load assets
  46. const dataObjects = await db.getMany(DataObject, {
  47. where: {
  48. joystreamContentId: In(contentIds.map((item) => encodeContentId(item))),
  49. } as FindConditions<DataObject>,
  50. })
  51. // store dataObject ids before they are deleted (for logging purposes)
  52. const dataObjectIds = dataObjects.map((item) => item.id)
  53. // remove assets from database
  54. for (const item of dataObjects) {
  55. // ensure dataObject is nowhere used to prevent db constraint error
  56. await disconnectDataObjectRelations(db, item)
  57. // remove data object
  58. await db.remove<DataObject>(item)
  59. }
  60. // emit log event
  61. logger.info('Storage content have been removed', { id: contentIds, dataObjectIds })
  62. }
  63. export async function dataDirectory_ContentAccepted(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
  64. // read event data
  65. const { contentId, storageProviderId } = new DataDirectory.ContentAcceptedEvent(event).data
  66. const encodedContentId = encodeContentId(contentId)
  67. // load asset
  68. const dataObject = await db.get(DataObject, {
  69. where: { joystreamContentId: encodedContentId } as FindConditions<DataObject>,
  70. })
  71. // ensure object exists
  72. if (!dataObject) {
  73. return inconsistentState('Non-existing content acceptation requested', encodedContentId)
  74. }
  75. // load storage provider
  76. const worker = await db.get(Worker, {
  77. where: {
  78. workerId: storageProviderId.toString(),
  79. type: WorkerType.STORAGE,
  80. } as FindConditions<Worker>,
  81. })
  82. // ensure object exists
  83. if (!worker) {
  84. return inconsistentState('Missing Storage Provider Id', storageProviderId)
  85. }
  86. // update object
  87. dataObject.liaison = worker
  88. dataObject.liaisonJudgement = LiaisonJudgement.ACCEPTED
  89. // set last update time
  90. dataObject.updatedAt = new Date(fixBlockTimestamp(event.blockTimestamp).toNumber())
  91. // save object
  92. await db.save<DataObject>(dataObject)
  93. // emit log event
  94. logger.info('Storage content has been accepted', { id: encodedContentId })
  95. // update asset availability for all connected channels and videos
  96. // this will not be needed after redudant AssetAvailability will be removed (after some Hydra upgrades)
  97. await updateConnectedAssets(db, dataObject)
  98. }
  99. /// ///////////////// Updating connected entities ////////////////////////////////
  100. async function updateConnectedAssets(db: DatabaseManager, dataObject: DataObject) {
  101. await updateSingleConnectedAsset(db, new Channel(), 'avatarPhoto', dataObject)
  102. await updateSingleConnectedAsset(db, new Channel(), 'coverPhoto', dataObject)
  103. await updateSingleConnectedAsset(db, new Video(), 'thumbnailPhoto', dataObject)
  104. await updateSingleConnectedAsset(db, new Video(), 'media', dataObject)
  105. }
  106. // async function updateSingleConnectedAsset(db: DatabaseManager, type: typeof Channel | typeof Video, propertyName: string, dataObject: DataObject) {
  107. async function updateSingleConnectedAsset<T extends Channel | Video>(
  108. db: DatabaseManager,
  109. type: T,
  110. propertyName: string,
  111. dataObject: DataObject
  112. ) {
  113. // prepare lookup condition
  114. const condition = {
  115. where: {
  116. [propertyName + 'DataObject']: dataObject,
  117. },
  118. } // as FindConditions<T>
  119. // NOTE: we don't need to retrieve multiple channels/videos via `db.getMany()` because dataObject
  120. // is allowed to be associated only with one channel/video in runtime
  121. // in therory the following condition(s) can be generalized `... db.get(type, ...` but in practice it doesn't work :-\
  122. const item = type instanceof Channel ? await db.get(Channel, condition) : await db.get(Video, condition)
  123. // escape when no dataObject association found
  124. if (!item) {
  125. return
  126. }
  127. item[propertyName + 'Availability'] = AssetAvailability.ACCEPTED
  128. if (type instanceof Channel) {
  129. await db.save<Channel>(item)
  130. // emit log event
  131. logger.info('Channel using Content has been accepted', {
  132. channelId: item.id.toString(),
  133. joystreamContentId: dataObject.joystreamContentId,
  134. })
  135. } else {
  136. await db.save<Video>(item)
  137. // emit log event
  138. logger.info('Video using Content has been accepted', {
  139. videoId: item.id.toString(),
  140. joystreamContentId: dataObject.joystreamContentId,
  141. })
  142. }
  143. }
  144. // removes connection between dataObject and other entities
  145. async function disconnectDataObjectRelations(db: DatabaseManager, dataObject: DataObject) {
  146. await disconnectSingleDataObjectRelation(db, new Channel(), 'avatarPhoto', dataObject)
  147. await disconnectSingleDataObjectRelation(db, new Channel(), 'coverPhoto', dataObject)
  148. await disconnectSingleDataObjectRelation(db, new Video(), 'thumbnailPhoto', dataObject)
  149. await disconnectSingleDataObjectRelation(db, new Video(), 'media', dataObject)
  150. }
  151. async function disconnectSingleDataObjectRelation<T extends Channel | Video>(
  152. db: DatabaseManager,
  153. type: T,
  154. propertyName: string,
  155. dataObject: DataObject
  156. ) {
  157. // prepare lookup condition
  158. const condition = {
  159. where: {
  160. [propertyName + 'DataObject']: dataObject,
  161. },
  162. } // as FindConditions<T>
  163. // NOTE: we don't need to retrieve multiple channels/videos via `db.getMany()` because dataObject
  164. // is allowed to be associated only with one channel/video in runtime
  165. // in therory the following condition(s) can be generalized `... db.get(type, ...` but in practice it doesn't work :-\
  166. const item = type instanceof Channel ? await db.get(Channel, condition) : await db.get(Video, condition)
  167. // escape when no dataObject association found
  168. if (!item) {
  169. return
  170. }
  171. item[propertyName + 'Availability'] = AssetAvailability.INVALID
  172. item[propertyName + 'DataObject'] = null
  173. if (type instanceof Channel) {
  174. await db.save<Channel>(item)
  175. // emit log event
  176. logger.info('Content has been disconnected from Channel', {
  177. channelId: item.id.toString(),
  178. joystreamContentId: dataObject.joystreamContentId,
  179. })
  180. } else {
  181. // type instanceof Video
  182. await db.save<Video>(item)
  183. // emit log event
  184. logger.info('Content has been disconnected from Video', {
  185. videoId: item.id.toString(),
  186. joystreamContentId: dataObject.joystreamContentId,
  187. })
  188. }
  189. }
  190. /// ///////////////// Helpers ////////////////////////////////////////////////////
  191. function convertStorageObjectOwner(objectOwner: StorageObjectOwner): typeof DataObjectOwner {
  192. if (objectOwner.isMember) {
  193. const owner = new DataObjectOwnerMember()
  194. owner.member = objectOwner.asMember.toNumber()
  195. return owner
  196. }
  197. if (objectOwner.isChannel) {
  198. const owner = new DataObjectOwnerChannel()
  199. owner.channel = objectOwner.asChannel.toNumber()
  200. return owner
  201. }
  202. if (objectOwner.isDao) {
  203. const owner = new DataObjectOwnerDao()
  204. owner.dao = objectOwner.asDao.toNumber()
  205. return owner
  206. }
  207. if (objectOwner.isCouncil) {
  208. return new DataObjectOwnerCouncil()
  209. }
  210. if (objectOwner.isWorkingGroup) {
  211. const owner = new DataObjectOwnerWorkingGroup()
  212. owner.workingGroup = objectOwner.asWorkingGroup.toNumber()
  213. return owner
  214. }
  215. logger.error('Not implemented StorageObjectOwner type', { objectOwner: objectOwner.toString() })
  216. throw new Error('Not implemented StorageObjectOwner type')
  217. }
  218. function encodeContentId(contentId: ContentId) {
  219. const customContentId = new Custom_ContentId(registry, contentId)
  220. return customContentId.encode()
  221. }