transaction.ts 14 KB

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