anote-server-libs 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +3 -0
- package/models/repository/BaseModelRepository.js +9 -8
- package/models/repository/BaseModelRepository.ts +10 -10
- package/models/repository/ModelDao.js +33 -65
- package/models/repository/ModelDao.ts +41 -70
- package/package.json +1 -1
- package/models/repository/CryptModelDao.ts +0 -89
|
@@ -70,22 +70,23 @@ END`)
|
|
|
70
70
|
hash: crypto.createHash('sha256').update(content).digest('hex')
|
|
71
71
|
};
|
|
72
72
|
});
|
|
73
|
-
|
|
73
|
+
const minAvailableId = migrationsAvailable.length > 0 ? migrationsAvailable[0].id : 0;
|
|
74
|
+
const maxAvailableId = migrationsAvailable.length > 0 ? migrationsAvailable[migrationsAvailable.length - 1].id : Infinity;
|
|
75
|
+
migrations = migrations.filter(m => m.id >= minAvailableId && m.id <= maxAvailableId);
|
|
76
|
+
if (onlyAboveOrEquals)
|
|
77
|
+
migrations = migrations.filter(m => m.id >= onlyAboveOrEquals);
|
|
74
78
|
if (onlyBelow)
|
|
75
79
|
migrations = migrations.filter(m => m.id < onlyBelow);
|
|
76
80
|
if (migrationsAvailable.length === 0 && migrations.length === 0)
|
|
77
81
|
process.exit(5);
|
|
78
82
|
if (migrationsAvailable.length && migrationsAvailable.length !== (migrationsAvailable[migrationsAvailable.length - 1].id - migrationsAvailable[0].id + 1))
|
|
79
83
|
process.exit(5);
|
|
80
|
-
let highestCommon =
|
|
81
|
-
while (highestCommon < migrations.length
|
|
82
|
-
&& highestCommon < migrationsAvailable.length
|
|
83
|
-
&& migrations[highestCommon]
|
|
84
|
-
&& migrationsAvailable[highestCommon]
|
|
84
|
+
let highestCommon = onlyAboveOrEquals;
|
|
85
|
+
while (highestCommon < migrations.length && highestCommon < migrationsAvailable.length
|
|
85
86
|
&& migrations[highestCommon].hash === migrationsAvailable[highestCommon].hash)
|
|
86
87
|
highestCommon++;
|
|
87
|
-
this.applyDownUntil(migrations, migrations.length, highestCommon).then(
|
|
88
|
-
this.applyUpUntil(migrationsAvailable, Math.min(
|
|
88
|
+
this.applyDownUntil(migrations, migrations.length, highestCommon).then(highestCommon => {
|
|
89
|
+
this.applyUpUntil(migrationsAvailable, Math.min(highestCommon, migrations.length), migrationsAvailable.length).then(callback, process.exit);
|
|
89
90
|
}, process.exit);
|
|
90
91
|
});
|
|
91
92
|
}, () => process.exit(3));
|
|
@@ -71,19 +71,19 @@ END`)])).then(() => {
|
|
|
71
71
|
hash: crypto.createHash('sha256').update(content).digest('hex')
|
|
72
72
|
};
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
const minAvailableId = migrationsAvailable.length > 0 ? migrationsAvailable[0].id : 0;
|
|
75
|
+
const maxAvailableId = migrationsAvailable.length > 0 ? migrationsAvailable[migrationsAvailable.length - 1].id : Infinity;
|
|
76
|
+
migrations = migrations.filter(m => m.id >= minAvailableId && m.id <= maxAvailableId);
|
|
77
|
+
if(onlyAboveOrEquals) migrations = migrations.filter(m => m.id >= onlyAboveOrEquals);
|
|
75
78
|
if(onlyBelow) migrations = migrations.filter(m => m.id < onlyBelow);
|
|
76
79
|
if(migrationsAvailable.length === 0 && migrations.length === 0) process.exit(5);
|
|
77
80
|
if(migrationsAvailable.length && migrationsAvailable.length !== (migrationsAvailable[migrationsAvailable.length - 1].id - migrationsAvailable[0].id + 1)) process.exit(5);
|
|
78
|
-
let highestCommon =
|
|
79
|
-
while(highestCommon < migrations.length
|
|
80
|
-
|
|
81
|
-
&& migrations[highestCommon]
|
|
82
|
-
&& migrationsAvailable[highestCommon]
|
|
83
|
-
&& migrations[highestCommon].hash === migrationsAvailable[highestCommon].hash)
|
|
81
|
+
let highestCommon = onlyAboveOrEquals;
|
|
82
|
+
while(highestCommon < migrations.length && highestCommon < migrationsAvailable.length
|
|
83
|
+
&& migrations[highestCommon].hash === migrationsAvailable[highestCommon].hash)
|
|
84
84
|
highestCommon++;
|
|
85
|
-
this.applyDownUntil(migrations, migrations.length, highestCommon).then(
|
|
86
|
-
this.applyUpUntil(migrationsAvailable, Math.min(
|
|
85
|
+
this.applyDownUntil(migrations, migrations.length, highestCommon).then(highestCommon => {
|
|
86
|
+
this.applyUpUntil(migrationsAvailable, Math.min(highestCommon, migrations.length), migrationsAvailable.length).then(callback, process.exit);
|
|
87
87
|
}, process.exit);
|
|
88
88
|
});
|
|
89
89
|
}, () => process.exit(3));
|
|
@@ -172,4 +172,4 @@ END`)])).then(() => {
|
|
|
172
172
|
});
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
|
-
}
|
|
175
|
+
}
|
|
@@ -74,33 +74,23 @@ class Dao {
|
|
|
74
74
|
return Promise.reject('Record archived!');
|
|
75
75
|
instance.updatedOn = on || new Date();
|
|
76
76
|
if (this.pool) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return props.then(resolvedProps => {
|
|
81
|
-
resolvedProps.push(instance.id);
|
|
82
|
-
return client.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), resolvedProps).then(() => instance.id);
|
|
83
|
-
});
|
|
77
|
+
const props = this.serialize(instance);
|
|
78
|
+
props.push(instance.id);
|
|
79
|
+
return client.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
|
|
84
80
|
}
|
|
85
81
|
else {
|
|
86
82
|
const request = client.request();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return props.then(() => {
|
|
91
|
-
request.input(String(this.nFields + 1), instance.id);
|
|
92
|
-
return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
|
|
93
|
-
});
|
|
83
|
+
this.serialize(instance, request);
|
|
84
|
+
request.input(String(this.nFields + 1), instance.id);
|
|
85
|
+
return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
|
|
94
86
|
}
|
|
95
87
|
}
|
|
96
88
|
create(instance, client, on) {
|
|
97
89
|
instance.createdOn = instance.updatedOn = on || new Date();
|
|
98
90
|
if (this.pool) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return props.then(resolvedProps => (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
103
|
-
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '$' + (i + 1)).join(',') + ') RETURNING id', resolvedProps)).then(q => {
|
|
91
|
+
const props = this.serialize(instance);
|
|
92
|
+
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
93
|
+
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '$' + (i + 1)).join(',') + ') RETURNING id', props).then(q => {
|
|
104
94
|
const idNum = parseInt(q.rows[0].id, 10);
|
|
105
95
|
if (String(idNum) !== q.rows[0].id)
|
|
106
96
|
return q.rows[0].id;
|
|
@@ -109,11 +99,9 @@ class Dao {
|
|
|
109
99
|
}
|
|
110
100
|
else {
|
|
111
101
|
const request = (client || this.poolMssql).request();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return props.then(() => request.query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=@\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
116
|
-
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id')).then((q) => {
|
|
102
|
+
this.serialize(instance, request);
|
|
103
|
+
return request.query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=@\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
104
|
+
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id').then((q) => {
|
|
117
105
|
return q.recordsets[0][0].id || instance.id;
|
|
118
106
|
});
|
|
119
107
|
}
|
|
@@ -123,40 +111,26 @@ class Dao {
|
|
|
123
111
|
return Promise.resolve([]);
|
|
124
112
|
const now = on || new Date();
|
|
125
113
|
instances.forEach(instance => instance.updatedOn = now);
|
|
126
|
-
const props = instances.map(instance =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
props = Promise.resolve(props);
|
|
130
|
-
return props;
|
|
131
|
-
});
|
|
132
|
-
return Promise.all(props).then(resolvedProps => {
|
|
133
|
-
resolvedProps = resolvedProps.reduce((p, n) => p.concat(n), []);
|
|
134
|
-
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
135
|
-
+ ' VALUES' + instances.map((_, j) => ('(' + new Array(this.nFields).fill(undefined).map((__, i) => '$' + (j * this.nFields + i + 1)).join(', ') + ')')).join(',') + ' RETURNING id', resolvedProps);
|
|
136
|
-
}).then(q => q.rows.map(r => {
|
|
114
|
+
const props = [].concat.apply([], instances.map(instance => this.serialize(instance)));
|
|
115
|
+
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
116
|
+
+ ' VALUES' + instances.map((_, j) => ('(' + new Array(this.nFields).fill(undefined).map((__, i) => '$' + (j * this.nFields + i + 1)).join(', ') + ')')).join(',') + ' RETURNING id', props).then(q => q.rows.map(r => {
|
|
137
117
|
const idNum = parseInt(r.id, 10);
|
|
138
118
|
if (String(idNum) !== q.rows[0].id)
|
|
139
119
|
return r.id;
|
|
140
120
|
return idNum;
|
|
141
121
|
}));
|
|
142
122
|
}
|
|
143
|
-
buildObjectWrapper(q) {
|
|
144
|
-
return this.buildObject(q);
|
|
145
|
-
}
|
|
146
|
-
serializeWrapper(instance, request) {
|
|
147
|
-
return this.serialize(instance, request);
|
|
148
|
-
}
|
|
149
123
|
}
|
|
150
124
|
exports.Dao = Dao;
|
|
151
125
|
class ModelDao extends Dao {
|
|
152
126
|
get(id, client, lock = true) {
|
|
153
127
|
if (this.pool) {
|
|
154
|
-
return (client || this.pool).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.
|
|
128
|
+
return (client || this.pool).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
|
|
155
129
|
}
|
|
156
130
|
else {
|
|
157
131
|
const request = (client || this.poolMssql).request();
|
|
158
132
|
request.input('1', id);
|
|
159
|
-
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id=@1').then((q) => this.
|
|
133
|
+
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id=@1').then((q) => this.buildObject(q.recordsets[0][0]));
|
|
160
134
|
}
|
|
161
135
|
}
|
|
162
136
|
count(where, inputs = [], client) {
|
|
@@ -173,20 +147,20 @@ class ModelDao extends Dao {
|
|
|
173
147
|
getList(ids, client, lock = true) {
|
|
174
148
|
if (this.pool) {
|
|
175
149
|
return (client || this.pool).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
|
|
176
|
-
.then(q =>
|
|
150
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
177
151
|
}
|
|
178
152
|
else {
|
|
179
153
|
const request = (client || this.poolMssql).request();
|
|
180
154
|
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id IN ('
|
|
181
155
|
+ (ids.length > 0 ? (typeof ids[0] === 'string' ? '\'' + ids.join('\',\'') + '\'' : ids.join(',')) : '') + ')')
|
|
182
|
-
.then((q) =>
|
|
156
|
+
.then((q) => q.recordsets[0].map((r) => this.buildObject(r)));
|
|
183
157
|
}
|
|
184
158
|
}
|
|
185
159
|
getAllBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
186
160
|
if (this.pool) {
|
|
187
161
|
return (client || this.pool).query(this.selectDefinition + ' FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
188
162
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
189
|
-
.then(q =>
|
|
163
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
190
164
|
}
|
|
191
165
|
else {
|
|
192
166
|
const request = (client || this.poolMssql).request();
|
|
@@ -194,17 +168,16 @@ class ModelDao extends Dao {
|
|
|
194
168
|
where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
195
169
|
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
196
170
|
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : ''))
|
|
197
|
-
.then((q) =>
|
|
171
|
+
.then((q) => q.recordsets[0].map((r) => this.buildObject(r)));
|
|
198
172
|
}
|
|
199
173
|
}
|
|
200
174
|
getViewCountBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
201
175
|
if (this.pool) {
|
|
202
176
|
return (client || this.pool).query(this.selectDefinition + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
203
177
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
204
|
-
.then(q =>
|
|
205
|
-
.
|
|
206
|
-
|
|
207
|
-
count
|
|
178
|
+
.then(q => ({
|
|
179
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
180
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
208
181
|
}));
|
|
209
182
|
}
|
|
210
183
|
else {
|
|
@@ -215,11 +188,9 @@ class ModelDao extends Dao {
|
|
|
215
188
|
() => request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
216
189
|
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
217
190
|
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
218
|
-
])
|
|
219
|
-
|
|
220
|
-
.
|
|
221
|
-
views,
|
|
222
|
-
count
|
|
191
|
+
]).then(([q1, q2]) => ({
|
|
192
|
+
views: q1.recordsets[0].map((r) => this.buildObject(r)),
|
|
193
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
|
|
223
194
|
}));
|
|
224
195
|
}
|
|
225
196
|
}
|
|
@@ -227,10 +198,9 @@ class ModelDao extends Dao {
|
|
|
227
198
|
if (this.pool) {
|
|
228
199
|
return (client || this.pool).query('SELECT ' + cols.map(r => r.indexOf(' ') > -1 ? r : ('"' + r + '"')).join(',') + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
229
200
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
230
|
-
.then(q =>
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
count
|
|
201
|
+
.then(q => ({
|
|
202
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
203
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
234
204
|
}));
|
|
235
205
|
}
|
|
236
206
|
else {
|
|
@@ -242,11 +212,9 @@ class ModelDao extends Dao {
|
|
|
242
212
|
+ ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
243
213
|
+ (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
244
214
|
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
245
|
-
])
|
|
246
|
-
|
|
247
|
-
.
|
|
248
|
-
views,
|
|
249
|
-
count
|
|
215
|
+
]).then(([q1, q2]) => ({
|
|
216
|
+
views: q1.recordsets[0].map((r) => this.buildObject(r)),
|
|
217
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
|
|
250
218
|
}));
|
|
251
219
|
}
|
|
252
220
|
}
|
|
@@ -94,41 +94,33 @@ export abstract class Dao<R, T extends Model<R>> implements ModelRepr {
|
|
|
94
94
|
if((<any>instance).archivedOn) return Promise.reject('Record archived!');
|
|
95
95
|
instance.updatedOn = on || new Date();
|
|
96
96
|
if(this.pool) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return props.then(
|
|
100
|
-
resolvedProps.push(instance.id);
|
|
101
|
-
return (<ClientBase>client).query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), resolvedProps).then(() => instance.id);
|
|
102
|
-
});
|
|
97
|
+
const props = this.serialize(instance);
|
|
98
|
+
props.push(instance.id);
|
|
99
|
+
return (<ClientBase>client).query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
|
|
103
100
|
} else {
|
|
104
101
|
const request = (<ConnectionPool | Transaction>client).request();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return
|
|
108
|
-
request.input(String(this.nFields + 1), instance.id);
|
|
109
|
-
return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
|
|
110
|
-
});
|
|
102
|
+
this.serialize(instance, request);
|
|
103
|
+
request.input(String(this.nFields + 1), instance.id);
|
|
104
|
+
return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
|
|
111
105
|
}
|
|
112
106
|
}
|
|
113
107
|
|
|
114
108
|
create(instance: T, client?: ClientBase | Transaction, on?: Date): Promise<R> {
|
|
115
109
|
(<any>instance).createdOn = instance.updatedOn = on || new Date();
|
|
116
110
|
if(this.pool) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return props.then(resolvedProps => (<ClientBase | Pool>(client || this.pool)).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
111
|
+
const props = this.serialize(instance);
|
|
112
|
+
return (<ClientBase | Pool>(client || this.pool)).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
120
113
|
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '$' + (i + 1)).join(',') + ') RETURNING id',
|
|
121
|
-
|
|
114
|
+
props).then(q => {
|
|
122
115
|
const idNum = parseInt(q.rows[0].id, 10);
|
|
123
116
|
if(String(idNum) !== q.rows[0].id) return q.rows[0].id;
|
|
124
117
|
return idNum;
|
|
125
118
|
});
|
|
126
119
|
} else {
|
|
127
120
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id')).then((q: any) => {
|
|
121
|
+
this.serialize(instance, request);
|
|
122
|
+
return request.query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=@\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
123
|
+
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i: number) => '@' + (i + 1)).join(',') + '); SELECT SCOPE_IDENTITY() AS id').then((q: any) => {
|
|
132
124
|
return q.recordsets[0][0].id || instance.id;
|
|
133
125
|
});
|
|
134
126
|
}
|
|
@@ -138,45 +130,30 @@ export abstract class Dao<R, T extends Model<R>> implements ModelRepr {
|
|
|
138
130
|
if(!instances.length) return Promise.resolve([]);
|
|
139
131
|
const now = on || new Date();
|
|
140
132
|
instances.forEach(instance => instance.updatedOn = now);
|
|
141
|
-
const props = instances.map(instance =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
('(' + new Array(this.nFields).fill(undefined).map((__, i: number) => '$' + (j * this.nFields + i + 1)).join(', ') + ')')).join(',') + ' RETURNING id',
|
|
151
|
-
resolvedProps);
|
|
152
|
-
}).then(q => q.rows.map(r => {
|
|
153
|
-
const idNum = parseInt(r.id, 10);
|
|
154
|
-
if(String(idNum) !== q.rows[0].id) return r.id;
|
|
155
|
-
return idNum;
|
|
156
|
-
}));
|
|
133
|
+
const props = [].concat.apply([], instances.map(instance => this.serialize(instance)));
|
|
134
|
+
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
135
|
+
+ ' VALUES' + instances.map((_, j) =>
|
|
136
|
+
('(' + new Array(this.nFields).fill(undefined).map((__, i: number) => '$' + (j * this.nFields + i + 1)).join(', ') + ')')).join(',') + ' RETURNING id',
|
|
137
|
+
props).then(q => q.rows.map(r => {
|
|
138
|
+
const idNum = parseInt(r.id, 10);
|
|
139
|
+
if(String(idNum) !== q.rows[0].id) return r.id;
|
|
140
|
+
return idNum;
|
|
141
|
+
}));
|
|
157
142
|
}
|
|
158
143
|
|
|
159
|
-
protected abstract buildObject(q: any): T
|
|
144
|
+
protected abstract buildObject(q: any): T;
|
|
160
145
|
|
|
161
|
-
protected abstract serialize(instance: T, request?: Request): any[]
|
|
162
|
-
|
|
163
|
-
protected buildObjectWrapper(q: any): T | Promise<T> {
|
|
164
|
-
return this.buildObject(q);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
protected serializeWrapper(instance: T, request?: Request): any[] | Promise<any[]> {
|
|
168
|
-
return this.serialize(instance, request);
|
|
169
|
-
}
|
|
146
|
+
protected abstract serialize(instance: T, request?: Request): any[];
|
|
170
147
|
}
|
|
171
148
|
|
|
172
149
|
export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
173
150
|
get(id: R, client?: ClientBase | Transaction, lock = true): Promise<T> {
|
|
174
151
|
if(this.pool) {
|
|
175
|
-
return (<ClientBase | Pool>(client || this.pool)).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.
|
|
152
|
+
return (<ClientBase | Pool>(client || this.pool)).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
|
|
176
153
|
} else {
|
|
177
154
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
178
155
|
request.input('1', id);
|
|
179
|
-
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id=@1').then((q: any) => this.
|
|
156
|
+
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id=@1').then((q: any) => this.buildObject(q.recordsets[0][0]));
|
|
180
157
|
}
|
|
181
158
|
}
|
|
182
159
|
|
|
@@ -193,12 +170,12 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
193
170
|
getList(ids: R[], client?: ClientBase | Transaction, lock = true): Promise<T[]> {
|
|
194
171
|
if(this.pool) {
|
|
195
172
|
return (<ClientBase | Pool>(client || this.pool)).query(this.selectDefinition + ' FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
|
|
196
|
-
.then(q =>
|
|
173
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
197
174
|
} else {
|
|
198
175
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
199
176
|
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + ' WHERE id IN ('
|
|
200
177
|
+ (ids.length > 0 ? (typeof ids[0] === 'string' ? '\'' + ids.join('\',\'') + '\'' : ids.join(',')) : '') + ')')
|
|
201
|
-
.then((q: any) =>
|
|
178
|
+
.then((q: any) => q.recordsets[0].map((r: any) => this.buildObject(r)));
|
|
202
179
|
}
|
|
203
180
|
}
|
|
204
181
|
|
|
@@ -206,13 +183,13 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
206
183
|
if(this.pool) {
|
|
207
184
|
return (<ClientBase | Pool>(client || this.pool)).query(this.selectDefinition + ' FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
208
185
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
209
|
-
.then(q =>
|
|
186
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
210
187
|
} else {
|
|
211
188
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
212
189
|
if(where) where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
213
190
|
return request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
214
191
|
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : ''))
|
|
215
|
-
.then((q: any) =>
|
|
192
|
+
.then((q: any) => q.recordsets[0].map((r: any) => this.buildObject(r)));
|
|
216
193
|
}
|
|
217
194
|
}
|
|
218
195
|
|
|
@@ -220,10 +197,9 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
220
197
|
if(this.pool) {
|
|
221
198
|
return (<ClientBase | Pool>(client || this.pool)).query(this.selectDefinition + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
222
199
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
223
|
-
.then(q =>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
count
|
|
200
|
+
.then(q => ({
|
|
201
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
202
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
227
203
|
}));
|
|
228
204
|
} else {
|
|
229
205
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
@@ -232,11 +208,9 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
232
208
|
() => request.query(this.selectDefinition + ' FROM ' + this.table + ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
233
209
|
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
234
210
|
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
235
|
-
])
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
views,
|
|
239
|
-
count
|
|
211
|
+
]).then(([q1, q2]: [any, any]) => ({
|
|
212
|
+
views: q1.recordsets[0].map((r: any) => this.buildObject(r)),
|
|
213
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p: number, n: any) => p + n.cnt, 0) : 0
|
|
240
214
|
}));
|
|
241
215
|
}
|
|
242
216
|
}
|
|
@@ -245,10 +219,9 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
245
219
|
if(this.pool) {
|
|
246
220
|
return (<ClientBase | Pool>(client || this.pool)).query('SELECT ' + cols.map(r => r.indexOf(' ') > -1 ? r : ('"' + r + '"')).join(',') + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
247
221
|
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
248
|
-
.then(q =>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
count
|
|
222
|
+
.then(q => ({
|
|
223
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
224
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
252
225
|
}));
|
|
253
226
|
} else {
|
|
254
227
|
const request = (<Transaction | ConnectionPool>(client || this.poolMssql)).request();
|
|
@@ -258,11 +231,9 @@ export abstract class ModelDao<R, T extends Model<R>> extends Dao<R, T> {
|
|
|
258
231
|
+ ((client && lock) ? ' WITH (UPDLOCK, ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
259
232
|
+ (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
260
233
|
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
261
|
-
])
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
views,
|
|
265
|
-
count
|
|
234
|
+
]).then(([q1, q2]: [any, any]) => ({
|
|
235
|
+
views: q1.recordsets[0].map((r: any) => this.buildObject(r)),
|
|
236
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p: number, n: any) => p + n.cnt, 0) : 0
|
|
266
237
|
}));
|
|
267
238
|
}
|
|
268
239
|
}
|
package/package.json
CHANGED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import {createCipheriv, createDecipheriv, randomFill} from 'crypto';
|
|
2
|
-
import {promisify} from 'util';
|
|
3
|
-
import {Model, ModelDao} from './ModelDao';
|
|
4
|
-
import {Request} from 'mssql';
|
|
5
|
-
|
|
6
|
-
const randomFillAsync = promisify(randomFill);
|
|
7
|
-
|
|
8
|
-
export abstract class CryptModelDao<R, T extends Model<R>> extends ModelDao<R, T> {
|
|
9
|
-
private static readonly ALGORITHM = 'aes-256-cbc';
|
|
10
|
-
|
|
11
|
-
private key: Buffer;
|
|
12
|
-
private encryptedColumns: string[];
|
|
13
|
-
|
|
14
|
-
constructor(keyBase64: string, encryptedColumns: string[], ...args: ConstructorParameters<typeof ModelDao>) {
|
|
15
|
-
super(...args);
|
|
16
|
-
this.key = Buffer.from(keyBase64, 'base64');
|
|
17
|
-
this.encryptedColumns = encryptedColumns;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
protected serializeWrapper(instance: T, request?: Request): Promise<any[]> | any[] {
|
|
21
|
-
const props = this.serialize(instance, request) as any[];
|
|
22
|
-
const encryptPromises: Promise<void>[] = [];
|
|
23
|
-
this.encryptedColumns.forEach(col => {
|
|
24
|
-
// Parse "protected updateDefinition: string" to find index
|
|
25
|
-
// e.g. updateDefinition = '"name"=$1,secret=$2,age=$3' -> secret
|
|
26
|
-
const idx = this.updateDefinition.split(',').findIndex(def => def.trim().startsWith('"' + col + '"') || def.trim().startsWith(col + '='));
|
|
27
|
-
if(idx >= 0) {
|
|
28
|
-
const val = request ? request.parameters[idx] : props[idx];
|
|
29
|
-
if(val !== null && val !== undefined) {
|
|
30
|
-
const encryptPromise = this.encrypt(String(val)).then(encrypted => {
|
|
31
|
-
if(request) request.replaceInput(String(idx + 1), encrypted);
|
|
32
|
-
else props[idx] = encrypted;
|
|
33
|
-
});
|
|
34
|
-
encryptPromises.push(encryptPromise);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
if(encryptPromises.length > 0) {
|
|
39
|
-
return Promise.all(encryptPromises).then(() => props);
|
|
40
|
-
}
|
|
41
|
-
return props;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
protected buildObjectWrapper(row: any): T | Promise<T> {
|
|
45
|
-
const decryptPromises: Promise<void>[] = [];
|
|
46
|
-
this.encryptedColumns.forEach(col => {
|
|
47
|
-
if(row[col] !== null && row[col] !== undefined) {
|
|
48
|
-
const decryptPromise = this.decrypt(String(row[col])).then(decrypted => {
|
|
49
|
-
row[col] = decrypted;
|
|
50
|
-
});
|
|
51
|
-
decryptPromises.push(decryptPromise);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
if(decryptPromises.length > 0) {
|
|
55
|
-
return Promise.all(decryptPromises).then(() => this.buildObject(row));
|
|
56
|
-
}
|
|
57
|
-
return this.buildObject(row);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private async encrypt(decrypted: string): Promise<string> {
|
|
61
|
-
const iv = new Uint8Array(16);
|
|
62
|
-
await randomFillAsync(iv);
|
|
63
|
-
const cipher = createCipheriv(CryptModelDao.ALGORITHM, this.key, iv);
|
|
64
|
-
let encrypted = '';
|
|
65
|
-
cipher.setEncoding('base64');
|
|
66
|
-
cipher.on('data', (chunk) => encrypted += chunk);
|
|
67
|
-
cipher.write(decrypted);
|
|
68
|
-
cipher.end(); // Blocking
|
|
69
|
-
return `$$enc$$:${Buffer.from(iv).toString('base64')}${encrypted}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private async decrypt(encrypted: string): Promise<string> {
|
|
73
|
-
if(!encrypted.startsWith('$$enc$$:')) {
|
|
74
|
-
return encrypted;
|
|
75
|
-
}
|
|
76
|
-
encrypted = encrypted.slice(8);
|
|
77
|
-
const iv = new Uint8Array(Buffer.from(encrypted.slice(0, 24), 'base64'));
|
|
78
|
-
const decipher = createDecipheriv(CryptModelDao.ALGORITHM, this.key, iv);
|
|
79
|
-
let decrypted = '';
|
|
80
|
-
decipher.on('readable', () => {
|
|
81
|
-
for(let chunk = decipher.read(); chunk !== null; chunk = decipher.read()) {
|
|
82
|
-
decrypted += chunk.toString('base64');
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
decipher.write(encrypted.slice(24), 'base64');
|
|
86
|
-
decipher.end(); // Blocking
|
|
87
|
-
return Buffer.from(decrypted, 'base64').toString('utf8');
|
|
88
|
-
}
|
|
89
|
-
}
|