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