transaction.ts 13 KB

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