1 | "use strict"
|
2 |
|
3 | const fs = require("fs")
|
4 | const path = require("path")
|
5 | const _ = require("lodash")
|
6 |
|
7 | const HighResDate = require("../lib/high-res-date")
|
8 |
|
9 | class ModelsInitializer {
|
10 | constructor(logger, uri, Sequelize, {modelsDir, sequelizeOptions, disableHighResDate, disableVirtualJson, disableStatusJson}) {
|
11 | this.Sequelize = Sequelize
|
12 | this.uri = uri
|
13 | this.logger = logger
|
14 | this.modelsDir = modelsDir || "app/models"
|
15 | this.models = {}
|
16 | this.options = {
|
17 | disableHighResDate: !!disableHighResDate,
|
18 | disableVirtualJson: !!disableVirtualJson,
|
19 | disableStatusJson: !!disableStatusJson
|
20 | }
|
21 | this.sequelizeOptions = Object.assign({}, {
|
22 | timestamps: false,
|
23 | operatorsAliases: Sequelize.Op,
|
24 | benchmark: true,
|
25 | logging: (sql, time) => logger.debug(`${sql} [${time}ms]`)
|
26 | }, sequelizeOptions || {})
|
27 | }
|
28 |
|
29 | async initializeModels() {
|
30 | this._extendDataTypes()
|
31 | this._createConnection()
|
32 | this._importModels()
|
33 | }
|
34 |
|
35 | async destroy() {
|
36 | await this.sequelize.close()
|
37 | }
|
38 |
|
39 | _extendDataTypes() {
|
40 | if (!this.options.disableVirtualJson) this._extendWithVirutalJson()
|
41 | if (!this.options.disableStatusJson) this._extendWithStatusJson()
|
42 | if (!this.options.disableHighResDate) this._monkeypatchDateType()
|
43 | }
|
44 |
|
45 | _createConnection() {
|
46 | let Sequelize = this.Sequelize
|
47 | this.sequelize = new Sequelize(this.uri, this.sequelizeOptions)
|
48 | }
|
49 |
|
50 | _importModels() {
|
51 | fs.readdirSync(this.modelsDir)
|
52 | .filter((file) => (file.indexOf(".") !== 0) && (file.slice(-3) === ".js"))
|
53 | .forEach((file) => {
|
54 | let model = this.sequelize["import"](path.join(this.modelsDir, file))
|
55 | this.models[model.name] = model
|
56 | this[model.name] = model
|
57 | })
|
58 |
|
59 | Object.keys(this.models).forEach((modelName) => {
|
60 | if (this.models[modelName].associate) {
|
61 | this.models[modelName].associate(this.models)
|
62 | }
|
63 | })
|
64 | }
|
65 |
|
66 | _extendWithVirutalJson() {
|
67 | const hash = require("object-hash")
|
68 |
|
69 | this.Sequelize.DataTypes.VIRTUAL_JSON = (field, config = {}) => {
|
70 | let defaultValue = config.default ? JSON.stringify(config.default) : "{}"
|
71 | return ({
|
72 | type: this.Sequelize.DataTypes.TEXT("medium"),
|
73 | get: function() { return JSON.parse(this.getDataValue(field) || defaultValue)},
|
74 | set: function(json) {
|
75 | json = json || config.default
|
76 | this.setDataValue(field, JSON.stringify(json))
|
77 | if(config.hash) {
|
78 | this.setDataValue(config.hash, hash(json))
|
79 | }
|
80 | }
|
81 | })
|
82 | }
|
83 | }
|
84 |
|
85 | _extendWithStatusJson() {
|
86 | function statusJSON(json) {
|
87 | return _.mapValues(json || {}, (v) => {
|
88 | if (_.isString(v)) {
|
89 | try {
|
90 | return JSON.parse(v)
|
91 | } catch(e) {
|
92 | return null
|
93 | }
|
94 | }
|
95 | return v
|
96 | })
|
97 | }
|
98 |
|
99 | this.Sequelize.DataTypes.STATUS_JSON = (field, config = {}) => {
|
100 | let defaultValue = config.default ? JSON.stringify(config.default) : "{}"
|
101 | return ({
|
102 | type: this.Sequelize.DataTypes.TEXT("medium"),
|
103 | get: function() {
|
104 | return statusJSON(JSON.parse(this.getDataValue(field) || defaultValue))
|
105 | },
|
106 | set: function(json) {
|
107 | this.setDataValue(field, JSON.stringify(statusJSON(json)))
|
108 | }
|
109 | })
|
110 | }
|
111 | }
|
112 |
|
113 | _monkeypatchDateType() {
|
114 | let originalMethods = {
|
115 | parse: this.Sequelize.DataTypes.mysql.DATE.parse,
|
116 | strigify: this.Sequelize.DataTypes.mysql.DATE.prototype._stringify
|
117 | }
|
118 |
|
119 | class HackieString extends String {
|
120 | string(){
|
121 | return this.toString()
|
122 | }
|
123 | }
|
124 | this.Sequelize.DataTypes.mysql.DATE.prototype._sanitize = function (value, options) {
|
125 | if ((!options || options && !options.raw) && !(value instanceof Date) && !!value) {
|
126 | return new HighResDate(value)
|
127 | }
|
128 | return value
|
129 | }
|
130 | this.Sequelize.DataTypes.mysql.DATE.prototype._isChanged = function (value, originalValue) {
|
131 | if (originalValue && !!value && (value === originalValue || HighResDate.equals(value, originalValue))) {
|
132 | return false
|
133 | }
|
134 |
|
135 | if (!originalValue && !value && originalValue === value) {
|
136 | return false
|
137 | }
|
138 | return true
|
139 | }
|
140 |
|
141 | this.Sequelize.DataTypes.mysql.DATE.parse = function(value, options) {
|
142 | let strValue = value.string()
|
143 | if (strValue === null) {
|
144 | return null
|
145 | }
|
146 | value = new HackieString(strValue)
|
147 | let res = originalMethods.parse.call(this, value, options)
|
148 | res = new HighResDate(res)
|
149 | res.parseFraction(value.string())
|
150 | return res
|
151 | }
|
152 |
|
153 | this.Sequelize.DataTypes.mysql.DATE.prototype._stringify = function(date, options) {
|
154 | let res = originalMethods.strigify.call(this, date, options)
|
155 | res = new HighResDate(res)
|
156 | if (date instanceof HighResDate) {
|
157 | res.fraction = date.fraction
|
158 | }
|
159 | return res.toSQL()
|
160 | }
|
161 | }
|
162 | }
|
163 |
|
164 | module.exports = ModelsInitializer
|