transaction.ts 14 KB

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