فهرست منبع

keep track of the next entity id

metmirr 4 سال پیش
والد
کامیت
75992f7315

+ 6 - 0
query-node/mappings/content-directory/decode.ts

@@ -83,6 +83,12 @@ function getEntityProperties(propertyValues: ParametrizedClassPropertyValue[]):
       value = inputPropVal.isOfType('Single')
         ? inputPropVal.asType('Single').value.toJSON()
         : inputPropVal.asType('Vector').value.toJSON()
+
+      if (inputPropVal.isOfType('Single')) {
+        if (inputPropVal.asType('Single').isOfType('Reference')) {
+          reference = { entityId: value as number, existing: true }
+        }
+      }
     } else if (v.isOfType('InternalEntityJustAdded')) {
       value = v.asType('InternalEntityJustAdded').toJSON()
       reference = { entityId: value as number, existing: false }

+ 0 - 640
query-node/mappings/content-directory/entity-helper.ts

@@ -1,640 +0,0 @@
-import { DB } from '../../generated/indexer'
-import { Channel } from '../../generated/graphql-server/src/modules/channel/channel.model'
-import { Category } from '../../generated/graphql-server/src/modules/category/category.model'
-import { KnownLicense } from '../../generated/graphql-server/src/modules/known-license/known-license.model'
-import { UserDefinedLicense } from '../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
-import { JoystreamMediaLocation } from '../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
-import { HttpMediaLocation } from '../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
-import { VideoMedia } from '../../generated/graphql-server/src/modules/video-media/video-media.model'
-import { Video } from '../../generated/graphql-server/src/modules/video/video.model'
-import { Block, Network } from '../../generated/graphql-server/src/modules/block/block.model'
-import { Language } from '../../generated/graphql-server/src/modules/language/language.model'
-import { VideoMediaEncoding } from '../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
-import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
-import { License } from '../../generated/graphql-server/src/modules/license/license.model'
-import { MediaLocation } from '../../generated/graphql-server/src/modules/media-location/media-location.model'
-
-import { contentDirectoryClassNamesWithId } from './content-dir-consts'
-import {
-  ClassEntityMap,
-  ICategory,
-  IChannel,
-  ICreateEntityOperation,
-  IDBBlockId,
-  IEntity,
-  IHttpMediaLocation,
-  IJoystreamMediaLocation,
-  IKnownLicense,
-  ILanguage,
-  ILicense,
-  IMediaLocation,
-  IUserDefinedLicense,
-  IVideo,
-  IVideoMedia,
-  IVideoMediaEncoding,
-  IWhereCond,
-} from '../types'
-import { getOrCreate } from './get-or-create'
-import BN from 'bn.js'
-
-async function createBlockOrGetFromDatabase(db: DB, blockNumber: number): Promise<Block> {
-  let b = await db.get(Block, { where: { block: blockNumber } })
-  if (b === undefined) {
-    // TODO: get timestamp from the event or extrinsic
-    b = new Block({ block: blockNumber, network: Network.BABYLON, timestamp: new BN(Date.now()) })
-    await db.save<Block>(b)
-  }
-  return b
-}
-
-async function createChannel(
-  { db, block, id }: IDBBlockId,
-  classEntityMap: ClassEntityMap,
-  p: IChannel
-): Promise<Channel> {
-  const record = await db.get(Channel, { where: { id } })
-  if (record) return record
-
-  const channel = new Channel()
-
-  channel.version = block
-  channel.id = id
-  channel.title = p.title
-  channel.description = p.description
-  channel.isCurated = p.isCurated || false
-  channel.isPublic = p.isPublic
-  channel.coverPhotoUrl = p.coverPhotoURL
-  channel.avatarPhotoUrl = p.avatarPhotoURL
-
-  channel.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  const { language } = p
-  if (language !== undefined) {
-    channel.language = await getOrCreate.language({ db, block, id }, classEntityMap, language)
-  }
-  await db.save(channel)
-  return channel
-}
-
-async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Promise<Category> {
-  const record = await db.get(Category, { where: { id } })
-  if (record) return record
-
-  const category = new Category()
-
-  category.id = id
-  category.name = p.name
-  category.description = p.description
-  category.version = block
-  category.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save(category)
-  return category
-}
-
-async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicense): Promise<KnownLicense> {
-  const record = await db.get(KnownLicense, { where: { id } })
-  if (record) return record
-
-  const knownLicence = new KnownLicense()
-
-  knownLicence.id = id
-  knownLicence.code = p.code
-  knownLicence.name = p.name
-  knownLicence.description = p.description
-  knownLicence.url = p.url
-  knownLicence.version = block
-  knownLicence.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save(knownLicence)
-  return knownLicence
-}
-
-async function createUserDefinedLicense(
-  { db, block, id }: IDBBlockId,
-  p: IUserDefinedLicense
-): Promise<UserDefinedLicense> {
-  const record = await db.get(UserDefinedLicense, { where: { id } })
-  if (record) return record
-
-  const userDefinedLicense = new UserDefinedLicense()
-
-  userDefinedLicense.id = id
-  userDefinedLicense.content = p.content
-  userDefinedLicense.version = block
-  userDefinedLicense.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save<UserDefinedLicense>(userDefinedLicense)
-  return userDefinedLicense
-}
-
-async function createJoystreamMediaLocation(
-  { db, block, id }: IDBBlockId,
-  p: IJoystreamMediaLocation
-): Promise<JoystreamMediaLocation> {
-  const record = await db.get(JoystreamMediaLocation, { where: { id } })
-  if (record) return record
-
-  const joyMediaLoc = new JoystreamMediaLocation()
-
-  joyMediaLoc.id = id
-  joyMediaLoc.dataObjectId = p.dataObjectId
-  joyMediaLoc.version = block
-  joyMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save(joyMediaLoc)
-  return joyMediaLoc
-}
-
-async function createHttpMediaLocation(
-  { db, block, id }: IDBBlockId,
-  p: IHttpMediaLocation
-): Promise<HttpMediaLocation> {
-  const record = await db.get(HttpMediaLocation, { where: { id } })
-  if (record) return record
-
-  const httpMediaLoc = new HttpMediaLocation()
-
-  httpMediaLoc.id = id
-  httpMediaLoc.url = p.url
-  httpMediaLoc.port = p.port
-  httpMediaLoc.version = block
-  httpMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save(httpMediaLoc)
-  return httpMediaLoc
-}
-
-async function createVideoMedia(
-  { db, block, id }: IDBBlockId,
-  classEntityMap: ClassEntityMap,
-  p: IVideoMedia
-): Promise<VideoMedia> {
-  const videoMedia = new VideoMedia()
-
-  videoMedia.id = id
-  videoMedia.pixelHeight = p.pixelHeight
-  videoMedia.pixelWidth = p.pixelWidth
-  videoMedia.size = p.size
-  videoMedia.version = block
-  const { encoding, location } = p
-  if (encoding !== undefined) {
-    videoMedia.encoding = await getOrCreate.videoMediaEncoding({ db, block, id }, classEntityMap, encoding)
-  }
-  if (location !== undefined) {
-    videoMedia.location = await getOrCreate.mediaLocation({ db, block, id }, classEntityMap, location)
-  }
-
-  videoMedia.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save(videoMedia)
-  return videoMedia
-}
-
-async function createVideo({ db, block, id }: IDBBlockId, classEntityMap: ClassEntityMap, p: IVideo): Promise<Video> {
-  const record = await db.get(Video, { where: { id } })
-  if (record) return record
-
-  const video = new Video()
-
-  video.id = id
-  video.title = p.title
-  video.description = p.description
-  video.duration = p.duration
-  video.hasMarketing = p.hasMarketing
-  // TODO: needs to be handled correctly, from runtime CurationStatus is coming
-  video.isCurated = p.isCurated || true
-  video.isExplicit = p.isExplicit
-  video.isPublic = p.isPublic
-  video.publishedBeforeJoystream = p.publishedBeforeJoystream
-  video.skippableIntroDuration = p.skippableIntroDuration
-  video.thumbnailUrl = p.thumbnailURL
-  video.version = block
-
-  const { language, license, category, channel, media } = p
-  if (language !== undefined) {
-    video.language = await getOrCreate.language({ db, block, id }, classEntityMap, language)
-  }
-  if (license !== undefined) {
-    video.license = await getOrCreate.license({ db, block, id }, classEntityMap, license)
-  }
-  if (category !== undefined) {
-    video.category = await getOrCreate.category({ db, block, id }, classEntityMap, category)
-  }
-  if (channel !== undefined) {
-    video.channel = await getOrCreate.channel({ db, block, id }, classEntityMap, channel)
-  }
-  if (media !== undefined) {
-    video.media = await getOrCreate.videoMedia({ db, block, id }, classEntityMap, media)
-  }
-
-  video.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save<Video>(video)
-  return video
-}
-
-async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Promise<Language> {
-  const record = await db.get(Language, { where: { id } })
-  if (record) return record
-
-  const language = new Language()
-  language.id = id
-  language.name = p.name
-  language.code = p.code
-  language.version = block
-  language.happenedIn = await createBlockOrGetFromDatabase(db, block)
-
-  await db.save<Language>(language)
-  return language
-}
-
-async function createVideoMediaEncoding(
-  { db, block, id }: IDBBlockId,
-  p: IVideoMediaEncoding
-): Promise<VideoMediaEncoding> {
-  const record = await db.get(VideoMediaEncoding, { where: { id } })
-  if (record) return record
-
-  const encoding = new VideoMediaEncoding()
-  encoding.id = id
-  encoding.name = p.name
-  encoding.version = block
-  encoding.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save<VideoMediaEncoding>(encoding)
-  return encoding
-}
-
-async function createLicense(
-  { db, block, id }: IDBBlockId,
-  classEntityMap: ClassEntityMap,
-  p: ILicense
-): Promise<License> {
-  const record = await db.get(License, { where: { id } })
-  if (record) return record
-
-  const { knownLicense, userDefinedLicense } = p
-
-  const license = new License()
-  license.id = id
-  if (knownLicense !== undefined) {
-    license.knownLicense = await getOrCreate.knownLicense({ db, block, id }, classEntityMap, knownLicense)
-  }
-  if (userDefinedLicense !== undefined) {
-    license.userdefinedLicense = await getOrCreate.userDefinedLicense(
-      { db, block, id },
-      classEntityMap,
-      userDefinedLicense
-    )
-  }
-  license.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save<License>(license)
-  return license
-}
-
-async function createMediaLocation(
-  { db, block, id }: IDBBlockId,
-  classEntityMap: ClassEntityMap,
-  p: IMediaLocation
-): Promise<MediaLocation> {
-  const { httpMediaLocation, joystreamMediaLocation } = p
-
-  const location = new MediaLocation()
-  location.id = id
-  console.log(p)
-  if (httpMediaLocation !== undefined) {
-    location.httpMediaLocation = await getOrCreate.httpMediaLocation(
-      { db, block, id },
-      classEntityMap,
-      httpMediaLocation
-    )
-  }
-  if (joystreamMediaLocation !== undefined) {
-    location.joystreamMediaLocation = await getOrCreate.joystreamMediaLocation(
-      { db, block, id },
-      classEntityMap,
-      joystreamMediaLocation
-    )
-  }
-  location.happenedIn = await createBlockOrGetFromDatabase(db, block)
-  await db.save<License>(location)
-  return location
-}
-
-async function batchCreateClassEntities(db: DB, block: number, operations: ICreateEntityOperation[]): Promise<void> {
-  operations.map(async ({ classId }, index) => {
-    const c = new ClassEntity()
-    c.id = (index + 1).toString() // entity id
-    c.classId = classId
-    c.version = block
-    c.happenedIn = await createBlockOrGetFromDatabase(db, block)
-    await db.save<ClassEntity>(c)
-  })
-}
-
-async function getClassName(
-  db: DB,
-  entity: IEntity,
-  createEntityOperations: ICreateEntityOperation[]
-): Promise<string | undefined> {
-  const { entityId, indexOf } = entity
-  if (entityId === undefined && indexOf === undefined) {
-    throw Error(`Can not determine class of the entity`)
-  }
-
-  let classId: number | undefined
-  // Is newly created entity in the same transaction
-  if (indexOf !== undefined) {
-    classId = createEntityOperations[indexOf].classId
-  } else {
-    const ce = await db.get(ClassEntity, { where: { id: entityId } })
-    if (ce === undefined) console.log(`Class not found for the entity: ${entityId}`)
-    classId = ce ? ce.classId : undefined
-  }
-
-  const c = contentDirectoryClassNamesWithId.find((c) => c.classId === classId)
-  // TODO: stop execution, class should be created before entity creation
-  if (c === undefined) console.log(`Not recognized class id: ${classId}`)
-  return c ? c.name : undefined
-}
-
-async function removeChannel(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(Channel, where)
-  if (record === undefined) throw Error(`Channel not found`)
-  if (record.videos) record.videos.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
-  await db.remove<Channel>(record)
-}
-async function removeCategory(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(Category, where)
-  if (record === undefined) throw Error(`Category not found`)
-  if (record.videos) record.videos.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
-  await db.remove<Category>(record)
-}
-async function removeVideoMedia(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(VideoMedia, where)
-  if (record === undefined) throw Error(`VideoMedia not found`)
-  if (record.video) await db.remove<Video>(record.video)
-  await db.remove<VideoMedia>(record)
-}
-async function removeVideo(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(Video, where)
-  if (record === undefined) throw Error(`Video not found`)
-  await db.remove<Video>(record)
-}
-
-async function removeLicense(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(License, where)
-  if (record === undefined) throw Error(`License not found`)
-  // Remove all the videos under this license
-  if (record.videolicense) record.videolicense.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
-  await db.remove<License>(record)
-}
-async function removeUserDefinedLicense(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(UserDefinedLicense, where)
-  if (record === undefined) throw Error(`UserDefinedLicense not found`)
-  if (record.licenseuserdefinedLicense)
-    record.licenseuserdefinedLicense.map(async (l) => await removeLicense(db, { where: { id: l.id } }))
-  await db.remove<UserDefinedLicense>(record)
-}
-async function removeKnownLicense(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(KnownLicense, where)
-  if (record === undefined) throw Error(`KnownLicense not found`)
-  if (record.licenseknownLicense)
-    record.licenseknownLicense.map(async (k) => await removeLicense(db, { where: { id: k.id } }))
-  await db.remove<KnownLicense>(record)
-}
-async function removeMediaLocation(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(MediaLocation, where)
-  if (record === undefined) throw Error(`MediaLocation not found`)
-  if (record.videoMedia) await removeVideo(db, { where: { id: record.videoMedia.id } })
-  await db.remove<MediaLocation>(record)
-}
-async function removeHttpMediaLocation(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(HttpMediaLocation, where)
-  if (record === undefined) throw Error(`HttpMediaLocation not found`)
-  if (record.medialocationhttpMediaLocation)
-    record.medialocationhttpMediaLocation.map(async (v) => await removeMediaLocation(db, { where: { id: v.id } }))
-  await db.remove<HttpMediaLocation>(record)
-}
-async function removeJoystreamMediaLocation(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(JoystreamMediaLocation, where)
-  if (record === undefined) throw Error(`JoystreamMediaLocation not found`)
-  if (record.medialocationjoystreamMediaLocation)
-    record.medialocationjoystreamMediaLocation.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
-  await db.remove<JoystreamMediaLocation>(record)
-}
-async function removeLanguage(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(Language, where)
-  if (record === undefined) throw Error(`Language not found`)
-  if (record.channellanguage) record.channellanguage.map(async (c) => await removeChannel(db, { where: { id: c.id } }))
-  if (record.videolanguage) record.videolanguage.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
-  await db.remove<Language>(record)
-}
-async function removeVideoMediaEncoding(db: DB, where: IWhereCond): Promise<void> {
-  const record = await db.get(VideoMediaEncoding, where)
-  if (record === undefined) throw Error(`Language not found`)
-  await db.remove<VideoMediaEncoding>(record)
-}
-
-// ========Entity property value updates========
-
-async function updateMediaLocationEntityPropertyValues(
-  db: DB,
-  where: IWhereCond,
-  props: IMediaLocation
-): Promise<void> {
-  const { httpMediaLocation, joystreamMediaLocation } = props
-  const record = await db.get(MediaLocation, where)
-  if (record === undefined) throw Error(`MediaLocation entity not found: ${where.where.id}`)
-
-  if (httpMediaLocation) {
-    record.httpMediaLocation = await db.get(HttpMediaLocation, { where: { id: httpMediaLocation.toString() } })
-  }
-  if (joystreamMediaLocation) {
-    record.joystreamMediaLocation = await db.get(JoystreamMediaLocation, {
-      where: { id: joystreamMediaLocation.toString() },
-    })
-  }
-  await db.save<MediaLocation>(record)
-}
-
-async function updateLicenseEntityPropertyValues(db: DB, where: IWhereCond, props: ILicense): Promise<void> {
-  const { knownLicense, userDefinedLicense } = props
-  const record = await db.get(License, where)
-  if (record === undefined) throw Error(`License entity not found: ${where.where.id}`)
-
-  if (knownLicense) {
-    record.knownLicense = await db.get(KnownLicense, { where: { id: knownLicense.toString() } })
-  }
-  if (userDefinedLicense) {
-    record.userdefinedLicense = await db.get(UserDefinedLicense, {
-      where: { id: userDefinedLicense.toString() },
-    })
-  }
-  await db.save<License>(record)
-}
-
-async function updateCategoryEntityPropertyValues(db: DB, where: IWhereCond, props: ICategory): Promise<void> {
-  const record = await db.get(Category, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<Category>(record)
-}
-async function updateChannelEntityPropertyValues(db: DB, where: IWhereCond, props: IChannel): Promise<void> {
-  const record = await db.get(Channel, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  if (props.language) {
-    const l = await db.get(Language, { where: { id: props.language.toString() } })
-    if (l === undefined) throw Error(`Language entity not found: ${props.language}`)
-    record.language = l
-    props.language = undefined
-  }
-  Object.assign(record, props)
-  await db.save<Channel>(record)
-}
-async function updateVideoMediaEntityPropertyValues(db: DB, where: IWhereCond, props: IVideoMedia): Promise<void> {
-  const record = await db.get(VideoMedia, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-
-  const { encoding, location } = props
-  if (encoding) {
-    const e = await db.get(VideoMediaEncoding, { where: { id: encoding.toString() } })
-    if (e === undefined) throw Error(`VideoMediaEncoding entity not found: ${encoding}`)
-    record.encoding = e
-    props.encoding = undefined
-  }
-  if (location) {
-    const mediaLoc = await db.get(MediaLocation, { where: { id: location.toString() } })
-    if (!mediaLoc) throw Error(`MediaLocation entity not found: ${location}`)
-    record.location = mediaLoc
-    props.location = undefined
-  }
-  Object.assign(record, props)
-  await db.save<VideoMedia>(record)
-}
-async function updateVideoEntityPropertyValues(db: DB, where: IWhereCond, props: IVideo): Promise<void> {
-  const record = await db.get<Video>(Video, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-
-  const { channel, category, language, media, license } = props
-  if (channel) {
-    const c = await db.get(Channel, { where: { id: channel.toString() } })
-    if (c === undefined) throw Error(`Channel entity not found: ${channel}`)
-    record.channel = c
-    props.channel = undefined
-  }
-  if (category) {
-    const c = await db.get(Category, { where: { id: category.toString() } })
-    if (c === undefined) throw Error(`Category entity not found: ${category}`)
-    record.category = c
-    props.category = undefined
-  }
-  if (media) {
-    const m = await db.get(VideoMedia, { where: { id: media.toString() } })
-    if (m === undefined) throw Error(`VideoMedia entity not found: ${channel}`)
-    record.media = m
-    props.media = undefined
-  }
-  if (license) {
-    const l = await db.get(License, { where: { id: license.toString() } })
-    if (!l) throw Error(`License entity not found: ${license}`)
-    record.license = l
-    props.license = undefined
-  }
-  if (language) {
-    const l = await db.get(Language, { where: { id: language.toString() } })
-    if (l === undefined) throw Error(`Language entity not found: ${language}`)
-    record.language = l
-    props.language = undefined
-  }
-
-  Object.assign(record, props)
-  await db.save<Video>(record)
-}
-async function updateUserDefinedLicenseEntityPropertyValues(
-  db: DB,
-  where: IWhereCond,
-  props: IUserDefinedLicense
-): Promise<void> {
-  const record = await db.get(UserDefinedLicense, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<UserDefinedLicense>(record)
-}
-async function updateKnownLicenseEntityPropertyValues(db: DB, where: IWhereCond, props: IKnownLicense): Promise<void> {
-  const record = await db.get(KnownLicense, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<KnownLicense>(record)
-}
-async function updateHttpMediaLocationEntityPropertyValues(
-  db: DB,
-  where: IWhereCond,
-  props: IHttpMediaLocation
-): Promise<void> {
-  const record = await db.get(HttpMediaLocation, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<HttpMediaLocation>(record)
-}
-
-async function updateJoystreamMediaLocationEntityPropertyValues(
-  db: DB,
-  where: IWhereCond,
-  props: IJoystreamMediaLocation
-): Promise<void> {
-  const record = await db.get(JoystreamMediaLocation, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<JoystreamMediaLocation>(record)
-}
-async function updateLanguageEntityPropertyValues(db: DB, where: IWhereCond, props: ILanguage): Promise<void> {
-  const record = await db.get(Language, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<Language>(record)
-}
-async function updateVideoMediaEncodingEntityPropertyValues(
-  db: DB,
-  where: IWhereCond,
-  props: IVideoMediaEncoding
-): Promise<void> {
-  const record = await db.get(VideoMediaEncoding, where)
-  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
-  Object.assign(record, props)
-  await db.save<VideoMediaEncoding>(record)
-}
-
-export {
-  createCategory,
-  createChannel,
-  createVideoMedia,
-  createVideo,
-  createUserDefinedLicense,
-  createKnownLicense,
-  createHttpMediaLocation,
-  createJoystreamMediaLocation,
-  createLanguage,
-  createVideoMediaEncoding,
-  createLicense,
-  createMediaLocation,
-  removeCategory,
-  removeChannel,
-  removeVideoMedia,
-  removeVideo,
-  removeUserDefinedLicense,
-  removeKnownLicense,
-  removeHttpMediaLocation,
-  removeJoystreamMediaLocation,
-  removeLanguage,
-  removeVideoMediaEncoding,
-  removeMediaLocation,
-  removeLicense,
-  createBlockOrGetFromDatabase,
-  batchCreateClassEntities,
-  getClassName,
-  updateCategoryEntityPropertyValues,
-  updateChannelEntityPropertyValues,
-  updateVideoMediaEntityPropertyValues,
-  updateVideoEntityPropertyValues,
-  updateUserDefinedLicenseEntityPropertyValues,
-  updateHttpMediaLocationEntityPropertyValues,
-  updateJoystreamMediaLocationEntityPropertyValues,
-  updateKnownLicenseEntityPropertyValues,
-  updateLanguageEntityPropertyValues,
-  updateVideoMediaEncodingEntityPropertyValues,
-  updateLicenseEntityPropertyValues,
-  updateMediaLocationEntityPropertyValues,
-}

+ 399 - 0
query-node/mappings/content-directory/entity/create.ts

@@ -0,0 +1,399 @@
+import { DB } from '../../../generated/indexer'
+import { Channel } from '../../../generated/graphql-server/src/modules/channel/channel.model'
+import { Category } from '../../../generated/graphql-server/src/modules/category/category.model'
+import { KnownLicense } from '../../../generated/graphql-server/src/modules/known-license/known-license.model'
+import { UserDefinedLicense } from '../../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
+import { JoystreamMediaLocation } from '../../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
+import { HttpMediaLocation } from '../../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
+import { VideoMedia } from '../../../generated/graphql-server/src/modules/video-media/video-media.model'
+import { Video } from '../../../generated/graphql-server/src/modules/video/video.model'
+import { Block, Network } from '../../../generated/graphql-server/src/modules/block/block.model'
+import { Language } from '../../../generated/graphql-server/src/modules/language/language.model'
+import { VideoMediaEncoding } from '../../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
+import { ClassEntity } from '../../../generated/graphql-server/src/modules/class-entity/class-entity.model'
+import { License } from '../../../generated/graphql-server/src/modules/license/license.model'
+import { MediaLocation } from '../../../generated/graphql-server/src/modules/media-location/media-location.model'
+import { NextEntityId } from '../../../generated/graphql-server/src/modules/next-entity-id/next-entity-id.model'
+
+import { contentDirectoryClassNamesWithId } from '../content-dir-consts'
+import {
+  ClassEntityMap,
+  ICategory,
+  IChannel,
+  ICreateEntityOperation,
+  IDBBlockId,
+  IEntity,
+  IHttpMediaLocation,
+  IJoystreamMediaLocation,
+  IKnownLicense,
+  ILanguage,
+  ILicense,
+  IMediaLocation,
+  IUserDefinedLicense,
+  IVideo,
+  IVideoMedia,
+  IVideoMediaEncoding,
+} from '../../types'
+import { getOrCreate } from '../get-or-create'
+import BN from 'bn.js'
+
+async function createBlockOrGetFromDatabase(db: DB, blockNumber: number): Promise<Block> {
+  let b = await db.get(Block, { where: { block: blockNumber } })
+  if (b === undefined) {
+    // TODO: get timestamp from the event or extrinsic
+    b = new Block({ block: blockNumber, network: Network.BABYLON, timestamp: new BN(Date.now()) })
+    await db.save<Block>(b)
+  }
+  return b
+}
+
+async function createChannel(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: IChannel,
+  nextEntityIdBeforeTransaction: number
+): Promise<Channel> {
+  const record = await db.get(Channel, { where: { id } })
+  if (record) return record
+
+  const channel = new Channel()
+
+  channel.version = block
+  channel.id = id
+  channel.title = p.title
+  channel.description = p.description
+  channel.isCurated = p.isCurated || false
+  channel.isPublic = p.isPublic
+  channel.coverPhotoUrl = p.coverPhotoURL
+  channel.avatarPhotoUrl = p.avatarPhotoURL
+
+  channel.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  const { language } = p
+  if (language !== undefined) {
+    channel.language = await getOrCreate.language(
+      { db, block, id },
+      classEntityMap,
+      language,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  await db.save(channel)
+  return channel
+}
+
+async function createCategory({ db, block, id }: IDBBlockId, p: ICategory): Promise<Category> {
+  const record = await db.get(Category, { where: { id } })
+  if (record) return record
+
+  const category = new Category()
+
+  category.id = id
+  category.name = p.name
+  category.description = p.description
+  category.version = block
+  category.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(category)
+  return category
+}
+
+async function createKnownLicense({ db, block, id }: IDBBlockId, p: IKnownLicense): Promise<KnownLicense> {
+  const record = await db.get(KnownLicense, { where: { id } })
+  if (record) return record
+
+  const knownLicence = new KnownLicense()
+
+  knownLicence.id = id
+  knownLicence.code = p.code
+  knownLicence.name = p.name
+  knownLicence.description = p.description
+  knownLicence.url = p.url
+  knownLicence.version = block
+  knownLicence.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(knownLicence)
+  return knownLicence
+}
+
+async function createUserDefinedLicense(
+  { db, block, id }: IDBBlockId,
+  p: IUserDefinedLicense
+): Promise<UserDefinedLicense> {
+  const record = await db.get(UserDefinedLicense, { where: { id } })
+  if (record) return record
+
+  const userDefinedLicense = new UserDefinedLicense()
+
+  userDefinedLicense.id = id
+  userDefinedLicense.content = p.content
+  userDefinedLicense.version = block
+  userDefinedLicense.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<UserDefinedLicense>(userDefinedLicense)
+  return userDefinedLicense
+}
+
+async function createJoystreamMediaLocation(
+  { db, block, id }: IDBBlockId,
+  p: IJoystreamMediaLocation
+): Promise<JoystreamMediaLocation> {
+  const record = await db.get(JoystreamMediaLocation, { where: { id } })
+  if (record) return record
+
+  const joyMediaLoc = new JoystreamMediaLocation()
+
+  joyMediaLoc.id = id
+  joyMediaLoc.dataObjectId = p.dataObjectId
+  joyMediaLoc.version = block
+  joyMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(joyMediaLoc)
+  return joyMediaLoc
+}
+
+async function createHttpMediaLocation(
+  { db, block, id }: IDBBlockId,
+  p: IHttpMediaLocation
+): Promise<HttpMediaLocation> {
+  const record = await db.get(HttpMediaLocation, { where: { id } })
+  if (record) return record
+
+  const httpMediaLoc = new HttpMediaLocation()
+
+  httpMediaLoc.id = id
+  httpMediaLoc.url = p.url
+  httpMediaLoc.port = p.port
+  httpMediaLoc.version = block
+  httpMediaLoc.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(httpMediaLoc)
+  return httpMediaLoc
+}
+
+async function createVideoMedia(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: IVideoMedia,
+  nextEntityIdBeforeTransaction: number
+): Promise<VideoMedia> {
+  const videoMedia = new VideoMedia()
+
+  videoMedia.id = id
+  videoMedia.pixelHeight = p.pixelHeight
+  videoMedia.pixelWidth = p.pixelWidth
+  videoMedia.size = p.size
+  videoMedia.version = block
+  const { encoding, location } = p
+  if (encoding !== undefined) {
+    videoMedia.encoding = await getOrCreate.videoMediaEncoding(
+      { db, block, id },
+      classEntityMap,
+      encoding,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  if (location !== undefined) {
+    videoMedia.location = await getOrCreate.mediaLocation(
+      { db, block, id },
+      classEntityMap,
+      location,
+      nextEntityIdBeforeTransaction
+    )
+  }
+
+  videoMedia.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save(videoMedia)
+  return videoMedia
+}
+
+async function createVideo(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: IVideo,
+  nextEntityIdBeforeTransaction: number
+): Promise<Video> {
+  const record = await db.get(Video, { where: { id } })
+  if (record) return record
+
+  const video = new Video()
+
+  video.id = id
+  video.title = p.title
+  video.description = p.description
+  video.duration = p.duration
+  video.hasMarketing = p.hasMarketing
+  // TODO: needs to be handled correctly, from runtime CurationStatus is coming
+  video.isCurated = p.isCurated || true
+  video.isExplicit = p.isExplicit
+  video.isPublic = p.isPublic
+  video.publishedBeforeJoystream = p.publishedBeforeJoystream
+  video.skippableIntroDuration = p.skippableIntroDuration
+  video.thumbnailUrl = p.thumbnailURL
+  video.version = block
+
+  const { language, license, category, channel, media } = p
+  if (language !== undefined) {
+    video.language = await getOrCreate.language(
+      { db, block, id },
+      classEntityMap,
+      language,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  if (license !== undefined) {
+    video.license = await getOrCreate.license({ db, block, id }, classEntityMap, license, nextEntityIdBeforeTransaction)
+  }
+  if (category !== undefined) {
+    video.category = await getOrCreate.category(
+      { db, block, id },
+      classEntityMap,
+      category,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  if (channel !== undefined) {
+    video.channel = await getOrCreate.channel({ db, block, id }, classEntityMap, channel, nextEntityIdBeforeTransaction)
+  }
+  if (media !== undefined) {
+    video.media = await getOrCreate.videoMedia({ db, block, id }, classEntityMap, media, nextEntityIdBeforeTransaction)
+  }
+
+  video.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<Video>(video)
+  return video
+}
+
+async function createLanguage({ db, block, id }: IDBBlockId, p: ILanguage): Promise<Language> {
+  const record = await db.get(Language, { where: { id } })
+  if (record) return record
+
+  const language = new Language()
+  language.id = id
+  language.name = p.name
+  language.code = p.code
+  language.version = block
+  language.happenedIn = await createBlockOrGetFromDatabase(db, block)
+
+  await db.save<Language>(language)
+  return language
+}
+
+async function createVideoMediaEncoding(
+  { db, block, id }: IDBBlockId,
+  p: IVideoMediaEncoding
+): Promise<VideoMediaEncoding> {
+  const record = await db.get(VideoMediaEncoding, { where: { id } })
+  if (record) return record
+
+  const encoding = new VideoMediaEncoding()
+  encoding.id = id
+  encoding.name = p.name
+  encoding.version = block
+  encoding.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<VideoMediaEncoding>(encoding)
+  return encoding
+}
+
+async function createLicense(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: ILicense,
+  nextEntityIdBeforeTransaction: number
+): Promise<License> {
+  const record = await db.get(License, { where: { id } })
+  if (record) return record
+
+  const { knownLicense, userDefinedLicense } = p
+
+  const license = new License()
+  license.id = id
+  if (knownLicense !== undefined) {
+    license.knownLicense = await getOrCreate.knownLicense(
+      { db, block, id },
+      classEntityMap,
+      knownLicense,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  if (userDefinedLicense !== undefined) {
+    license.userdefinedLicense = await getOrCreate.userDefinedLicense(
+      { db, block, id },
+      classEntityMap,
+      userDefinedLicense,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  license.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<License>(license)
+  return license
+}
+
+async function createMediaLocation(
+  { db, block, id }: IDBBlockId,
+  classEntityMap: ClassEntityMap,
+  p: IMediaLocation,
+  nextEntityIdBeforeTransaction: number
+): Promise<MediaLocation> {
+  const { httpMediaLocation, joystreamMediaLocation } = p
+
+  const location = new MediaLocation()
+  location.id = id
+  console.log(p)
+  if (httpMediaLocation !== undefined) {
+    location.httpMediaLocation = await getOrCreate.httpMediaLocation(
+      { db, block, id },
+      classEntityMap,
+      httpMediaLocation,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  if (joystreamMediaLocation !== undefined) {
+    location.joystreamMediaLocation = await getOrCreate.joystreamMediaLocation(
+      { db, block, id },
+      classEntityMap,
+      joystreamMediaLocation,
+      nextEntityIdBeforeTransaction
+    )
+  }
+  location.happenedIn = await createBlockOrGetFromDatabase(db, block)
+  await db.save<License>(location)
+  return location
+}
+
+async function getClassName(
+  db: DB,
+  entity: IEntity,
+  createEntityOperations: ICreateEntityOperation[]
+): Promise<string | undefined> {
+  const { entityId, indexOf } = entity
+  if (entityId === undefined && indexOf === undefined) {
+    throw Error(`Can not determine class of the entity`)
+  }
+
+  let classId: number | undefined
+  // Is newly created entity in the same transaction
+  if (indexOf !== undefined) {
+    classId = createEntityOperations[indexOf].classId
+  } else {
+    const ce = await db.get(ClassEntity, { where: { id: entityId } })
+    if (ce === undefined) console.log(`Class not found for the entity: ${entityId}`)
+    classId = ce ? ce.classId : undefined
+  }
+
+  const c = contentDirectoryClassNamesWithId.find((c) => c.classId === classId)
+  // TODO: stop execution, class should be created before entity creation
+  if (c === undefined) console.log(`Not recognized class id: ${classId}`)
+  return c ? c.name : undefined
+}
+
+export {
+  createCategory,
+  createChannel,
+  createVideoMedia,
+  createVideo,
+  createUserDefinedLicense,
+  createKnownLicense,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
+  createLanguage,
+  createVideoMediaEncoding,
+  createLicense,
+  createMediaLocation,
+  createBlockOrGetFromDatabase,
+  getClassName,
+}

+ 43 - 34
query-node/mappings/content-directory/entity.ts → query-node/mappings/content-directory/entity/index.ts

@@ -1,17 +1,23 @@
 import Debug from 'debug'
-import { DB, SubstrateEvent } from '../../generated/indexer'
-import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
+import { DB, SubstrateEvent } from '../../../generated/indexer'
+import { ClassEntity } from '../../../generated/graphql-server/src/modules/class-entity/class-entity.model'
 
-import { decode } from './decode'
+import { decode } from '../decode'
+import {
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  updateLicenseEntityPropertyValues,
+  updateMediaLocationEntityPropertyValues,
+} from './update'
 import {
-  createCategory,
-  createChannel,
-  createVideoMedia,
-  createVideo,
-  createUserDefinedLicense,
-  createKnownLicense,
-  createHttpMediaLocation,
-  createJoystreamMediaLocation,
   removeCategory,
   removeChannel,
   removeVideoMedia,
@@ -22,24 +28,22 @@ import {
   removeJoystreamMediaLocation,
   removeLanguage,
   removeVideoMediaEncoding,
+  removeLicense,
+  removeMediaLocation,
+} from './remove'
+import {
+  createCategory,
+  createChannel,
+  createVideoMedia,
+  createVideo,
+  createUserDefinedLicense,
+  createKnownLicense,
+  createHttpMediaLocation,
+  createJoystreamMediaLocation,
   createLanguage,
   createVideoMediaEncoding,
-  updateCategoryEntityPropertyValues,
-  updateChannelEntityPropertyValues,
-  updateVideoMediaEntityPropertyValues,
-  updateVideoEntityPropertyValues,
-  updateUserDefinedLicenseEntityPropertyValues,
-  updateHttpMediaLocationEntityPropertyValues,
-  updateJoystreamMediaLocationEntityPropertyValues,
-  updateKnownLicenseEntityPropertyValues,
-  updateLanguageEntityPropertyValues,
-  updateVideoMediaEncodingEntityPropertyValues,
   createBlockOrGetFromDatabase,
-  removeLicense,
-  removeMediaLocation,
-  updateLicenseEntityPropertyValues,
-  updateMediaLocationEntityPropertyValues,
-} from './entity-helper'
+} from './create'
 import {
   CategoryPropertyNamesWithId,
   channelPropertyNamesWithId,
@@ -52,7 +56,7 @@ import {
   videoPropertyNamesWithId,
   contentDirectoryClassNamesWithId,
   ContentDirectoryKnownClasses,
-} from './content-dir-consts'
+} from '../content-dir-consts'
 
 import {
   IChannel,
@@ -70,7 +74,8 @@ import {
   IEntity,
   ILicense,
   IMediaLocation,
-} from '../types'
+} from '../../types'
+import { getOrCreate } from '../get-or-create'
 
 const debug = Debug('mappings:content-directory')
 
@@ -101,7 +106,8 @@ async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: Substrat
       await createChannel(
         arg,
         new Map<string, IEntity[]>(),
-        decode.setProperties<IChannel>(event, channelPropertyNamesWithId)
+        decode.setProperties<IChannel>(event, channelPropertyNamesWithId),
+        0 // ignored
       )
       break
 
@@ -138,7 +144,8 @@ async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: Substrat
       await createVideoMedia(
         arg,
         new Map<string, IEntity[]>(),
-        decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId)
+        decode.setProperties<IVideoMedia>(event, videoPropertyNamesWithId),
+        0 // ignored
       )
       break
 
@@ -146,7 +153,8 @@ async function contentDirectory_EntitySchemaSupportAdded(db: DB, event: Substrat
       await createVideo(
         arg,
         new Map<string, IEntity[]>(),
-        decode.setProperties<IVideo>(event, videoPropertyNamesWithId)
+        decode.setProperties<IVideo>(event, videoPropertyNamesWithId),
+        0 // ignored
       )
       break
 
@@ -251,17 +259,18 @@ async function contentDirectory_EntityCreated(db: DB, event: SubstrateEvent): Pr
   classEntity.version = event.blockNumber
   classEntity.happenedIn = await createBlockOrGetFromDatabase(db, event.blockNumber)
   await db.save<ClassEntity>(classEntity)
+
+  await getOrCreate.nextEntityId(db, c.entityId + 1)
 }
 
 // eslint-disable-next-line @typescript-eslint/naming-convention
 async function contentDirectory_EntityPropertyValuesUpdated(db: DB, event: SubstrateEvent): Promise<void> {
-  debug(`EntityPropertyValuesUpdated event: ${JSON.stringify(event)}`)
-
   const { extrinsic } = event
-
   if (extrinsic && extrinsic.method === 'transaction') return
   if (extrinsic === undefined) throw Error(`Extrinsic data not found for event: ${event.id}`)
 
+  debug(`EntityPropertyValuesUpdated event: ${JSON.stringify(event)}`)
+
   const { 2: newPropertyValues } = extrinsic.args
   const entityId = decode.stringIfyEntityId(event)
 

+ 108 - 0
query-node/mappings/content-directory/entity/remove.ts

@@ -0,0 +1,108 @@
+import { DB } from '../../../generated/indexer'
+import { Channel } from '../../../generated/graphql-server/src/modules/channel/channel.model'
+import { Category } from '../../../generated/graphql-server/src/modules/category/category.model'
+import { KnownLicense } from '../../../generated/graphql-server/src/modules/known-license/known-license.model'
+import { UserDefinedLicense } from '../../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
+import { JoystreamMediaLocation } from '../../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
+import { HttpMediaLocation } from '../../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
+import { VideoMedia } from '../../../generated/graphql-server/src/modules/video-media/video-media.model'
+import { Video } from '../../../generated/graphql-server/src/modules/video/video.model'
+import { Language } from '../../../generated/graphql-server/src/modules/language/language.model'
+import { VideoMediaEncoding } from '../../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
+import { License } from '../../../generated/graphql-server/src/modules/license/license.model'
+import { MediaLocation } from '../../../generated/graphql-server/src/modules/media-location/media-location.model'
+
+import { IWhereCond } from '../../types'
+
+async function removeChannel(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Channel, where)
+  if (record === undefined) throw Error(`Channel not found`)
+  if (record.videos) record.videos.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
+  await db.remove<Channel>(record)
+}
+async function removeCategory(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Category, where)
+  if (record === undefined) throw Error(`Category not found`)
+  if (record.videos) record.videos.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
+  await db.remove<Category>(record)
+}
+async function removeVideoMedia(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(VideoMedia, where)
+  if (record === undefined) throw Error(`VideoMedia not found`)
+  if (record.video) await db.remove<Video>(record.video)
+  await db.remove<VideoMedia>(record)
+}
+async function removeVideo(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Video, where)
+  if (record === undefined) throw Error(`Video not found`)
+  await db.remove<Video>(record)
+}
+
+async function removeLicense(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(License, where)
+  if (record === undefined) throw Error(`License not found`)
+  // Remove all the videos under this license
+  if (record.videolicense) record.videolicense.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
+  await db.remove<License>(record)
+}
+async function removeUserDefinedLicense(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(UserDefinedLicense, where)
+  if (record === undefined) throw Error(`UserDefinedLicense not found`)
+  if (record.licenseuserdefinedLicense)
+    record.licenseuserdefinedLicense.map(async (l) => await removeLicense(db, { where: { id: l.id } }))
+  await db.remove<UserDefinedLicense>(record)
+}
+async function removeKnownLicense(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(KnownLicense, where)
+  if (record === undefined) throw Error(`KnownLicense not found`)
+  if (record.licenseknownLicense)
+    record.licenseknownLicense.map(async (k) => await removeLicense(db, { where: { id: k.id } }))
+  await db.remove<KnownLicense>(record)
+}
+async function removeMediaLocation(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(MediaLocation, where)
+  if (record === undefined) throw Error(`MediaLocation not found`)
+  if (record.videoMedia) await removeVideo(db, { where: { id: record.videoMedia.id } })
+  await db.remove<MediaLocation>(record)
+}
+async function removeHttpMediaLocation(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(HttpMediaLocation, where)
+  if (record === undefined) throw Error(`HttpMediaLocation not found`)
+  if (record.medialocationhttpMediaLocation)
+    record.medialocationhttpMediaLocation.map(async (v) => await removeMediaLocation(db, { where: { id: v.id } }))
+  await db.remove<HttpMediaLocation>(record)
+}
+async function removeJoystreamMediaLocation(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(JoystreamMediaLocation, where)
+  if (record === undefined) throw Error(`JoystreamMediaLocation not found`)
+  if (record.medialocationjoystreamMediaLocation)
+    record.medialocationjoystreamMediaLocation.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
+  await db.remove<JoystreamMediaLocation>(record)
+}
+async function removeLanguage(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(Language, where)
+  if (record === undefined) throw Error(`Language not found`)
+  if (record.channellanguage) record.channellanguage.map(async (c) => await removeChannel(db, { where: { id: c.id } }))
+  if (record.videolanguage) record.videolanguage.map(async (v) => await removeVideo(db, { where: { id: v.id } }))
+  await db.remove<Language>(record)
+}
+async function removeVideoMediaEncoding(db: DB, where: IWhereCond): Promise<void> {
+  const record = await db.get(VideoMediaEncoding, where)
+  if (record === undefined) throw Error(`Language not found`)
+  await db.remove<VideoMediaEncoding>(record)
+}
+
+export {
+  removeCategory,
+  removeChannel,
+  removeVideoMedia,
+  removeVideo,
+  removeUserDefinedLicense,
+  removeKnownLicense,
+  removeHttpMediaLocation,
+  removeJoystreamMediaLocation,
+  removeLanguage,
+  removeVideoMediaEncoding,
+  removeMediaLocation,
+  removeLicense,
+}

+ 213 - 0
query-node/mappings/content-directory/entity/update.ts

@@ -0,0 +1,213 @@
+import { DB } from '../../../generated/indexer'
+import { Channel } from '../../../generated/graphql-server/src/modules/channel/channel.model'
+import { Category } from '../../../generated/graphql-server/src/modules/category/category.model'
+import { KnownLicense } from '../../../generated/graphql-server/src/modules/known-license/known-license.model'
+import { UserDefinedLicense } from '../../../generated/graphql-server/src/modules/user-defined-license/user-defined-license.model'
+import { JoystreamMediaLocation } from '../../../generated/graphql-server/src/modules/joystream-media-location/joystream-media-location.model'
+import { HttpMediaLocation } from '../../../generated/graphql-server/src/modules/http-media-location/http-media-location.model'
+import { VideoMedia } from '../../../generated/graphql-server/src/modules/video-media/video-media.model'
+import { Video } from '../../../generated/graphql-server/src/modules/video/video.model'
+import { Language } from '../../../generated/graphql-server/src/modules/language/language.model'
+import { VideoMediaEncoding } from '../../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
+import { License } from '../../../generated/graphql-server/src/modules/license/license.model'
+import { MediaLocation } from '../../../generated/graphql-server/src/modules/media-location/media-location.model'
+
+import {
+  ICategory,
+  IChannel,
+  IHttpMediaLocation,
+  IJoystreamMediaLocation,
+  IKnownLicense,
+  ILanguage,
+  ILicense,
+  IMediaLocation,
+  IUserDefinedLicense,
+  IVideo,
+  IVideoMedia,
+  IVideoMediaEncoding,
+  IWhereCond,
+} from '../../types'
+
+// ========Entity property value updates========
+
+async function updateMediaLocationEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IMediaLocation
+): Promise<void> {
+  const { httpMediaLocation, joystreamMediaLocation } = props
+  const record = await db.get(MediaLocation, where)
+  if (record === undefined) throw Error(`MediaLocation entity not found: ${where.where.id}`)
+
+  if (httpMediaLocation) {
+    record.httpMediaLocation = await db.get(HttpMediaLocation, { where: { id: httpMediaLocation.toString() } })
+  }
+  if (joystreamMediaLocation) {
+    record.joystreamMediaLocation = await db.get(JoystreamMediaLocation, {
+      where: { id: joystreamMediaLocation.toString() },
+    })
+  }
+  await db.save<MediaLocation>(record)
+}
+
+async function updateLicenseEntityPropertyValues(db: DB, where: IWhereCond, props: ILicense): Promise<void> {
+  const { knownLicense, userDefinedLicense } = props
+  const record = await db.get(License, where)
+  if (record === undefined) throw Error(`License entity not found: ${where.where.id}`)
+
+  if (knownLicense) {
+    record.knownLicense = await db.get(KnownLicense, { where: { id: knownLicense.toString() } })
+  }
+  if (userDefinedLicense) {
+    record.userdefinedLicense = await db.get(UserDefinedLicense, {
+      where: { id: userDefinedLicense.toString() },
+    })
+  }
+  await db.save<License>(record)
+}
+
+async function updateCategoryEntityPropertyValues(db: DB, where: IWhereCond, props: ICategory): Promise<void> {
+  const record = await db.get(Category, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Category>(record)
+}
+async function updateChannelEntityPropertyValues(db: DB, where: IWhereCond, props: IChannel): Promise<void> {
+  const record = await db.get(Channel, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  if (props.language) {
+    const l = await db.get(Language, { where: { id: props.language.toString() } })
+    if (l === undefined) throw Error(`Language entity not found: ${props.language}`)
+    record.language = l
+    props.language = undefined
+  }
+  Object.assign(record, props)
+  await db.save<Channel>(record)
+}
+async function updateVideoMediaEntityPropertyValues(db: DB, where: IWhereCond, props: IVideoMedia): Promise<void> {
+  const record = await db.get(VideoMedia, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+
+  const { encoding, location } = props
+  if (encoding) {
+    const e = await db.get(VideoMediaEncoding, { where: { id: encoding.toString() } })
+    if (e === undefined) throw Error(`VideoMediaEncoding entity not found: ${encoding}`)
+    record.encoding = e
+    props.encoding = undefined
+  }
+  if (location) {
+    const mediaLoc = await db.get(MediaLocation, { where: { id: location.toString() } })
+    if (!mediaLoc) throw Error(`MediaLocation entity not found: ${location}`)
+    record.location = mediaLoc
+    props.location = undefined
+  }
+  Object.assign(record, props)
+  await db.save<VideoMedia>(record)
+}
+async function updateVideoEntityPropertyValues(db: DB, where: IWhereCond, props: IVideo): Promise<void> {
+  const record = await db.get<Video>(Video, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+
+  const { channel, category, language, media, license } = props
+  if (channel) {
+    const c = await db.get(Channel, { where: { id: channel.toString() } })
+    if (c === undefined) throw Error(`Channel entity not found: ${channel}`)
+    record.channel = c
+    props.channel = undefined
+  }
+  if (category) {
+    const c = await db.get(Category, { where: { id: category.toString() } })
+    if (c === undefined) throw Error(`Category entity not found: ${category}`)
+    record.category = c
+    props.category = undefined
+  }
+  if (media) {
+    const m = await db.get(VideoMedia, { where: { id: media.toString() } })
+    if (m === undefined) throw Error(`VideoMedia entity not found: ${channel}`)
+    record.media = m
+    props.media = undefined
+  }
+  if (license) {
+    const l = await db.get(License, { where: { id: license.toString() } })
+    if (!l) throw Error(`License entity not found: ${license}`)
+    record.license = l
+    props.license = undefined
+  }
+  if (language) {
+    const l = await db.get(Language, { where: { id: language.toString() } })
+    if (l === undefined) throw Error(`Language entity not found: ${language}`)
+    record.language = l
+    props.language = undefined
+  }
+
+  Object.assign(record, props)
+  await db.save<Video>(record)
+}
+async function updateUserDefinedLicenseEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IUserDefinedLicense
+): Promise<void> {
+  const record = await db.get(UserDefinedLicense, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<UserDefinedLicense>(record)
+}
+async function updateKnownLicenseEntityPropertyValues(db: DB, where: IWhereCond, props: IKnownLicense): Promise<void> {
+  const record = await db.get(KnownLicense, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<KnownLicense>(record)
+}
+async function updateHttpMediaLocationEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IHttpMediaLocation
+): Promise<void> {
+  const record = await db.get(HttpMediaLocation, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<HttpMediaLocation>(record)
+}
+
+async function updateJoystreamMediaLocationEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IJoystreamMediaLocation
+): Promise<void> {
+  const record = await db.get(JoystreamMediaLocation, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<JoystreamMediaLocation>(record)
+}
+async function updateLanguageEntityPropertyValues(db: DB, where: IWhereCond, props: ILanguage): Promise<void> {
+  const record = await db.get(Language, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<Language>(record)
+}
+async function updateVideoMediaEncodingEntityPropertyValues(
+  db: DB,
+  where: IWhereCond,
+  props: IVideoMediaEncoding
+): Promise<void> {
+  const record = await db.get(VideoMediaEncoding, where)
+  if (record === undefined) throw Error(`Entity not found: ${where.where.id}`)
+  Object.assign(record, props)
+  await db.save<VideoMediaEncoding>(record)
+}
+
+export {
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  updateLicenseEntityPropertyValues,
+  updateMediaLocationEntityPropertyValues,
+}

+ 91 - 46
query-node/mappings/content-directory/get-or-create.ts

@@ -9,6 +9,7 @@ import { Language } from '../../generated/graphql-server/src/modules/language/la
 import { VideoMediaEncoding } from '../../generated/graphql-server/src/modules/video-media-encoding/video-media-encoding.model'
 import { License } from '../../generated/graphql-server/src/modules/license/license.model'
 import { MediaLocation } from '../../generated/graphql-server/src/modules/media-location/media-location.model'
+import { NextEntityId } from '../../generated/graphql-server/src/modules/next-entity-id/next-entity-id.model'
 
 import { decode } from './decode'
 import {
@@ -45,19 +46,29 @@ import {
 import {
   createCategory,
   createChannel,
+  createVideoMedia,
+  createUserDefinedLicense,
+  createKnownLicense,
   createHttpMediaLocation,
   createJoystreamMediaLocation,
-  createKnownLicense,
   createLanguage,
+  createVideoMediaEncoding,
   createLicense,
   createMediaLocation,
-  createUserDefinedLicense,
-  createVideoMedia,
-  createVideoMediaEncoding,
-} from './entity-helper'
+} from './entity/create'
+
+import { DB } from '../../generated/indexer'
+
+// Keep track of the next entity id
+async function nextEntityId(db: DB, nextEntityId: number): Promise<void> {
+  let e = await db.get(NextEntityId, { where: { id: '1' } })
+  if (!e) e = new NextEntityId({ id: '1' })
+  e.nextId = nextEntityId
+  await db.save<NextEntityId>(e)
+}
 
 function generateEntityIdFromIndex(index: number): string {
-  return `${index + 1}`
+  return `${index}`
 }
 
 function findEntity(entityId: number, className: string, classEntityMap: ClassEntityMap): IEntity {
@@ -72,7 +83,8 @@ function findEntity(entityId: number, className: string, classEntityMap: ClassEn
 async function language(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  language: IReference
+  language: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<Language> {
   let lang
   const { entityId, existing } = language
@@ -82,14 +94,15 @@ async function language(
     return lang
   }
 
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  lang = await db.get(Language, { where: { id: generateEntityIdFromIndex(entityId) } })
+  lang = await db.get(Language, { where: { id } })
   if (lang) return lang
 
   // get the entity from list of newly created entities and insert into db
   const { properties } = findEntity(entityId, 'Language', classEntityMap)
   return await createLanguage(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<ILanguage>(properties, languagePropertyNamesWIthId)
   )
 }
@@ -97,7 +110,8 @@ async function language(
 async function videoMediaEncoding(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  encoding: IReference
+  encoding: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<VideoMediaEncoding> {
   let vmEncoding
   const { entityId, existing } = encoding
@@ -107,13 +121,15 @@ async function videoMediaEncoding(
     return vmEncoding
   }
 
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
+
   // could be created in the transaction
-  vmEncoding = await db.get(VideoMediaEncoding, { where: { id: generateEntityIdFromIndex(entityId) } })
+  vmEncoding = await db.get(VideoMediaEncoding, { where: { id } })
   if (vmEncoding) return vmEncoding
 
   const { properties } = findEntity(entityId, 'VideoMediaEncoding', classEntityMap)
   return await createVideoMediaEncoding(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<IVideoMediaEncoding>(properties, videoMediaEncodingPropertyNamesWithId)
   )
 }
@@ -121,7 +137,8 @@ async function videoMediaEncoding(
 async function videoMedia(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  media: IReference
+  media: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<VideoMedia> {
   let videoM: VideoMedia | undefined
   const { entityId, existing } = media
@@ -130,23 +147,26 @@ async function videoMedia(
     if (!videoM) throw Error(`VideoMedia entity not found`)
     return videoM
   }
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
 
   // could be created in the transaction
-  videoM = await db.get(VideoMedia, { where: { id: generateEntityIdFromIndex(entityId) } })
+  videoM = await db.get(VideoMedia, { where: { id } })
   if (videoM) return videoM
 
   const { properties } = findEntity(entityId, 'VideoMedia', classEntityMap)
   return await createVideoMedia(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     classEntityMap,
-    decode.setEntityPropertyValues<IVideoMedia>(properties, videoPropertyNamesWithId)
+    decode.setEntityPropertyValues<IVideoMedia>(properties, videoPropertyNamesWithId),
+    nextEntityIdBeforeTransaction
   )
 }
 
 async function knownLicense(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  knownLicense: IReference
+  knownLicense: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<KnownLicense> {
   let kLicense: KnownLicense | undefined
   const { entityId, existing } = knownLicense
@@ -155,21 +175,22 @@ async function knownLicense(
     if (!kLicense) throw Error(`KnownLicense entity not found`)
     return kLicense
   }
-
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  kLicense = await db.get(KnownLicense, { where: { id: generateEntityIdFromIndex(entityId) } })
+  kLicense = await db.get(KnownLicense, { where: { id } })
   if (kLicense) return kLicense
 
   const { properties } = findEntity(entityId, 'KnownLicense', classEntityMap)
   return await createKnownLicense(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<IKnownLicense>(properties, knownLicensePropertyNamesWIthId)
   )
 }
 async function userDefinedLicense(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  userDefinedLicense: IReference
+  userDefinedLicense: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<UserDefinedLicense> {
   let udLicense: UserDefinedLicense | undefined
   const { entityId, existing } = userDefinedLicense
@@ -178,14 +199,16 @@ async function userDefinedLicense(
     if (!udLicense) throw Error(`UserDefinedLicense entity not found`)
     return udLicense
   }
-
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  udLicense = await db.get(UserDefinedLicense, { where: { id: generateEntityIdFromIndex(entityId) } })
+  udLicense = await db.get(UserDefinedLicense, {
+    where: { id },
+  })
   if (udLicense) return udLicense
 
   const { properties } = findEntity(entityId, 'UserDefinedLicense', classEntityMap)
   return await createUserDefinedLicense(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<IUserDefinedLicense>(properties, userDefinedLicensePropertyNamesWithId)
   )
 }
@@ -193,7 +216,8 @@ async function userDefinedLicense(
 async function channel(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  channel: IReference
+  channel: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<Channel> {
   let chann: Channel | undefined
   const { entityId, existing } = channel
@@ -204,22 +228,25 @@ async function channel(
     return chann
   }
 
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  chann = await db.get(Channel, { where: { id: generateEntityIdFromIndex(entityId) } })
+  chann = await db.get(Channel, { where: { id } })
   if (chann) return chann
 
   const { properties } = findEntity(entityId, 'Channel', classEntityMap)
   return await createChannel(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     classEntityMap,
-    decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId)
+    decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId),
+    nextEntityIdBeforeTransaction
   )
 }
 
 async function category(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  category: IReference
+  category: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<Category> {
   let cat: Category | undefined
   const { entityId, existing } = category
@@ -229,14 +256,14 @@ async function category(
     if (!cat) throw Error(`Category entity not found`)
     return cat
   }
-
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  cat = await db.get(Category, { where: { id: generateEntityIdFromIndex(entityId) } })
+  cat = await db.get(Category, { where: { id } })
   if (cat) return cat
 
   const { properties } = findEntity(entityId, 'Category', classEntityMap)
   return await createCategory(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<ICategory>(properties, CategoryPropertyNamesWithId)
   )
 }
@@ -244,7 +271,8 @@ async function category(
 async function httpMediaLocation(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  httpMediaLoc: IReference
+  httpMediaLoc: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<HttpMediaLocation | undefined> {
   let loc: HttpMediaLocation | undefined
   const { entityId, existing } = httpMediaLoc
@@ -254,14 +282,17 @@ async function httpMediaLocation(
     if (!loc) throw Error(`HttpMediaLocation entity not found`)
     return loc
   }
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
 
   // could be created in the transaction
-  loc = await db.get(HttpMediaLocation, { where: { id: generateEntityIdFromIndex(entityId) } })
+  loc = await db.get(HttpMediaLocation, {
+    where: { id },
+  })
   if (loc) return loc
 
   const { properties } = findEntity(entityId, 'HttpMediaLocation', classEntityMap)
   return await createHttpMediaLocation(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<IHttpMediaLocation>(properties, httpMediaLocationPropertyNamesWithId)
   )
 }
@@ -269,7 +300,8 @@ async function httpMediaLocation(
 async function joystreamMediaLocation(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  joyMediaLoc: IReference
+  joyMediaLoc: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<JoystreamMediaLocation | undefined> {
   let loc: JoystreamMediaLocation | undefined
   const { entityId, existing } = joyMediaLoc
@@ -280,13 +312,17 @@ async function joystreamMediaLocation(
     return loc
   }
 
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
+
   // could be created in the transaction
-  loc = await db.get(JoystreamMediaLocation, { where: { id: generateEntityIdFromIndex(entityId) } })
+  loc = await db.get(JoystreamMediaLocation, {
+    where: { id },
+  })
   if (loc) return loc
 
   const { properties } = findEntity(entityId, 'JoystreamMediaLocation', classEntityMap)
   return await createJoystreamMediaLocation(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     decode.setEntityPropertyValues<IJoystreamMediaLocation>(properties, joystreamMediaLocationPropertyNamesWithId)
   )
 }
@@ -294,7 +330,8 @@ async function joystreamMediaLocation(
 async function license(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  license: IReference
+  license: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<License> {
   let lic: License | undefined
   const { entityId, existing } = license
@@ -305,22 +342,25 @@ async function license(
     return lic
   }
 
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
   // could be created in the transaction
-  lic = await db.get(License, { where: { id: generateEntityIdFromIndex(entityId) } })
+  lic = await db.get(License, { where: { id } })
   if (lic) return lic
 
   const { properties } = findEntity(entityId, 'License', classEntityMap)
   return await createLicense(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     classEntityMap,
-    decode.setEntityPropertyValues<ILicense>(properties, licensePropertyNamesWithId)
+    decode.setEntityPropertyValues<ILicense>(properties, licensePropertyNamesWithId),
+    nextEntityIdBeforeTransaction
   )
 }
 
 async function mediaLocation(
   { db, block }: IDBBlockId,
   classEntityMap: ClassEntityMap,
-  location: IReference
+  location: IReference,
+  nextEntityIdBeforeTransaction: number
 ): Promise<MediaLocation> {
   let loc: MediaLocation | undefined
   const { entityId, existing } = location
@@ -329,16 +369,20 @@ async function mediaLocation(
     if (!loc) throw Error(`MediaLocation entity not found`)
     return loc
   }
+  const id = generateEntityIdFromIndex(nextEntityIdBeforeTransaction + entityId)
 
   // could be created in the transaction
-  loc = await db.get(MediaLocation, { where: { id: generateEntityIdFromIndex(entityId) } })
+  loc = await db.get(MediaLocation, {
+    where: { id },
+  })
   if (loc) return loc
 
   const { properties } = findEntity(entityId, 'MediaLocation', classEntityMap)
   return await createMediaLocation(
-    { db, block, id: generateEntityIdFromIndex(entityId) },
+    { db, block, id },
     classEntityMap,
-    decode.setEntityPropertyValues<IMediaLocation>(properties, mediaLocationPropertyNamesWithId)
+    decode.setEntityPropertyValues<IMediaLocation>(properties, mediaLocationPropertyNamesWithId),
+    nextEntityIdBeforeTransaction
   )
 }
 
@@ -363,4 +407,5 @@ export const getOrCreate = {
   httpMediaLocation,
   license,
   mediaLocation,
+  nextEntityId,
 }

+ 67 - 23
query-node/mappings/content-directory/transaction.ts

@@ -1,6 +1,9 @@
 import Debug from 'debug'
 
 import { DB, SubstrateEvent } from '../../generated/indexer'
+import { NextEntityId } from '../../generated/graphql-server/src/modules/next-entity-id/next-entity-id.model'
+import { ClassEntity } from '../../generated/graphql-server/src/modules/class-entity/class-entity.model'
+
 import { decode } from './decode'
 import {
   ClassEntityMap,
@@ -36,6 +39,21 @@ import {
   licensePropertyNamesWithId,
   mediaLocationPropertyNamesWithId,
 } from './content-dir-consts'
+import {
+  updateCategoryEntityPropertyValues,
+  updateChannelEntityPropertyValues,
+  updateVideoMediaEntityPropertyValues,
+  updateVideoEntityPropertyValues,
+  updateUserDefinedLicenseEntityPropertyValues,
+  updateHttpMediaLocationEntityPropertyValues,
+  updateJoystreamMediaLocationEntityPropertyValues,
+  updateKnownLicenseEntityPropertyValues,
+  updateLanguageEntityPropertyValues,
+  updateVideoMediaEncodingEntityPropertyValues,
+  updateLicenseEntityPropertyValues,
+  updateMediaLocationEntityPropertyValues,
+} from './entity/update'
+
 import {
   createCategory,
   createChannel,
@@ -48,25 +66,21 @@ import {
   createLanguage,
   createVideoMediaEncoding,
   getClassName,
-  updateCategoryEntityPropertyValues,
-  updateChannelEntityPropertyValues,
-  updateVideoMediaEntityPropertyValues,
-  updateVideoEntityPropertyValues,
-  updateUserDefinedLicenseEntityPropertyValues,
-  updateHttpMediaLocationEntityPropertyValues,
-  updateJoystreamMediaLocationEntityPropertyValues,
-  updateKnownLicenseEntityPropertyValues,
-  updateLanguageEntityPropertyValues,
-  updateVideoMediaEncodingEntityPropertyValues,
-  batchCreateClassEntities,
   createLicense,
   createMediaLocation,
-  updateLicenseEntityPropertyValues,
-  updateMediaLocationEntityPropertyValues,
-} from './entity-helper'
+  createBlockOrGetFromDatabase,
+} from './entity/create'
+import { getOrCreate } from './get-or-create'
 
 const debug = Debug('mappings:cd:transaction')
 
+async function getNextEntityId(db: DB): Promise<number> {
+  const e = await db.get(NextEntityId, { where: { id: '1' } })
+  // Entity creation happens before addSchemaSupport so this should never happen
+  if (!e) throw Error(`NextEntityId table doesn't have any record`)
+  return e.nextId
+}
+
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export async function contentDirectory_TransactionCompleted(db: DB, event: SubstrateEvent): Promise<void> {
   debug(`TransactionCompleted event: ${JSON.stringify(event)}`)
@@ -97,6 +111,24 @@ export async function contentDirectory_TransactionCompleted(db: DB, event: Subst
   await batchUpdatePropertyValue(db, createEntityOperations, updatePropertyValuesOperations)
 }
 
+async function batchCreateClassEntities(db: DB, block: number, operations: ICreateEntityOperation[]): Promise<void> {
+  const nId = await db.get(NextEntityId, { where: { id: '1' } })
+  let nextId = nId ? nId.nextId : 1 // start entity id from 1
+
+  for (const { classId } of operations) {
+    const c = new ClassEntity({
+      id: nextId.toString(), // entity id
+      classId: classId,
+      version: block,
+      happenedIn: await createBlockOrGetFromDatabase(db, block),
+    })
+    await db.save<ClassEntity>(c)
+    nextId++
+  }
+
+  await getOrCreate.nextEntityId(db, nextId)
+}
+
 /**
  *
  * @param db database connection
@@ -124,12 +156,13 @@ async function batchAddSchemaSupportToEntity(
   // We will remove items from this list whenever we insert them into db
   const doneList: ClassEntityMap = new Map(classEntityMap.entries())
 
+  const nextEntityIdBeforeTransaction = (await getNextEntityId(db)) - createEntityOperations.length
+
   for (const [className, entities] of classEntityMap) {
     for (const entity of entities) {
       const { entityId, indexOf, properties } = entity
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const id = entityId !== undefined ? entityId : indexOf! + 1 // create entity id from index
-
+      const id = entityId !== undefined ? entityId : indexOf! + nextEntityIdBeforeTransaction
       const arg: IDBBlockId = { db, block, id: id.toString() }
 
       switch (className) {
@@ -141,7 +174,8 @@ async function batchAddSchemaSupportToEntity(
           await createChannel(
             arg,
             doneList,
-            decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId)
+            decode.setEntityPropertyValues<IChannel>(properties, channelPropertyNamesWithId),
+            nextEntityIdBeforeTransaction
           )
           break
 
@@ -180,12 +214,18 @@ async function batchAddSchemaSupportToEntity(
           await createVideoMedia(
             arg,
             doneList,
-            decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId)
+            decode.setEntityPropertyValues<IVideoMedia>(properties, videoMediaPropertyNamesWithId),
+            nextEntityIdBeforeTransaction
           )
           break
 
         case ContentDirectoryKnownClasses.VIDEO:
-          await createVideo(arg, doneList, decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId))
+          await createVideo(
+            arg,
+            doneList,
+            decode.setEntityPropertyValues<IVideo>(properties, videoPropertyNamesWithId),
+            nextEntityIdBeforeTransaction
+          )
           break
 
         case ContentDirectoryKnownClasses.LANGUAGE:
@@ -203,14 +243,16 @@ async function batchAddSchemaSupportToEntity(
           await createLicense(
             arg,
             classEntityMap,
-            decode.setEntityPropertyValues<ILicense>(properties, licensePropertyNamesWithId)
+            decode.setEntityPropertyValues<ILicense>(properties, licensePropertyNamesWithId),
+            nextEntityIdBeforeTransaction
           )
           break
         case ContentDirectoryKnownClasses.MEDIALOCATION:
           await createMediaLocation(
             arg,
             classEntityMap,
-            decode.setEntityPropertyValues<IMediaLocation>(properties, mediaLocationPropertyNamesWithId)
+            decode.setEntityPropertyValues<IMediaLocation>(properties, mediaLocationPropertyNamesWithId),
+            nextEntityIdBeforeTransaction
           )
           break
 
@@ -229,12 +271,14 @@ async function batchAddSchemaSupportToEntity(
  * @param entities list of entities those properties values updated
  */
 async function batchUpdatePropertyValue(db: DB, createEntityOperations: ICreateEntityOperation[], entities: IEntity[]) {
+  const entityIdBeforeTransaction = (await getNextEntityId(db)) - createEntityOperations.length
+
   for (const entity of entities) {
     const { entityId, indexOf, properties } = entity
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const id = entityId ? entityId.toString() : indexOf!.toString()
+    const id = entityId ? entityId.toString() : entityIdBeforeTransaction - indexOf!
 
-    const where: IWhereCond = { where: { id } }
+    const where: IWhereCond = { where: { id: id.toString() } }
     const className = await getClassName(db, entity, createEntityOperations)
     if (className === undefined) {
       console.log(`Can not update entity properties values. Unknown class name`)

+ 8 - 0
query-node/schema.graphql

@@ -55,6 +55,14 @@ type ClassEntity @entity {
   happenedIn: Block!
 }
 
+"Keep track of the next entity id"
+type NextEntityId @entity {
+  "Constant field is set to '1'"
+  id: ID!
+
+  nextId: Int!
+}
+
 #### High Level Derivative Entities ####
 
 type Language @entity {