anote-server-libs 0.2.8 → 0.2.9
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/models/ApiCall.js +37 -0
- package/models/Migration.js +35 -0
- package/models/repository/BaseModelRepository.js +156 -0
- package/models/repository/MemoryCache.js +92 -0
- package/models/repository/ModelDao.js +239 -0
- package/package.json +1 -1
- package/services/WithBody.js +60 -0
- package/services/WithTransaction.js +131 -0
- package/services/utils.js +197 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiCallRepository = void 0;
|
|
4
|
+
const mssql_1 = require("mssql");
|
|
5
|
+
const ModelDao_1 = require("./repository/ModelDao");
|
|
6
|
+
class ApiCallRepository extends ModelDao_1.ModelDao {
|
|
7
|
+
constructor(pool, poolMssql, logger) {
|
|
8
|
+
super(pool, poolMssql, logger, 'api_call', 5, pool ? 'id=$1,"updatedOn"=$2,"responseCode"=$3,"responseJson"=$4,"expiresAt"=$5' :
|
|
9
|
+
'id=@1,"updatedOn"=@2,"responseCode"=@3,"responseJson"=@4,"expiresAt"=@5');
|
|
10
|
+
this.pool = pool;
|
|
11
|
+
this.poolMssql = poolMssql;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
buildObject(q) {
|
|
15
|
+
if (!q)
|
|
16
|
+
return undefined;
|
|
17
|
+
q.id = q.id.trim();
|
|
18
|
+
q.updatedOn = q.updatedOn && new Date(q.updatedOn);
|
|
19
|
+
q.responseCode = parseInt(q.responseCode, 10);
|
|
20
|
+
q.expiresAt = q.expiresAt && new Date(q.expiresAt);
|
|
21
|
+
return q;
|
|
22
|
+
}
|
|
23
|
+
serialize(instance, request) {
|
|
24
|
+
if (request) {
|
|
25
|
+
request.input('1', instance.id);
|
|
26
|
+
request.input('2', mssql_1.DateTimeOffset, instance.updatedOn);
|
|
27
|
+
request.input('3', instance.responseCode);
|
|
28
|
+
request.input('4', instance.responseJson);
|
|
29
|
+
request.input('5', mssql_1.DateTimeOffset, instance.expiresAt);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return [instance.id, instance.updatedOn, instance.responseCode, instance.responseJson, instance.expiresAt];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.ApiCallRepository = ApiCallRepository;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationRepository = void 0;
|
|
4
|
+
const ModelDao_1 = require("./repository/ModelDao");
|
|
5
|
+
class MigrationRepository extends ModelDao_1.ModelDao {
|
|
6
|
+
constructor(pool, poolMssql, logger) {
|
|
7
|
+
super(pool, poolMssql, logger, 'migration', 5, pool ? 'id=$1,hash=$2,"sqlUp"=$3,"sqlDown"=$4,state=$5' :
|
|
8
|
+
'id=@1,hash=@2,"sqlUp"=@3,"sqlDown"=@4,state=@5');
|
|
9
|
+
this.pool = pool;
|
|
10
|
+
this.poolMssql = poolMssql;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
}
|
|
13
|
+
buildObject(q) {
|
|
14
|
+
if (!q)
|
|
15
|
+
return undefined;
|
|
16
|
+
q.id = parseInt(q.id, 10);
|
|
17
|
+
q.hash = q.hash.trim();
|
|
18
|
+
q.state = parseInt(q.state, 10);
|
|
19
|
+
return q;
|
|
20
|
+
}
|
|
21
|
+
serialize(instance, request) {
|
|
22
|
+
if (request) {
|
|
23
|
+
request.input('1', instance.id);
|
|
24
|
+
request.input('2', instance.hash);
|
|
25
|
+
request.input('3', instance.sqlUp);
|
|
26
|
+
request.input('4', instance.sqlDown);
|
|
27
|
+
request.input('5', instance.state);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
return [instance.id, instance.hash, instance.sqlUp, instance.sqlDown, instance.state];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.MigrationRepository = MigrationRepository;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseModelRepository = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const node_forge_1 = require("node-forge");
|
|
6
|
+
const ApiCall_1 = require("../ApiCall");
|
|
7
|
+
const Migration_1 = require("../Migration");
|
|
8
|
+
class BaseModelRepository {
|
|
9
|
+
constructor(db, dbSpare, dbMssql, logger, cache) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.dbSpare = dbSpare;
|
|
12
|
+
this.dbMssql = dbMssql;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.cache = cache;
|
|
15
|
+
if (db && dbSpare) {
|
|
16
|
+
const dbQuery = db.query.bind(db);
|
|
17
|
+
db.query = (function (text, values, cb) {
|
|
18
|
+
if ((this.idleCount + this.waitingCount) >= this.totalCount && this.totalCount === this.options.max)
|
|
19
|
+
return dbSpare.query(text, values, cb);
|
|
20
|
+
return dbQuery(text, values, cb);
|
|
21
|
+
}).bind(db);
|
|
22
|
+
}
|
|
23
|
+
this.Migration = new Migration_1.MigrationRepository(db, dbMssql, logger);
|
|
24
|
+
this.ApiCall = new ApiCall_1.ApiCallRepository(db, dbMssql, logger);
|
|
25
|
+
}
|
|
26
|
+
migrate(migrationsPath, callback) {
|
|
27
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
28
|
+
'id integer PRIMARY KEY,' +
|
|
29
|
+
'hash text NOT NULL,' +
|
|
30
|
+
'"sqlUp" text NOT NULL,' +
|
|
31
|
+
'"sqlDown" text NOT NULL,' +
|
|
32
|
+
'state integer NOT NULL' +
|
|
33
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
34
|
+
'id int PRIMARY KEY,' +
|
|
35
|
+
'hash text NOT NULL,' +
|
|
36
|
+
'"sqlUp" text NOT NULL,' +
|
|
37
|
+
'"sqlDown" text NOT NULL,' +
|
|
38
|
+
'state int NOT NULL' +
|
|
39
|
+
')')).then(() => {
|
|
40
|
+
this.Migration.getAllBy('id').then((migrations) => {
|
|
41
|
+
if (migrations.find(migration => migration.state !== 0))
|
|
42
|
+
process.exit(4);
|
|
43
|
+
fs.readdir(migrationsPath, (_, files) => {
|
|
44
|
+
const migrationsAvailable = files
|
|
45
|
+
.filter(file => /[0-9]+\.sql/.test(file))
|
|
46
|
+
.map(file => parseInt(file.split('.sql')[0], 10))
|
|
47
|
+
.filter(file => file > 0)
|
|
48
|
+
.sort((a, b) => a - b)
|
|
49
|
+
.map(file => {
|
|
50
|
+
const content = fs.readFileSync(migrationsPath + file + '.sql', 'utf-8');
|
|
51
|
+
return {
|
|
52
|
+
id: file,
|
|
53
|
+
content: content,
|
|
54
|
+
hash: node_forge_1.md.sha256.create().update(content).digest().toHex()
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
if (migrationsAvailable.length === 0
|
|
58
|
+
|| migrationsAvailable.length
|
|
59
|
+
!== migrationsAvailable[migrationsAvailable.length - 1].id)
|
|
60
|
+
process.exit(5);
|
|
61
|
+
let highestCommon = 0;
|
|
62
|
+
while (highestCommon < migrations.length && highestCommon < migrationsAvailable.length
|
|
63
|
+
&& migrations[highestCommon].hash === migrationsAvailable[highestCommon].hash)
|
|
64
|
+
highestCommon++;
|
|
65
|
+
this.applyDownUntil(migrations, migrations.length, highestCommon).then(() => {
|
|
66
|
+
this.applyUpUntil(migrationsAvailable, highestCommon, migrationsAvailable.length).then(callback, process.exit);
|
|
67
|
+
}, process.exit);
|
|
68
|
+
});
|
|
69
|
+
}, () => process.exit(3));
|
|
70
|
+
}, () => process.exit(2));
|
|
71
|
+
}
|
|
72
|
+
lockTables(tables, client) {
|
|
73
|
+
if (this.db)
|
|
74
|
+
return Promise.all(tables.map(t => client.query('LOCK TABLE ' + t.table + ' IN EXCLUSIVE MODE')));
|
|
75
|
+
return Promise.all(tables.map(t => client.request().query('SELECT id FROM ' + t.table + ' WITH (UPDLOCK)')));
|
|
76
|
+
}
|
|
77
|
+
applyUpUntil(migrations, current, until) {
|
|
78
|
+
if (current < until)
|
|
79
|
+
return this.applyUp(migrations[current]).then(() => this.applyUpUntil(migrations, current + 1, until));
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
applyUp(migration) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const sqlParts = migration.content.split('----');
|
|
85
|
+
this.Migration.create({
|
|
86
|
+
id: migration.id,
|
|
87
|
+
hash: migration.hash,
|
|
88
|
+
sqlUp: sqlParts[0],
|
|
89
|
+
sqlDown: sqlParts[1],
|
|
90
|
+
state: 2
|
|
91
|
+
}).then(() => {
|
|
92
|
+
(this.db || this.dbMssql).query(sqlParts[0], (err) => {
|
|
93
|
+
if (err) {
|
|
94
|
+
console.error(err);
|
|
95
|
+
reject(10);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
(this.db || this.dbMssql).query('UPDATE "migration" SET "state"=0 WHERE "id"=' + migration.id, (err2) => {
|
|
99
|
+
if (err2)
|
|
100
|
+
reject(11);
|
|
101
|
+
else
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}, () => process.exit(9));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
applyDownUntil(migrations, current, until) {
|
|
110
|
+
if (current > until) {
|
|
111
|
+
current--;
|
|
112
|
+
return this.applyDown(migrations[current]).then(() => this.applyDownUntil(migrations, current, until));
|
|
113
|
+
}
|
|
114
|
+
return Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
applyDown(migration) {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
(this.db || this.dbMssql).query('UPDATE "migration" SET "state"=1 WHERE "id"=' + migration.id, (err) => {
|
|
119
|
+
if (err)
|
|
120
|
+
reject(6);
|
|
121
|
+
else
|
|
122
|
+
(this.db || this.dbMssql).query(migration.sqlDown, (err2) => {
|
|
123
|
+
if (err2) {
|
|
124
|
+
console.error(err2);
|
|
125
|
+
reject(7);
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
(this.db || this.dbMssql).query('DELETE FROM "migration" WHERE "id"=' + migration.id, (err3) => {
|
|
129
|
+
if (err3 && migration.id !== 1)
|
|
130
|
+
reject(8);
|
|
131
|
+
else {
|
|
132
|
+
if (migration.id === 1) {
|
|
133
|
+
(this.db ? this.db.query('CREATE TABLE IF NOT EXISTS migration (' +
|
|
134
|
+
'id integer PRIMARY KEY,' +
|
|
135
|
+
'hash text NOT NULL,' +
|
|
136
|
+
'"sqlUp" text NOT NULL,' +
|
|
137
|
+
'"sqlDown" text NOT NULL,' +
|
|
138
|
+
'state integer NOT NULL' +
|
|
139
|
+
')') : this.dbMssql.query('if not exists (select * from sysobjects where name=\'migration\' and xtype=\'U\') CREATE TABLE migration (' +
|
|
140
|
+
'id int PRIMARY KEY,' +
|
|
141
|
+
'hash text NOT NULL,' +
|
|
142
|
+
'"sqlUp" text NOT NULL,' +
|
|
143
|
+
'"sqlDown" text NOT NULL,' +
|
|
144
|
+
'state int NOT NULL' +
|
|
145
|
+
')')).then(() => resolve(), reject);
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
resolve();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.BaseModelRepository = BaseModelRepository;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryCache = void 0;
|
|
4
|
+
class MemoryCache {
|
|
5
|
+
constructor(localKey, cache) {
|
|
6
|
+
this.localKey = localKey;
|
|
7
|
+
this.cache = cache;
|
|
8
|
+
this.localCache = {};
|
|
9
|
+
}
|
|
10
|
+
list() {
|
|
11
|
+
if (this.cache)
|
|
12
|
+
return new Promise(resolve => {
|
|
13
|
+
let pending = true;
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
if (pending) {
|
|
16
|
+
pending = false;
|
|
17
|
+
resolve([]);
|
|
18
|
+
}
|
|
19
|
+
}, 250);
|
|
20
|
+
this.cache.items((_, data) => {
|
|
21
|
+
if (pending) {
|
|
22
|
+
pending = false;
|
|
23
|
+
resolve(data.map(s => {
|
|
24
|
+
const value = s[Object.getOwnPropertyNames(s).find(sname => sname.startsWith(this.localKey))];
|
|
25
|
+
return value && JSON.parse(value);
|
|
26
|
+
}).filter(x => x));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
else
|
|
31
|
+
return Promise.resolve(Object.getOwnPropertyNames(this.localCache).map(key => this.localCache[key]));
|
|
32
|
+
}
|
|
33
|
+
get(key) {
|
|
34
|
+
if (this.cache)
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
let pending = true;
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
if (pending) {
|
|
39
|
+
pending = false;
|
|
40
|
+
resolve(undefined);
|
|
41
|
+
}
|
|
42
|
+
}, 250);
|
|
43
|
+
this.cache.get(this.localKey + key, (_, data) => {
|
|
44
|
+
if (pending) {
|
|
45
|
+
pending = false;
|
|
46
|
+
resolve(data && JSON.parse(data));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
else
|
|
51
|
+
return Promise.resolve(this.localCache[key]);
|
|
52
|
+
}
|
|
53
|
+
set(key, val) {
|
|
54
|
+
if (this.cache)
|
|
55
|
+
return new Promise(resolve => this.cache.add(this.localKey + key, JSON.stringify(val), 15 * 60, resolve));
|
|
56
|
+
else {
|
|
57
|
+
this.localCache[key] = val;
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
delete(key) {
|
|
62
|
+
if (this.cache) {
|
|
63
|
+
return new Promise(resolve => {
|
|
64
|
+
let fired = false;
|
|
65
|
+
const handler = setTimeout(() => {
|
|
66
|
+
fired = true;
|
|
67
|
+
resolve();
|
|
68
|
+
}, 50);
|
|
69
|
+
this.cache.del(this.localKey + key, () => {
|
|
70
|
+
if (!fired) {
|
|
71
|
+
clearTimeout(handler);
|
|
72
|
+
resolve();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.localCache[key] = undefined;
|
|
79
|
+
return Promise.resolve();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
clear() {
|
|
83
|
+
if (this.cache) {
|
|
84
|
+
this.localKey = this.localKey + '_';
|
|
85
|
+
if (this.localKey.length > 40)
|
|
86
|
+
this.localKey = this.localKey.replace(/_+$/, '');
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
this.localCache = {};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.MemoryCache = MemoryCache;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ModelDao = exports.Dao = exports.promiseAllStepN = void 0;
|
|
4
|
+
function promiseAllStepN(n, list) {
|
|
5
|
+
if (!list || !list.length)
|
|
6
|
+
return Promise.resolve([]);
|
|
7
|
+
const tail = list.splice(n);
|
|
8
|
+
const head = list;
|
|
9
|
+
const resolved = [];
|
|
10
|
+
let processed = 0;
|
|
11
|
+
return new Promise(resolve => {
|
|
12
|
+
head.forEach(x => {
|
|
13
|
+
const res = x();
|
|
14
|
+
resolved.push(res);
|
|
15
|
+
res.then((y) => {
|
|
16
|
+
runNext();
|
|
17
|
+
return y;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
function runNext() {
|
|
21
|
+
if (processed === tail.length) {
|
|
22
|
+
resolve(Promise.all(resolved));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
resolved.push(tail[processed]().then((x) => {
|
|
26
|
+
runNext();
|
|
27
|
+
return x;
|
|
28
|
+
}));
|
|
29
|
+
processed++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
exports.promiseAllStepN = promiseAllStepN;
|
|
35
|
+
Promise.allConcurrent = (n) => (list) => promiseAllStepN(n, list);
|
|
36
|
+
class Dao {
|
|
37
|
+
constructor(pool, poolMssql, logger, table, nFields, updateDefinition) {
|
|
38
|
+
this.pool = pool;
|
|
39
|
+
this.poolMssql = poolMssql;
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.table = table;
|
|
42
|
+
this.nFields = nFields;
|
|
43
|
+
this.updateDefinition = updateDefinition;
|
|
44
|
+
}
|
|
45
|
+
groupResultSet(q, key) {
|
|
46
|
+
const storage = {};
|
|
47
|
+
for (let i = 0; i < q.length; i++) {
|
|
48
|
+
storage[q[i][key]] = storage[q[i][key]] || [];
|
|
49
|
+
storage[q[i][key]].push(q[i]);
|
|
50
|
+
}
|
|
51
|
+
return Object.getOwnPropertyNames(storage).map(k => storage[k]);
|
|
52
|
+
}
|
|
53
|
+
groupResultSetBy(q, key) {
|
|
54
|
+
const storage = {};
|
|
55
|
+
for (let i = 0; i < q.length; i++) {
|
|
56
|
+
storage[key(q[i])] = storage[key(q[i])] || [];
|
|
57
|
+
storage[key(q[i])].push(q[i]);
|
|
58
|
+
}
|
|
59
|
+
return Object.getOwnPropertyNames(storage).map(k => storage[k]);
|
|
60
|
+
}
|
|
61
|
+
update(instance, client) {
|
|
62
|
+
if (instance.archivedOn)
|
|
63
|
+
return Promise.reject('Record archived!');
|
|
64
|
+
instance.updatedOn = new Date();
|
|
65
|
+
if (this.pool) {
|
|
66
|
+
const props = this.serialize(instance);
|
|
67
|
+
props.push(instance.id);
|
|
68
|
+
return client.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=$' + (this.nFields + 1), props).then(() => instance.id);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const request = client.request();
|
|
72
|
+
this.serialize(instance, request);
|
|
73
|
+
request.input(String(this.nFields + 1), instance.id);
|
|
74
|
+
return request.query('UPDATE ' + this.table + ' SET ' + this.updateDefinition + ' WHERE id=@' + (this.nFields + 1)).then(() => instance.id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
create(instance, client) {
|
|
78
|
+
instance.createdOn = instance.updatedOn = new Date();
|
|
79
|
+
if (this.pool) {
|
|
80
|
+
const props = this.serialize(instance);
|
|
81
|
+
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
82
|
+
+ ' VALUES(' + new Array(this.nFields).fill(undefined).map((_, i) => '$' + (i + 1)).join(',') + ') RETURNING id', props).then(q => {
|
|
83
|
+
const idNum = parseInt(q.rows[0].id, 10);
|
|
84
|
+
if (String(idNum) !== q.rows[0].id)
|
|
85
|
+
return q.rows[0].id;
|
|
86
|
+
return idNum;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const request = (client || this.poolMssql).request();
|
|
91
|
+
this.serialize(instance, request);
|
|
92
|
+
return request.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(',') + '); SELECT SCOPE_IDENTITY() AS id').then(q => {
|
|
94
|
+
return q.recordsets[0][0].id || instance.id;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
createSeveral(instances, client) {
|
|
99
|
+
if (!instances.length)
|
|
100
|
+
return Promise.resolve([]);
|
|
101
|
+
const now = new Date();
|
|
102
|
+
instances.forEach(instance => instance.updatedOn = now);
|
|
103
|
+
const props = [].concat.apply([], instances.map(instance => this.serialize(instance)));
|
|
104
|
+
return (client || this.pool).query('INSERT INTO ' + this.table + '(' + this.updateDefinition.replace(/=\$\d+/g, '').replace(/=[^)]+\)/g, '') + ')'
|
|
105
|
+
+ ' 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 => {
|
|
106
|
+
const idNum = parseInt(r.id, 10);
|
|
107
|
+
if (String(idNum) !== q.rows[0].id)
|
|
108
|
+
return r.id;
|
|
109
|
+
return idNum;
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.Dao = Dao;
|
|
114
|
+
class ModelDao extends Dao {
|
|
115
|
+
get(id, client, lock = true) {
|
|
116
|
+
if (this.pool) {
|
|
117
|
+
return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=$1' + ((client && lock) ? ' FOR UPDATE' : ''), [id]).then(q => this.buildObject(q.rows[0]));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const request = (client || this.poolMssql).request();
|
|
121
|
+
request.input('1', id);
|
|
122
|
+
return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id=@1').then(q => this.buildObject(q.recordsets[0][0]));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
count(where, inputs = [], client) {
|
|
126
|
+
if (this.pool) {
|
|
127
|
+
return (client || this.pool).query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''), inputs).then(q => parseInt(q.rows[0].cnt, 10));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const request = (client || this.poolMssql).request();
|
|
131
|
+
if (where)
|
|
132
|
+
where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
133
|
+
return request.query('SELECT count(*) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '')).then(q => q.recordsets[0][0].cnt);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
getList(ids, client, lock = true) {
|
|
137
|
+
if (this.pool) {
|
|
138
|
+
return (client || this.pool).query('SELECT * FROM ' + this.table + ' WHERE id=ANY($1)' + ((client && lock) ? ' FOR UPDATE' : ''), [ids])
|
|
139
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const request = (client || this.poolMssql).request();
|
|
143
|
+
return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + ' WHERE id IN ('
|
|
144
|
+
+ (ids.length > 0 ? (typeof ids[0] === 'string' ? '\'' + ids.join('\',\'') + '\'' : ids.join(',')) : '') + ')')
|
|
145
|
+
.then(q => q.recordsets[0].map(r => this.buildObject(r)));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
getAllBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
149
|
+
if (this.pool) {
|
|
150
|
+
return (client || this.pool).query('SELECT * FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
151
|
+
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
152
|
+
.then(q => q.rows.map(r => this.buildObject(r)));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const request = (client || this.poolMssql).request();
|
|
156
|
+
if (where)
|
|
157
|
+
where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
158
|
+
return request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
159
|
+
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : ''))
|
|
160
|
+
.then(q => q.recordsets[0].map(r => this.buildObject(r)));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
getViewCountBy(order, offset, limit, where, inputs = [], client, lock = true) {
|
|
164
|
+
if (this.pool) {
|
|
165
|
+
return (client || this.pool).query('SELECT *, COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
166
|
+
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
167
|
+
.then(q => ({
|
|
168
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
169
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const request = (client || this.poolMssql).request();
|
|
174
|
+
if (where)
|
|
175
|
+
where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
176
|
+
return Promise.allConcurrent(1)([
|
|
177
|
+
() => request.query('SELECT * FROM ' + this.table + ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '')
|
|
178
|
+
+ (order ? (' ORDER BY ' + order) : '') + (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
179
|
+
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
180
|
+
]).then(([q1, q2]) => ({
|
|
181
|
+
views: q1.recordsets[0].map((r) => this.buildObject(r)),
|
|
182
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
getRowsViewCountBy(rows, order, offset, limit, where, inputs = [], client, lock = true) {
|
|
187
|
+
if (this.pool) {
|
|
188
|
+
return (client || this.pool).query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ', COUNT(*) OVER() AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
189
|
+
+ (offset ? (' OFFSET ' + offset) : '') + (limit !== undefined ? (' LIMIT ' + limit) : '') + ((client && lock) ? ' FOR UPDATE' : ''), inputs)
|
|
190
|
+
.then(q => ({
|
|
191
|
+
views: q.rows.map(r => this.buildObject(r)),
|
|
192
|
+
count: q.rows.length ? parseInt(q.rows[0].cnt, 10) : 0
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const request = (client || this.poolMssql).request();
|
|
197
|
+
if (where)
|
|
198
|
+
where.match(/(@\d+)/g).forEach((match, i) => request.input(match.substr(1), inputs[i]));
|
|
199
|
+
return Promise.allConcurrent(1)([
|
|
200
|
+
() => request.query('SELECT ' + rows.map(r => '"' + r + '"').join(',') + ' FROM ' + this.table
|
|
201
|
+
+ ((client && lock) ? ' WITH (ROWLOCK)' : '') + (where ? (' WHERE ' + where) : '') + (order ? (' ORDER BY ' + order) : '')
|
|
202
|
+
+ (offset !== undefined ? (' OFFSET ' + offset + ' ROWS') : '') + (limit !== undefined ? (' FETCH NEXT ' + limit + ' ROWS ONLY') : '')),
|
|
203
|
+
() => request.query('SELECT COUNT(DISTINCT id) AS cnt FROM ' + this.table + (where ? (' WHERE ' + where) : ''))
|
|
204
|
+
]).then(([q1, q2]) => ({
|
|
205
|
+
views: q1.recordsets[0].map((r) => this.buildObject(r)),
|
|
206
|
+
count: q2.recordsets.length ? q2.recordsets[0].reduce((p, n) => p + n.cnt, 0) : 0
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
archive(id, replacedById, client) {
|
|
211
|
+
if (this.pool) {
|
|
212
|
+
return client.query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=$1 WHERE id=$2', [replacedById, id]);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const request = client.request();
|
|
216
|
+
request.input('1', replacedById);
|
|
217
|
+
request.input('2', id);
|
|
218
|
+
return request.query('UPDATE ' + this.table + ' SET "archivedOn"=now(),replaced_by_id=@1 WHERE id=@2');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
replace(instance, client) {
|
|
222
|
+
if (instance.archivedOn)
|
|
223
|
+
return Promise.reject('Record archived!');
|
|
224
|
+
return this.create({ ...instance, replaced_id: instance.id }, client).then(id => {
|
|
225
|
+
return this.archive(instance.id, id, client).then(() => id);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
delete(id, client) {
|
|
229
|
+
if (this.pool) {
|
|
230
|
+
return client.query('DELETE FROM ' + this.table + ' WHERE id=$1', [id]);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const request = client.request();
|
|
234
|
+
request.input('1', id);
|
|
235
|
+
return request.query('DELETE FROM ' + this.table + ' WHERE id=@1');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.ModelDao = ModelDao;
|
package/package.json
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WithBody = void 0;
|
|
4
|
+
const jsonschema_1 = require("jsonschema");
|
|
5
|
+
function WithBody(schema) {
|
|
6
|
+
const validator = new jsonschema_1.Validator();
|
|
7
|
+
validator.attributes.maxDigits = (instance, sc) => {
|
|
8
|
+
if (typeof instance !== 'number')
|
|
9
|
+
return undefined;
|
|
10
|
+
if (typeof sc.maxDigits !== 'number' || Math.floor(sc.maxDigits) !== sc.maxDigits) {
|
|
11
|
+
throw new jsonschema_1.SchemaError('"maxDigits" expects an integer', sc);
|
|
12
|
+
}
|
|
13
|
+
if (Math.round(instance * 10 ** sc.maxDigits) / (10 ** sc.maxDigits) !== instance) {
|
|
14
|
+
return 'has more precision than ' + sc.maxDigits + ' digits';
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
18
|
+
return function (_, __, descriptor) {
|
|
19
|
+
if (typeof descriptor.value === 'function') {
|
|
20
|
+
const previousMethod = descriptor.value;
|
|
21
|
+
descriptor.value = function (req, res) {
|
|
22
|
+
const keys = Object.getOwnPropertyNames(schema.properties);
|
|
23
|
+
keys.forEach(key => {
|
|
24
|
+
if (typeof schema.properties[key] === 'string') {
|
|
25
|
+
schema.properties[key] = this.config.app.endpointsSchemas[schema.properties[key]];
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (req.method.toUpperCase() !== 'POST' && req.method.toUpperCase() !== 'PUT') {
|
|
29
|
+
return previousMethod.call(this, req, res);
|
|
30
|
+
}
|
|
31
|
+
if (!req.body) {
|
|
32
|
+
res.status(400).json({
|
|
33
|
+
error: {
|
|
34
|
+
errorKey: 'client.body.missing',
|
|
35
|
+
additionalInfo: 'client.extended.badPayload'
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const result = validator.validate(req.body, schema);
|
|
41
|
+
if (result.valid) {
|
|
42
|
+
return previousMethod.call(this, req, res);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
res.status(400).json({
|
|
46
|
+
error: {
|
|
47
|
+
errorKey: 'client.body.missing',
|
|
48
|
+
additionalInfo: 'client.extended.badPayload',
|
|
49
|
+
detailedInfo: result.errors
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return descriptor;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
exports.WithBody = WithBody;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withTransaction = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function withTransaction(repo, logger, previousMethod, lock) {
|
|
6
|
+
return function (req, res, next) {
|
|
7
|
+
const endTerminator = res.end.bind(res);
|
|
8
|
+
const jsonTerminator = (obj) => {
|
|
9
|
+
res.write((0, utils_1.jsonStringify)(obj) || '{}');
|
|
10
|
+
endTerminator();
|
|
11
|
+
};
|
|
12
|
+
const connectTimeoutHandler = setTimeout(() => {
|
|
13
|
+
logger.error('Error timed out getting a client, exiting...');
|
|
14
|
+
process.exit(22);
|
|
15
|
+
}, 3000);
|
|
16
|
+
Promise.all([
|
|
17
|
+
repo.db ? repo.db.connect() : Promise.resolve(undefined),
|
|
18
|
+
repo.dbMssql ? Promise.resolve(repo.dbMssql.transaction()) : Promise.resolve(undefined)
|
|
19
|
+
]).then(([c1, c2]) => {
|
|
20
|
+
const dbClient = c1 || c2;
|
|
21
|
+
clearTimeout(connectTimeoutHandler);
|
|
22
|
+
utils_1.utils.logger = logger;
|
|
23
|
+
dbClient.removeListener('error', utils_1.utils.clientErrorHandler);
|
|
24
|
+
dbClient.on('error', utils_1.utils.clientErrorHandler);
|
|
25
|
+
res.locals.dbClient = dbClient;
|
|
26
|
+
res.locals.dbClientCommited = false;
|
|
27
|
+
res.locals.dbClientCommit = (cb) => {
|
|
28
|
+
if (!res.locals.dbClientCommited) {
|
|
29
|
+
res.locals.dbClientCommited = true;
|
|
30
|
+
(repo.db ? dbClient.query('COMMIT') : dbClient.commit()).catch((err) => err).then((err) => {
|
|
31
|
+
if (repo.db)
|
|
32
|
+
dbClient.release();
|
|
33
|
+
if (!(err instanceof Error)) {
|
|
34
|
+
for (let i = 0; i < res.locals.dbClientOnCommit.length; i++) {
|
|
35
|
+
res.locals.dbClientOnCommit[i]();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
cb(err instanceof Error ? err : undefined);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
cb(undefined);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
res.locals.dbClientOnCommit = [];
|
|
46
|
+
return (repo.db ? dbClient.query('BEGIN') : dbClient.begin()).then(() => {
|
|
47
|
+
const finish = () => {
|
|
48
|
+
res.json = (obj) => {
|
|
49
|
+
if (res.statusCode > 303 && res.statusCode !== 412) {
|
|
50
|
+
if (logger && res.statusCode > 499) {
|
|
51
|
+
logger.error('Uncaught 500: %j', obj.error.additionalInfo);
|
|
52
|
+
}
|
|
53
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch((err) => obj.error.additionalInfo2 = { message: err.message }).then(() => {
|
|
54
|
+
if (repo.db)
|
|
55
|
+
dbClient.release();
|
|
56
|
+
jsonTerminator(obj);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
res.locals.dbClientCommit((err) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
res.status(500);
|
|
63
|
+
jsonTerminator({
|
|
64
|
+
error: {
|
|
65
|
+
errorKey: 'internal.db',
|
|
66
|
+
additionalInfo: { message: err.message }
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
jsonTerminator(obj);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return res;
|
|
75
|
+
};
|
|
76
|
+
res.end = () => {
|
|
77
|
+
if (res.statusCode > 303 && res.statusCode !== 412) {
|
|
78
|
+
if (logger && res.statusCode > 499) {
|
|
79
|
+
logger.error('Uncaught 500 with no details...');
|
|
80
|
+
}
|
|
81
|
+
(repo.db ? dbClient.query('ROLLBACK') : dbClient.rollback()).catch(() => undefined).then(() => {
|
|
82
|
+
if (repo.db)
|
|
83
|
+
dbClient.release();
|
|
84
|
+
endTerminator();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
res.locals.dbClientCommit((err) => {
|
|
89
|
+
if (err) {
|
|
90
|
+
res.status(500);
|
|
91
|
+
jsonTerminator({
|
|
92
|
+
error: {
|
|
93
|
+
errorKey: 'internal.db',
|
|
94
|
+
additionalInfo: { message: err.message }
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
endTerminator();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return res;
|
|
103
|
+
};
|
|
104
|
+
return previousMethod.call(this, req, res, next);
|
|
105
|
+
};
|
|
106
|
+
if (lock) {
|
|
107
|
+
dbClient.query('SELECT pg_advisory_xact_lock(' + lock + ')').then(() => finish()).catch((err) => {
|
|
108
|
+
res.status(500).json({
|
|
109
|
+
error: {
|
|
110
|
+
errorKey: 'internal.db',
|
|
111
|
+
additionalInfo: { message: err.message }
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
dbClient.release();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
finish();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}).catch(err => {
|
|
122
|
+
res.status(500).json({
|
|
123
|
+
error: {
|
|
124
|
+
errorKey: 'internal.db',
|
|
125
|
+
additionalInfo: { message: err.message }
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
exports.withTransaction = withTransaction;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.digitize = exports.fpEuros = exports.sendSelfPostableMessage = exports.idempotent = exports.jsonStringify = exports.lcm = exports.gcd = exports.utils = exports.clientErrorHandle = exports.btoa = exports.atob = void 0;
|
|
4
|
+
function atob(str) {
|
|
5
|
+
return Buffer.from(str, 'base64').toString('binary');
|
|
6
|
+
}
|
|
7
|
+
exports.atob = atob;
|
|
8
|
+
function btoa(str) {
|
|
9
|
+
return Buffer.from(str).toString('base64');
|
|
10
|
+
}
|
|
11
|
+
exports.btoa = btoa;
|
|
12
|
+
function clientErrorHandle(err) {
|
|
13
|
+
this.error('Error on DB client: %j', err);
|
|
14
|
+
}
|
|
15
|
+
exports.clientErrorHandle = clientErrorHandle;
|
|
16
|
+
exports.utils = {
|
|
17
|
+
clientErrorHandler: undefined
|
|
18
|
+
};
|
|
19
|
+
function gcdTwo(a, b) {
|
|
20
|
+
if (a === 0)
|
|
21
|
+
return b;
|
|
22
|
+
return gcdTwo(b % a, a);
|
|
23
|
+
}
|
|
24
|
+
function gcd(values) {
|
|
25
|
+
let result = values[0];
|
|
26
|
+
for (let i = 1; i < values.length; i++)
|
|
27
|
+
result = gcdTwo(values[i], result);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
exports.gcd = gcd;
|
|
31
|
+
function lcm(values) {
|
|
32
|
+
let l = 1, divisor = 2;
|
|
33
|
+
while (true) {
|
|
34
|
+
let counter = 0;
|
|
35
|
+
let divisible = false;
|
|
36
|
+
for (let i = 0; i < values.length; i++) {
|
|
37
|
+
if (values[i] === 0) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
else if (values[i] < 0) {
|
|
41
|
+
values[i] = values[i] * (-1);
|
|
42
|
+
}
|
|
43
|
+
if (values[i] === 1) {
|
|
44
|
+
counter++;
|
|
45
|
+
}
|
|
46
|
+
if (values[i] % divisor === 0) {
|
|
47
|
+
divisible = true;
|
|
48
|
+
values[i] = values[i] / divisor;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (divisible) {
|
|
52
|
+
l = l * divisor;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
divisor++;
|
|
56
|
+
}
|
|
57
|
+
if (counter === values.length) {
|
|
58
|
+
return l;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.lcm = lcm;
|
|
63
|
+
function jsonStringify(obj) {
|
|
64
|
+
const cache = {};
|
|
65
|
+
return JSON.stringify(obj, function (_, value) {
|
|
66
|
+
if (typeof value === 'object' && value !== null) {
|
|
67
|
+
if (cache[value] !== -1) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(JSON.stringify(value));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
cache[value] = true;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
exports.jsonStringify = jsonStringify;
|
|
81
|
+
function idempotent(repo, debug, logger) {
|
|
82
|
+
return function (req, res, next) {
|
|
83
|
+
let idempotenceKey = req.header('x-idempotent-key');
|
|
84
|
+
if (idempotenceKey) {
|
|
85
|
+
idempotenceKey = idempotenceKey.substring(0, 40);
|
|
86
|
+
repo.ApiCall.get(idempotenceKey).then(call => {
|
|
87
|
+
if (!call) {
|
|
88
|
+
const jsonTerminator = res.json;
|
|
89
|
+
const endTerminator = res.end;
|
|
90
|
+
const writeTerminator = res.write;
|
|
91
|
+
let response = '';
|
|
92
|
+
res.json = (function (obj) {
|
|
93
|
+
repo.ApiCall.create({
|
|
94
|
+
id: idempotenceKey,
|
|
95
|
+
responseCode: res.statusCode,
|
|
96
|
+
responseJson: jsonStringify(obj),
|
|
97
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
98
|
+
}).then(() => jsonTerminator.call(res, obj), err => {
|
|
99
|
+
if (err)
|
|
100
|
+
logger.warn('Cannot save idempotent key: %j', err);
|
|
101
|
+
jsonTerminator.call(res, obj);
|
|
102
|
+
});
|
|
103
|
+
}).bind(res);
|
|
104
|
+
res.end = (function (buf) {
|
|
105
|
+
if (buf)
|
|
106
|
+
response = response + buf.toString();
|
|
107
|
+
repo.ApiCall.create({
|
|
108
|
+
id: idempotenceKey,
|
|
109
|
+
responseCode: res.statusCode,
|
|
110
|
+
responseJson: response,
|
|
111
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
112
|
+
}).then(() => endTerminator.call(res, buf), err => {
|
|
113
|
+
if (err)
|
|
114
|
+
logger.warn('Cannot save idempotent key: %j', err);
|
|
115
|
+
endTerminator.call(res, buf);
|
|
116
|
+
});
|
|
117
|
+
}).bind(res);
|
|
118
|
+
res.write = (function (buf) {
|
|
119
|
+
if (buf)
|
|
120
|
+
response = response + buf.toString();
|
|
121
|
+
writeTerminator.call(res, buf);
|
|
122
|
+
}).bind(res);
|
|
123
|
+
next();
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
res.status(417).json({
|
|
127
|
+
responseCode: call.responseCode,
|
|
128
|
+
responseBody: call.responseJson
|
|
129
|
+
});
|
|
130
|
+
}, err => res.status(500).json({
|
|
131
|
+
error: {
|
|
132
|
+
errorKey: 'internal.db',
|
|
133
|
+
additionalInfo: { message: err.message, stack: debug && err.stack }
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
next();
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
exports.idempotent = idempotent;
|
|
142
|
+
function sendSelfPostableMessage(res, code, messageType, err) {
|
|
143
|
+
res.type('text/html').status(code).write(`
|
|
144
|
+
<!DOCTYPE HTML>
|
|
145
|
+
<html>
|
|
146
|
+
<head>
|
|
147
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
|
148
|
+
</head>
|
|
149
|
+
<body>
|
|
150
|
+
<script type="text/javascript">
|
|
151
|
+
window.parent.postMessage({
|
|
152
|
+
type: '${messageType}',
|
|
153
|
+
confirm: ${!err},
|
|
154
|
+
error: JSON.parse('${JSON.stringify(err) || 'null'}')
|
|
155
|
+
}, '*');
|
|
156
|
+
</script>
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
159
|
+
`);
|
|
160
|
+
res.end();
|
|
161
|
+
}
|
|
162
|
+
exports.sendSelfPostableMessage = sendSelfPostableMessage;
|
|
163
|
+
function fpEuros(n) {
|
|
164
|
+
return Math.round(n * 100) / 100;
|
|
165
|
+
}
|
|
166
|
+
exports.fpEuros = fpEuros;
|
|
167
|
+
function digitize(value, opts) {
|
|
168
|
+
if (value === undefined || value === null)
|
|
169
|
+
return 'undefined';
|
|
170
|
+
if (typeof value === 'number') {
|
|
171
|
+
if (isNaN(value))
|
|
172
|
+
return '-';
|
|
173
|
+
if (!isFinite(value))
|
|
174
|
+
return 'Infinite';
|
|
175
|
+
value = String(value);
|
|
176
|
+
}
|
|
177
|
+
else if (typeof value === 'string')
|
|
178
|
+
return value;
|
|
179
|
+
const parts = value.split('.');
|
|
180
|
+
const initialLength = parts[0].length;
|
|
181
|
+
for (let i = initialLength - 3; i > 0; i -= 3) {
|
|
182
|
+
parts[0] = parts[0].slice(0, i) + ',' + parts[0].slice(i);
|
|
183
|
+
}
|
|
184
|
+
if (parts[0].startsWith('-,'))
|
|
185
|
+
parts[0] = '-' + parts[0].slice(2);
|
|
186
|
+
if (parts[1]) {
|
|
187
|
+
const expDecimals = parts[1].split(/[eE]-/);
|
|
188
|
+
if (expDecimals.length > 1 && parseInt(expDecimals[1], 10) > 2)
|
|
189
|
+
return '0.00';
|
|
190
|
+
let decimals = fpEuros(parseFloat('0.' + parts[1])).toString().substr(2, 2);
|
|
191
|
+
if (decimals.length === 1)
|
|
192
|
+
decimals += '0';
|
|
193
|
+
return parts[0] + '.' + decimals;
|
|
194
|
+
}
|
|
195
|
+
return (opts && opts.currency) ? (parts[0] + '.00') : parts[0];
|
|
196
|
+
}
|
|
197
|
+
exports.digitize = digitize;
|