transaction.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import Debug from 'debug'
  2. import { SubstrateEvent } from '@dzlzv/hydra-common'
  3. import { DatabaseManager, DatabaseManager as DB } from '@dzlzv/hydra-db-utils'
  4. import { NextEntityId } from '../../generated/graphql-server/src/modules/next-entity-id/next-entity-id.model'
  5. import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
  6. import { decode } from './decode'
  7. import {
  8. ClassEntityMap,
  9. IBatchOperation,
  10. ICategory,
  11. IChannel,
  12. ICreateEntityOperation,
  13. IDBBlockId,
  14. IEntity,
  15. IFeaturedVideo,
  16. IHttpMediaLocation,
  17. IJoystreamMediaLocation,
  18. IKnownLicense,
  19. ILanguage,
  20. ILicense,
  21. IMediaLocation,
  22. IProperty,
  23. IUserDefinedLicense,
  24. IVideo,
  25. IVideoMedia,
  26. IVideoMediaEncoding,
  27. IWhereCond,
  28. } from '../types'
  29. import {
  30. categoryPropertyNamesWithId,
  31. channelPropertyNamesWithId,
  32. knownLicensePropertyNamesWIthId,
  33. userDefinedLicensePropertyNamesWithId,
  34. joystreamMediaLocationPropertyNamesWithId,
  35. httpMediaLocationPropertyNamesWithId,
  36. videoMediaPropertyNamesWithId,
  37. videoMediaEncodingPropertyNamesWithId,
  38. videoPropertyNamesWithId,
  39. languagePropertyNamesWIthId,
  40. ContentDirectoryKnownClasses,
  41. licensePropertyNamesWithId,
  42. mediaLocationPropertyNamesWithId,
  43. featuredVideoPropertyNamesWithId,
  44. } from './content-dir-consts'
  45. import { getClassName, createBlockOrGetFromDatabase } from './entity/create'
  46. import { getOrCreate } from './get-or-create'
  47. import {
  48. addSchemaToCategory,
  49. addSchemaToChannel,
  50. addSchemaToFeaturedVideo,
  51. addSchemaToHttpMediaLocation,
  52. addSchemaToJoystreamMediaLocation,
  53. addSchemaToKnownLicense,
  54. addSchemaToLanguage,
  55. addSchemaToLicense,
  56. addSchemaToMediaLocation,
  57. addSchemaToUserDefinedLicense,
  58. addSchemaToVideo,
  59. addSchemaToVideoMedia,
  60. addSchemaToVideoMediaEncoding,
  61. } from './entity/addSchema'
  62. import { createDefaultSchema } from './default-schemas'
  63. const debug = Debug('mappings:cd:transaction')
  64. async function getNextEntityId(db: DB): Promise<number> {
  65. const e = await db.get(NextEntityId, { where: { id: '1' } })
  66. // Entity creation happens before addSchemaSupport so this should never happen
  67. if (!e) throw Error(`NextEntityId table doesn't have any record`)
  68. return e.nextId
  69. }
  70. // eslint-disable-next-line @typescript-eslint/naming-convention
  71. export async function contentDirectory_TransactionFailed(db: DatabaseManager, event: SubstrateEvent): Promise<void> {
  72. debug(`TransactionFailed event: ${JSON.stringify(event)}`)
  73. const failedOperationIndex = event.params[1].value as number
  74. const operations = decode.getOperations(event)
  75. const successfulOperations = operations.toArray().slice(0, failedOperationIndex)
  76. if (!successfulOperations.length) return // No succesfull operations
  77. await applyOperations(decode.getOperationsByTypes(successfulOperations), db, event)
  78. }
  79. // eslint-disable-next-line @typescript-eslint/naming-convention
  80. export async function contentDirectory_TransactionCompleted(db: DB, event: SubstrateEvent): Promise<void> {
  81. debug(`TransactionCompleted event: ${JSON.stringify(event)}`)
  82. const operations = decode.getOperations(event)
  83. await applyOperations(decode.getOperationsByTypes(operations), db, event)
  84. }
  85. async function applyOperations(operations: IBatchOperation, db: DB, event: SubstrateEvent) {
  86. const { addSchemaSupportToEntityOperations, createEntityOperations, updatePropertyValuesOperations } = operations
  87. // Create entities before adding schema support
  88. // We need this to know which entity belongs to which class(we will need to know to update/create
  89. // Channel, Video etc.). For example if there is a property update operation there is no class id
  90. await batchCreateClassEntities(db, event.blockNumber, createEntityOperations)
  91. await batchAddSchemaSupportToEntity(db, createEntityOperations, addSchemaSupportToEntityOperations, event.blockNumber)
  92. await batchUpdatePropertyValue(db, createEntityOperations, updatePropertyValuesOperations)
  93. }
  94. async function batchCreateClassEntities(db: DB, block: number, operations: ICreateEntityOperation[]): Promise<void> {
  95. const nextEntityIdFromDb = await getOrCreate.nextEntityId(db)
  96. let entityId = nextEntityIdFromDb.nextId
  97. for (const { classId } of operations) {
  98. const c = new ClassEntity({
  99. id: entityId.toString(),
  100. classId: classId,
  101. version: block,
  102. happenedIn: await createBlockOrGetFromDatabase(db, block),
  103. })
  104. await db.save<ClassEntity>(c)
  105. // Create default schema for the entity
  106. await createDefaultSchema(db, c)
  107. entityId++
  108. }
  109. // Update database for next entity id
  110. nextEntityIdFromDb.nextId = entityId
  111. await db.save<NextEntityId>(nextEntityIdFromDb)
  112. }
  113. /**
  114. *
  115. * @param db database connection
  116. * @param createEntityOperations: Entity creations with in the same transaction
  117. * @param entities List of entities that schema support is added for
  118. * @param block block number
  119. */
  120. async function batchAddSchemaSupportToEntity(
  121. db: DB,
  122. createEntityOperations: ICreateEntityOperation[],
  123. entities: IEntity[],
  124. block: number
  125. ) {
  126. const classEntityMap: ClassEntityMap = new Map<string, IEntity[]>()
  127. for (const entity of entities) {
  128. const className = await getClassName(db, entity, createEntityOperations)
  129. if (className !== undefined) {
  130. const es = classEntityMap.get(className)
  131. classEntityMap.set(className, es ? [...es, entity] : [entity])
  132. }
  133. }
  134. // This is a copy of classEntityMap, we will use it to keep track of items.
  135. // We will remove items from this list whenever we insert them into db
  136. // const doneList: ClassEntityMap = new Map(classEntityMap.entries())
  137. const nextEntityIdBeforeTransaction = (await getNextEntityId(db)) - createEntityOperations.length
  138. for (const [className, entities] of classEntityMap) {
  139. for (const entity of entities) {
  140. const { entityId, indexOf, properties } = entity
  141. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  142. const id = entityId !== undefined ? entityId : indexOf! + nextEntityIdBeforeTransaction
  143. // const arg: IDBBlockId = { db, block, id: id.toString() }
  144. await addSchemaSupportToEntity(db, className, id, nextEntityIdBeforeTransaction, properties)
  145. }
  146. }
  147. }
  148. /**
  149. * Batch update operations for entity properties values update
  150. * @param db database connection
  151. * @param createEntityOperations Entity creations with in the same transaction
  152. * @param entities list of entities those properties values updated
  153. */
  154. async function batchUpdatePropertyValue(db: DB, createEntityOperations: ICreateEntityOperation[], entities: IEntity[]) {
  155. const entityIdBeforeTransaction = (await getNextEntityId(db)) - createEntityOperations.length
  156. for (const entity of entities) {
  157. const { entityId, indexOf, properties } = entity
  158. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  159. const id = entityId !== undefined ? entityId : entityIdBeforeTransaction - indexOf!
  160. // const where: IWhereCond = { where: { id: id.toString() } }
  161. const className = await getClassName(db, entity, createEntityOperations)
  162. if (!className) {
  163. debug(`Can not update entity properties values. Unknown class name`)
  164. return
  165. }
  166. await addSchemaSupportToEntity(db, className, id, entityIdBeforeTransaction, properties)
  167. }
  168. }
  169. async function addSchemaSupportToEntity(
  170. db: DB,
  171. className: string,
  172. entityId: number,
  173. nextEntityId: number,
  174. properties: IProperty[]
  175. ) {
  176. switch (className) {
  177. case ContentDirectoryKnownClasses.CATEGORY:
  178. await addSchemaToCategory({
  179. db,
  180. entityId,
  181. nextEntityId,
  182. props: decode.setEntityPropertyValues<ICategory>(properties, categoryPropertyNamesWithId),
  183. })
  184. break
  185. case ContentDirectoryKnownClasses.CHANNEL:
  186. await addSchemaToChannel({
  187. db,
  188. entityId,
  189. nextEntityId,
  190. props: decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId),
  191. })
  192. break
  193. case ContentDirectoryKnownClasses.KNOWNLICENSE:
  194. await addSchemaToKnownLicense({
  195. db,
  196. entityId,
  197. nextEntityId,
  198. props: decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId),
  199. })
  200. break
  201. case ContentDirectoryKnownClasses.USERDEFINEDLICENSE:
  202. await addSchemaToUserDefinedLicense({
  203. db,
  204. entityId,
  205. nextEntityId,
  206. props: decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId),
  207. })
  208. break
  209. case ContentDirectoryKnownClasses.JOYSTREAMMEDIALOCATION:
  210. await addSchemaToJoystreamMediaLocation({
  211. db,
  212. entityId,
  213. nextEntityId,
  214. props: decode.setEntityPropertyValues<IJoystreamMediaLocation>(
  215. properties,
  216. joystreamMediaLocationPropertyNamesWithId
  217. ),
  218. })
  219. break
  220. case ContentDirectoryKnownClasses.HTTPMEDIALOCATION:
  221. await addSchemaToHttpMediaLocation({
  222. db,
  223. entityId,
  224. nextEntityId,
  225. props: decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId),
  226. })
  227. break
  228. case ContentDirectoryKnownClasses.VIDEOMEDIA:
  229. await addSchemaToVideoMedia({
  230. db,
  231. entityId,
  232. nextEntityId,
  233. props: decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId),
  234. })
  235. break
  236. case ContentDirectoryKnownClasses.VIDEO:
  237. await addSchemaToVideo({
  238. db,
  239. entityId,
  240. nextEntityId,
  241. props: decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId),
  242. })
  243. break
  244. case ContentDirectoryKnownClasses.LANGUAGE:
  245. await addSchemaToLanguage({
  246. db,
  247. entityId,
  248. nextEntityId,
  249. props: decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId),
  250. })
  251. break
  252. case ContentDirectoryKnownClasses.VIDEOMEDIAENCODING:
  253. await addSchemaToVideoMediaEncoding({
  254. db,
  255. entityId,
  256. nextEntityId,
  257. props: decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId),
  258. })
  259. break
  260. case ContentDirectoryKnownClasses.LICENSE:
  261. await addSchemaToLicense({
  262. db,
  263. entityId,
  264. nextEntityId,
  265. props: decode.setEntityPropertyValues<ILicense>(properties, licensePropertyNamesWithId),
  266. })
  267. break
  268. case ContentDirectoryKnownClasses.MEDIALOCATION:
  269. await addSchemaToMediaLocation({
  270. db,
  271. entityId,
  272. nextEntityId,
  273. props: decode.setEntityPropertyValues<IMediaLocation>(properties, mediaLocationPropertyNamesWithId),
  274. })
  275. break
  276. case ContentDirectoryKnownClasses.FEATUREDVIDEOS:
  277. await addSchemaToFeaturedVideo({
  278. db,
  279. entityId,
  280. nextEntityId,
  281. props: decode.setEntityPropertyValues<IFeaturedVideo>(properties, featuredVideoPropertyNamesWithId),
  282. })
  283. break
  284. default:
  285. debug(`Unknown class name: ${className}`)
  286. break
  287. }
  288. }