ニジカ投稿局 https://tv.nizika.tv
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

120 lines
4.0 KiB

  1. import { logger } from '@server/helpers/logger.js'
  2. import isPlainObject from 'lodash-es/isPlainObject.js'
  3. import { ModelStatic, Sequelize, Model as SequelizeModel } from 'sequelize'
  4. /**
  5. *
  6. * Build Sequelize models from sequelize raw query (that must use { nest: true } options)
  7. *
  8. * In order to sequelize to correctly build the JSON this class will ingest,
  9. * the columns selected in the raw query should be in the following form:
  10. * * All tables must be Pascal Cased (for example "VideoChannel")
  11. * * Root table must end with `Model` (for example "VideoCommentModel")
  12. * * Joined tables must contain the origin table name + '->JoinedTable'. For example:
  13. * * "Actor" is joined to "Account": "Actor" table must be renamed "Account->Actor"
  14. * * "Account->Actor" is joined to "Server": "Server" table must be renamed to "Account->Actor->Server"
  15. * * Selected columns must be renamed to contain the JSON path:
  16. * * "videoComment"."id": "VideoCommentModel"."id"
  17. * * "Account"."Actor"."Server"."id": "Account.Actor.Server.id"
  18. * * All tables must contain the row id
  19. */
  20. export class ModelBuilder <T extends SequelizeModel> {
  21. private readonly modelRegistry = new Map<string, T>()
  22. constructor (private readonly sequelize: Sequelize) {
  23. }
  24. createModels (jsonArray: any[], baseModelName: string): T[] {
  25. const result: T[] = []
  26. for (const json of jsonArray) {
  27. const { created, model } = this.createModel(json, baseModelName, json.id + '.' + baseModelName)
  28. if (created) result.push(model)
  29. }
  30. return result
  31. }
  32. private createModel (json: any, modelName: string, keyPath: string) {
  33. if (!json.id) return { created: false, model: null }
  34. const { created, model } = this.createOrFindModel(json, modelName, keyPath)
  35. for (const key of Object.keys(json)) {
  36. const value = json[key]
  37. if (!value) continue
  38. // Child model
  39. if (isPlainObject(value)) {
  40. const { created, model: subModel } = this.createModel(value, key, `${keyPath}.${json.id}.${key}`)
  41. if (!created || !subModel) continue
  42. const Model = this.findModelBuilder(modelName)
  43. const association = Model.associations[key]
  44. if (!association) {
  45. logger.error('Cannot find association %s of model %s', key, modelName, { associations: Object.keys(Model.associations) })
  46. continue
  47. }
  48. if (association.isMultiAssociation) {
  49. if (!Array.isArray(model[key])) model[key] = []
  50. model[key].push(subModel)
  51. } else {
  52. model[key] = subModel
  53. }
  54. }
  55. }
  56. return { created, model }
  57. }
  58. private createOrFindModel (json: any, modelName: string, keyPath: string) {
  59. const registryKey = this.getModelRegistryKey(json, keyPath)
  60. if (this.modelRegistry.has(registryKey)) {
  61. return {
  62. created: false,
  63. model: this.modelRegistry.get(registryKey)
  64. }
  65. }
  66. const Model = this.findModelBuilder(modelName)
  67. if (!Model) {
  68. logger.error(
  69. 'Cannot build model %s that does not exist', this.buildSequelizeModelName(modelName),
  70. { existing: this.sequelize.modelManager.all.map(m => m.name) }
  71. )
  72. return { created: false, model: null }
  73. }
  74. const model = Model.build(json, { raw: true, isNewRecord: false })
  75. this.modelRegistry.set(registryKey, model)
  76. return { created: true, model }
  77. }
  78. private findModelBuilder (modelName: string) {
  79. return this.sequelize.modelManager.getModel(this.buildSequelizeModelName(modelName)) as ModelStatic<T>
  80. }
  81. private buildSequelizeModelName (modelName: string) {
  82. if (modelName === 'Avatars') return 'ActorImageModel'
  83. if (modelName === 'ActorFollowing') return 'ActorModel'
  84. if (modelName === 'ActorFollower') return 'ActorModel'
  85. if (modelName === 'FlaggedAccount') return 'AccountModel'
  86. if (modelName === 'CommentAutomaticTags') return 'CommentAutomaticTagModel'
  87. return modelName + 'Model'
  88. }
  89. private getModelRegistryKey (json: any, keyPath: string) {
  90. return keyPath + json.id
  91. }
  92. }