ti2 1.0.14 → 1.0.17
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/.eslintrc.js +1 -0
- package/.github/workflows/tests.yml +7 -5
- package/.nvmrc +1 -0
- package/api.yml +241 -0
- package/auth/authHandler.js +2 -2
- package/cli.js +36 -0
- package/closeAll.js +6 -0
- package/controllers/__tests__/admin.js +12 -9
- package/controllers/__tests__/app.js +57 -5
- package/controllers/__tests__/bookings.js +20 -10
- package/controllers/__tests__/ping.js +8 -3
- package/controllers/__tests__/user.js +9 -3
- package/controllers/admin.js +46 -1
- package/controllers/app.js +174 -1
- package/controllers/bookings.js +28 -0
- package/controllers/ping.js +2 -1
- package/controllers/user.js +2 -6
- package/index.js +44 -8
- package/jest.setup.js +11 -0
- package/migrations/20200128162519-deletedAtForIntegrations.js +0 -1
- package/migrations/20210408024232-expand-JWT-field-size.js +2 -2
- package/migrations/20220426153807-cronJobs.js +41 -0
- package/models/cronjobs.js +31 -0
- package/models/db.js +0 -1
- package/models/index.js +1 -0
- package/package.json +14 -3
- package/test/plugin.js +55 -13
- package/test/utils.js +3 -5
- package/test/worker.js +5 -0
- package/tutorials/plugin-development.md +6 -3
- package/tutorials/setup-your-instance.md +70 -18
- package/worker/index.js +53 -0
- package/worker/queue.js +88 -0
|
@@ -216,14 +216,17 @@ Ti2 uses [Sequelize v6.13](https://sequelize.org/v6) which is the database ORM w
|
|
|
216
216
|
|
|
217
217
|
Migrations should be run after the fact, from the root of ti2 instance like so:
|
|
218
218
|
|
|
219
|
-
|
|
220
219
|
```
|
|
221
|
-
$
|
|
220
|
+
$ ti2 dbapp [plugin name] migrate
|
|
222
221
|
```
|
|
223
222
|
|
|
224
223
|
We strongly encourage all the plugin's table names are prefixed with the plugin name (i.e. ti2-pluginName-cacheTable).
|
|
225
224
|
|
|
226
|
-
This should be executed after
|
|
225
|
+
This should be executed after all ti2 host migrations had been executed.
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
$ ti2 db migrate
|
|
229
|
+
```
|
|
227
230
|
|
|
228
231
|
## Scheduled Task (to be released on v2)
|
|
229
232
|
|
|
@@ -23,6 +23,7 @@ User + app integration credentials can be added after the app has been added to
|
|
|
23
23
|
A MySQl instance is required and the following environment variables are required:
|
|
24
24
|
|
|
25
25
|
- DB_URL (mysql connection in url format)
|
|
26
|
+
- REDIS_URL (a redis database url to store the background queue)
|
|
26
27
|
- dbCryptoKey (integration details encription key, should be a 32 chars base64 encoded random string)
|
|
27
28
|
- adminKey (a key for admin related requests)
|
|
28
29
|
- jwtKey (a key to encrypt user sessions request)
|
|
@@ -31,6 +32,7 @@ A MySQl instance is required and the following environment variables are require
|
|
|
31
32
|
|
|
32
33
|
```
|
|
33
34
|
DB_URL=mysql://root:@mysqlserver/ti2development
|
|
35
|
+
REDIS_URL=redis://redis:6379
|
|
34
36
|
dbCryptoKey=cG9VYmJyQ2tlSDVHeXQ4RUN4VlNieEJRejNYWDlZU0g=
|
|
35
37
|
jwtSecret=C8k0mrHVfWVTP7pIoZEHRrvFgTULhw3E4swDq1aoDH4P
|
|
36
38
|
frontendKey=IpOiLahrGQxsQimwoK7Z
|
|
@@ -52,7 +54,7 @@ $ npm i ti2 ti2-tourconnect
|
|
|
52
54
|
After installing the ti2 package you need to run the database migrations for ti2:
|
|
53
55
|
|
|
54
56
|
```
|
|
55
|
-
$
|
|
57
|
+
$ ti2 db migrate
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
### Entry File
|
|
@@ -61,21 +63,57 @@ then just create an entry file like this :
|
|
|
61
63
|
|
|
62
64
|
```javascript
|
|
63
65
|
// index.js
|
|
64
|
-
const ti2 = require('ti2')({
|
|
65
|
-
plugins: {
|
|
66
|
-
travelgate: require('ti2-travelgate'),
|
|
67
|
-
ventrata: require('ti2-ventrata'),
|
|
68
|
-
tourconnect: require('ti2-tourconnect'),
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
66
|
|
|
73
|
-
|
|
67
|
+
module.exports = (async () => {
|
|
68
|
+
const ti2 = await require('ti2')({
|
|
69
|
+
plugins: {
|
|
70
|
+
ventrata: require('ti2-ventrata'),
|
|
71
|
+
travelgate: require('ti2-travelgate'),
|
|
72
|
+
tourconnect: require('ti2-tourconnect'),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
return ti2;
|
|
76
|
+
})();
|
|
74
77
|
|
|
75
78
|
```
|
|
79
|
+
|
|
80
|
+
and start the server:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
76
83
|
$ node index.js
|
|
77
84
|
```
|
|
78
85
|
|
|
86
|
+
in order to support the background job queue (required by some plugins) you should consider the following example:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const plugins = {
|
|
90
|
+
ventrata: require('ti2-ventrata'),
|
|
91
|
+
travelgate: require('ti2-travelgate'),
|
|
92
|
+
tourconnect: require('ti2-tourconnect'),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
module.exports = (async () => {
|
|
96
|
+
if (process.argv.indexOf('worker') > 0) {
|
|
97
|
+
const worker = await require('ti2')({
|
|
98
|
+
plugins,
|
|
99
|
+
worker: true,
|
|
100
|
+
});
|
|
101
|
+
return worker;
|
|
102
|
+
} else {
|
|
103
|
+
const ti2 = await require('ti2')({
|
|
104
|
+
plugins,
|
|
105
|
+
});
|
|
106
|
+
return ti2;
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
and start a background worker (on a second terminal), required for some plugins:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
$ node index.js worker
|
|
115
|
+
```
|
|
116
|
+
|
|
79
117
|
## API logging
|
|
80
118
|
|
|
81
119
|
Currently logging is supported via elastic search, an elasticLogClient value can ben passed down, example:
|
|
@@ -87,12 +125,26 @@ const elasticLogsClient = (() => {
|
|
|
87
125
|
return new Client({ node: elasticLogs });
|
|
88
126
|
})();
|
|
89
127
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
128
|
+
const plugins = {
|
|
129
|
+
ventrata: require('ti2-ventrata'),
|
|
130
|
+
travelgate: require('ti2-travelgate'),
|
|
131
|
+
tourconnect: require('ti2-tourconnect'),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
module.exports = (async () => {
|
|
135
|
+
if (process.argv.indexOf('worker') > 0) {
|
|
136
|
+
const worker = await require('ti2')({
|
|
137
|
+
elasticLogsClient,
|
|
138
|
+
plugins,
|
|
139
|
+
worker: true,
|
|
140
|
+
});
|
|
141
|
+
return worker;
|
|
142
|
+
} else {
|
|
143
|
+
const ti2 = await require('ti2')({
|
|
144
|
+
elasticLogsClient,
|
|
145
|
+
plugins,
|
|
146
|
+
});
|
|
147
|
+
return ti2;
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
98
150
|
```
|
package/worker/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const throng = require('throng');
|
|
2
|
+
require('util').inspect.defaultOptions.depth = null;
|
|
3
|
+
const fakePlugin = require('../test/plugin');
|
|
4
|
+
|
|
5
|
+
const { queue, saveResult } = require('./queue');
|
|
6
|
+
|
|
7
|
+
const workers = process.env.WEB_CONCURRENCY || 2;
|
|
8
|
+
|
|
9
|
+
const maxJobsPerWorker = 1;
|
|
10
|
+
|
|
11
|
+
const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
|
|
12
|
+
console.log(`Started worker ${id}`);
|
|
13
|
+
|
|
14
|
+
const bye = () => {
|
|
15
|
+
console.log(`Worker ${id} exiting`);
|
|
16
|
+
disconnect();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
process.on('SIGTERM', bye);
|
|
21
|
+
process.on('SIGUSR2', bye);
|
|
22
|
+
process.on('SIGINT', bye);
|
|
23
|
+
|
|
24
|
+
queue.process(maxJobsPerWorker, async job => {
|
|
25
|
+
const {
|
|
26
|
+
data: {
|
|
27
|
+
pluginName,
|
|
28
|
+
method,
|
|
29
|
+
userId,
|
|
30
|
+
inTesting,
|
|
31
|
+
},
|
|
32
|
+
id: jobId,
|
|
33
|
+
data: params,
|
|
34
|
+
} = job;
|
|
35
|
+
console.log(`job ${jobId} > running ${pluginName}:${method} for ${userId}`);
|
|
36
|
+
const plugins = await (async () => {
|
|
37
|
+
if (inTesting) {
|
|
38
|
+
return [...pluginsParam, await new fakePlugin({ name: pluginName })];
|
|
39
|
+
}
|
|
40
|
+
return pluginsParam;
|
|
41
|
+
})();
|
|
42
|
+
const thePlugin = plugins.find(({ name }) => name === pluginName);
|
|
43
|
+
const resultValue = await thePlugin[method](params);
|
|
44
|
+
console.log(`job ${jobId} > completed`);
|
|
45
|
+
await saveResult({ id: jobId, resultValue });
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = async args => throng({
|
|
50
|
+
workers,
|
|
51
|
+
worker: worker(args),
|
|
52
|
+
signals: ['SIGUSR2', 'SIGTERM', 'SIGINT'],
|
|
53
|
+
});
|
package/worker/queue.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const Queue = require('bull');
|
|
2
|
+
const Redis = require('ioredis');
|
|
3
|
+
const R = require('ramda');
|
|
4
|
+
|
|
5
|
+
const REDIS_URL = process.env.REDIS_URL || 'redis://redis:6379';
|
|
6
|
+
const itemsTTL = 3 * 60 * 60; // 3 hours
|
|
7
|
+
|
|
8
|
+
const queue = new Queue('work', `${REDIS_URL}/0`);
|
|
9
|
+
const redisResults = new Redis(`${REDIS_URL}/1`);
|
|
10
|
+
|
|
11
|
+
const getPending = async () => {
|
|
12
|
+
const pending = await queue.count();
|
|
13
|
+
const running = await queue.getActiveCount();
|
|
14
|
+
return pending + running;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const allDone = async () => {
|
|
18
|
+
const left1 = await getPending();
|
|
19
|
+
if (left1 === 0) return true;
|
|
20
|
+
return (new Promise(resolve => {
|
|
21
|
+
queue.on('global:completed', async () => {
|
|
22
|
+
const left = await getPending();
|
|
23
|
+
if (left === 0) resolve();
|
|
24
|
+
});
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
queue.on('failed', (job, err) => {
|
|
29
|
+
console.log(`job ${job.id} failed`, err);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const addJob = async (payload, paramsParam) => {
|
|
33
|
+
const params = paramsParam || {};
|
|
34
|
+
const inTesting = Boolean(process.env.JEST_WORKER_ID);
|
|
35
|
+
const id = R.path(['id'], await queue.add({
|
|
36
|
+
...payload,
|
|
37
|
+
inTesting,
|
|
38
|
+
}, {
|
|
39
|
+
removeOnComplete: true,
|
|
40
|
+
...params,
|
|
41
|
+
}));
|
|
42
|
+
return id;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const saveResult = async ({ id, resultValue }) => {
|
|
46
|
+
await redisResults.set(id, JSON.stringify(resultValue), 'EX', itemsTTL);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const jobStatus = async ({ jobId }) => {
|
|
50
|
+
try {
|
|
51
|
+
const job = await queue.getJob(jobId);
|
|
52
|
+
if (!job) {
|
|
53
|
+
// check if it is done
|
|
54
|
+
const result = JSON.parse(await redisResults.get(jobId));
|
|
55
|
+
// await global.sleep();
|
|
56
|
+
if (result) {
|
|
57
|
+
return {
|
|
58
|
+
jobId,
|
|
59
|
+
status: 'success',
|
|
60
|
+
result,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { // 404
|
|
64
|
+
jobId,
|
|
65
|
+
status: 'failed',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const state = await job.getState();
|
|
69
|
+
return {
|
|
70
|
+
jobId,
|
|
71
|
+
status: state.toLowerCase(),
|
|
72
|
+
};
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.log(`could not get job status for ${jobId}`, err);
|
|
75
|
+
return { // 500
|
|
76
|
+
jobId,
|
|
77
|
+
status: 'failed',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
module.exports = {
|
|
82
|
+
addJob,
|
|
83
|
+
allDone,
|
|
84
|
+
jobStatus,
|
|
85
|
+
queue,
|
|
86
|
+
saveResult,
|
|
87
|
+
redisResults,
|
|
88
|
+
};
|