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
package/controllers/app.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
const jwt = require('jwt-promise');
|
|
2
2
|
const { omit } = require('ramda');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const { Umzug, SequelizeStorage } = require('umzug');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const Sequelize = require('sequelize');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const bb = require('bluebird');
|
|
9
|
+
const R = require('ramda');
|
|
10
|
+
|
|
3
11
|
const sqldb = require('../models');
|
|
12
|
+
const {
|
|
13
|
+
queue,
|
|
14
|
+
addJob,
|
|
15
|
+
jobStatus,
|
|
16
|
+
} = require('../worker/queue');
|
|
4
17
|
|
|
5
18
|
const { env: { jwtSecret } } = process;
|
|
6
19
|
|
|
@@ -42,6 +55,61 @@ const createAppToken = async (req, res, next) => {
|
|
|
42
55
|
});
|
|
43
56
|
if (userAppKeyDup) await userAppKeyDup.destroy();
|
|
44
57
|
const newAppKey = await sqldb.UserAppKey.create(payload);
|
|
58
|
+
// create any cronjobs related to the app
|
|
59
|
+
await bb.each(req.app.plugins, async plugin => {
|
|
60
|
+
if (Array.isArray(plugin.jobs)) {
|
|
61
|
+
await bb.each(plugin.jobs.filter(job => Boolean(job.cron)), async job => {
|
|
62
|
+
const where = {
|
|
63
|
+
pluginName: plugin.name,
|
|
64
|
+
pluginJobId: job.method,
|
|
65
|
+
userId,
|
|
66
|
+
hint,
|
|
67
|
+
cron: job.cron,
|
|
68
|
+
};
|
|
69
|
+
const existing = await sqldb.CronJobs.findOne({
|
|
70
|
+
where: R.omit(['cron'], where),
|
|
71
|
+
});
|
|
72
|
+
const jobPayload = {
|
|
73
|
+
...where,
|
|
74
|
+
...job.payload,
|
|
75
|
+
};
|
|
76
|
+
const jobParams = {
|
|
77
|
+
...(job.cron ? {
|
|
78
|
+
repeat: {
|
|
79
|
+
cron: job.cron,
|
|
80
|
+
},
|
|
81
|
+
} : {}),
|
|
82
|
+
...(job.params || {}),
|
|
83
|
+
removeOnComplete: false,
|
|
84
|
+
};
|
|
85
|
+
let bullJobId;
|
|
86
|
+
if (existing) {
|
|
87
|
+
const bullJob = await queue.getJob(job.bullJobId);
|
|
88
|
+
if (!bullJob) {
|
|
89
|
+
bullJobId = await addJob(jobPayload, jobParams);
|
|
90
|
+
existing.bullJobId = bullJobId;
|
|
91
|
+
await existing.save();
|
|
92
|
+
} else {
|
|
93
|
+
// make sure the cron is the same
|
|
94
|
+
const bullCron = R.path(
|
|
95
|
+
['opts', 'repeat', 'cron'],
|
|
96
|
+
await queue.getJob(bullJobId),
|
|
97
|
+
);
|
|
98
|
+
if (bullCron !== job.cron) {
|
|
99
|
+
await queue.removeJobs(bullJobId);
|
|
100
|
+
bullJobId = await addJob(jobPayload, jobParams);
|
|
101
|
+
existing.bullJobId = bullJobId;
|
|
102
|
+
await existing.save();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
bullJobId = await addJob(jobPayload, jobParams);
|
|
107
|
+
await sqldb.CronJobs.create({ ...where, bullJobId });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
45
113
|
return res.json({ value: newAppKey.get('id').toString() });
|
|
46
114
|
} catch (err) {
|
|
47
115
|
return next(err);
|
|
@@ -83,9 +151,114 @@ const listAppTokens = async (req, res, next) => {
|
|
|
83
151
|
}
|
|
84
152
|
};
|
|
85
153
|
|
|
154
|
+
const migrateApp = async ({ integrationId, action }) => {
|
|
155
|
+
const { sequelize } = sqldb;
|
|
156
|
+
assert(integrationId);
|
|
157
|
+
assert(action);
|
|
158
|
+
const migrationsPath = path.join(
|
|
159
|
+
__dirname,
|
|
160
|
+
'../',
|
|
161
|
+
'../',
|
|
162
|
+
`ti2-${integrationId}`,
|
|
163
|
+
'migrations',
|
|
164
|
+
);
|
|
165
|
+
try {
|
|
166
|
+
await fs.access(migrationsPath);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
throw Error(`Could not find any migrations for ${integrationId} on ${migrationsPath}`);
|
|
169
|
+
}
|
|
170
|
+
const umzug = new Umzug({
|
|
171
|
+
migrations: {
|
|
172
|
+
glob: `${migrationsPath}/*.js`,
|
|
173
|
+
resolve: ({ name, path: migPath, context }) => {
|
|
174
|
+
const migration = require(migPath);
|
|
175
|
+
return {
|
|
176
|
+
name,
|
|
177
|
+
up: async () => migration.up(context, Sequelize),
|
|
178
|
+
down: async () => migration.down(context, Sequelize),
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
context: sequelize.getQueryInterface(),
|
|
183
|
+
storage: new SequelizeStorage({
|
|
184
|
+
sequelize,
|
|
185
|
+
tableName: `SequelizeMeta-${integrationId}`,
|
|
186
|
+
}),
|
|
187
|
+
logger: console,
|
|
188
|
+
});
|
|
189
|
+
if (action === 'migrate') {
|
|
190
|
+
return umzug.up();
|
|
191
|
+
}
|
|
192
|
+
if (action === 'revert') {
|
|
193
|
+
return umzug.down({ to: 0 });
|
|
194
|
+
}
|
|
195
|
+
throw Error('No recognized action');
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const getAppScheduledJobs = async ({
|
|
199
|
+
integrationId,
|
|
200
|
+
userId,
|
|
201
|
+
hint,
|
|
202
|
+
}) => {
|
|
203
|
+
const where = R.reject(R.isNil)({
|
|
204
|
+
pluginName: integrationId,
|
|
205
|
+
userId,
|
|
206
|
+
hint,
|
|
207
|
+
});
|
|
208
|
+
const jobs = await sqldb.CronJobs.findAll({
|
|
209
|
+
where,
|
|
210
|
+
raw: true,
|
|
211
|
+
});
|
|
212
|
+
return { jobs };
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const runAppJob = async (req, res, next) => {
|
|
216
|
+
const {
|
|
217
|
+
body: {
|
|
218
|
+
payload,
|
|
219
|
+
jobParams,
|
|
220
|
+
},
|
|
221
|
+
params: {
|
|
222
|
+
app: pluginName,
|
|
223
|
+
hint,
|
|
224
|
+
userId,
|
|
225
|
+
},
|
|
226
|
+
} = req;
|
|
227
|
+
try {
|
|
228
|
+
const bullJobId = await addJob({
|
|
229
|
+
...payload,
|
|
230
|
+
pluginName,
|
|
231
|
+
hint,
|
|
232
|
+
userId,
|
|
233
|
+
}, jobParams);
|
|
234
|
+
assert(bullJobId);
|
|
235
|
+
return res.json(await jobStatus({ jobId: bullJobId }));
|
|
236
|
+
} catch (err) {
|
|
237
|
+
return next(err);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const getJobStatus = async (req, res, next) => {
|
|
242
|
+
const {
|
|
243
|
+
params: {
|
|
244
|
+
jobId,
|
|
245
|
+
},
|
|
246
|
+
} = req;
|
|
247
|
+
try {
|
|
248
|
+
const returnValue = await jobStatus({ jobId });
|
|
249
|
+
return res.json(returnValue);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return next(err);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
86
255
|
module.exports = {
|
|
87
|
-
jwtEncode,
|
|
88
256
|
createAppToken,
|
|
89
257
|
deleteAppToken,
|
|
258
|
+
getAppScheduledJobs,
|
|
259
|
+
getJobStatus,
|
|
260
|
+
jwtEncode,
|
|
90
261
|
listAppTokens,
|
|
262
|
+
migrateApp,
|
|
263
|
+
runAppJob,
|
|
91
264
|
};
|
package/controllers/bookings.js
CHANGED
|
@@ -110,6 +110,33 @@ const bookingsAvailabilitySearch = plugins => async (req, res, next) => {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
+
const bookingsAvailabilityCalendar = plugins => async (req, res, next) => {
|
|
114
|
+
const {
|
|
115
|
+
params: { appKey, userId, hint },
|
|
116
|
+
body: payload,
|
|
117
|
+
} = req;
|
|
118
|
+
try {
|
|
119
|
+
const app = plugins.find(({ name }) => name === appKey);
|
|
120
|
+
// const app = load(appKey);
|
|
121
|
+
const userAppKeys = (await UserAppKey.findOne({
|
|
122
|
+
where: {
|
|
123
|
+
userId,
|
|
124
|
+
integrationId: appKey,
|
|
125
|
+
...(hint ? { hint } : {}),
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
assert(userAppKeys, 'could not find the app key');
|
|
129
|
+
const token = userAppKeys.appKey;
|
|
130
|
+
const results = await app.availabilityCalendar({
|
|
131
|
+
token,
|
|
132
|
+
payload,
|
|
133
|
+
});
|
|
134
|
+
return res.json(results);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
return next(err);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
113
140
|
const searchQuote = plugins => async (req, res, next) => {
|
|
114
141
|
const {
|
|
115
142
|
params: { appKey, userId, hint },
|
|
@@ -171,6 +198,7 @@ module.exports = plugins => ({
|
|
|
171
198
|
bookingsCancel: bookingsCancel(plugins),
|
|
172
199
|
bookingsProductSearch: bookingsProductSearch(plugins),
|
|
173
200
|
bookingsAvailabilitySearch: bookingsAvailabilitySearch(plugins),
|
|
201
|
+
bookingsAvailabilityCalendar: bookingsAvailabilityCalendar(plugins),
|
|
174
202
|
searchQuote: searchQuote(plugins),
|
|
175
203
|
createBooking: createBooking(plugins),
|
|
176
204
|
});
|
package/controllers/ping.js
CHANGED
package/controllers/user.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// controllers/user.js
|
|
2
|
-
const { omit
|
|
3
|
-
const assert = require('assert');
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
|
-
const moment = require('moment');
|
|
2
|
+
const { omit } = require('ramda');
|
|
6
3
|
|
|
7
|
-
const { UserAppKey
|
|
8
|
-
// const { load } = require('../../plugins/index.js');
|
|
4
|
+
const { UserAppKey } = require('../models');
|
|
9
5
|
|
|
10
6
|
const userAppList = async (req, res, next) => {
|
|
11
7
|
const { params: { userId } } = req;
|
package/index.js
CHANGED
|
@@ -5,6 +5,9 @@ const swaggerUi = require('swagger-ui-express');
|
|
|
5
5
|
const yaml = require('js-yaml');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const { pickBy } = require('ramda');
|
|
8
|
+
const bb = require('bluebird');
|
|
9
|
+
const { Op } = require('sequelize');
|
|
10
|
+
const R = require('ramda');
|
|
8
11
|
|
|
9
12
|
const schema = yaml.load(fs.readFileSync(`${__dirname}/api.yml`));
|
|
10
13
|
|
|
@@ -14,18 +17,20 @@ const adminController = require('./controllers/admin');
|
|
|
14
17
|
const appController = require('./controllers/app');
|
|
15
18
|
const userController = require('./controllers/user');
|
|
16
19
|
const bookingsController = require('./controllers/bookings');
|
|
20
|
+
const { Integration } = require('./models');
|
|
17
21
|
|
|
18
|
-
module.exports = ({
|
|
22
|
+
module.exports = async ({
|
|
19
23
|
apiDocs = true,
|
|
20
24
|
elasticLogIndex = 'apilog_ti2',
|
|
21
25
|
elasticLogsClient,
|
|
22
26
|
plugins: pluginsParam = {},
|
|
23
27
|
port: portParam,
|
|
24
28
|
startServer = true,
|
|
29
|
+
worker = false,
|
|
25
30
|
}) => {
|
|
26
31
|
const port = portParam || process.env.PORT || 10010;
|
|
27
|
-
|
|
28
|
-
const plugins = Object.entries(pluginsParam)
|
|
32
|
+
// create the plugin instances
|
|
33
|
+
const plugins = await bb.map(Object.entries(pluginsParam), async ([pluginName, Plugin]) => {
|
|
29
34
|
// pass all env variables
|
|
30
35
|
const pluginEnv = pickBy(
|
|
31
36
|
(_val, key) => key.substring(0, `ti2_${pluginName}`.length)
|
|
@@ -37,9 +42,34 @@ module.exports = ({
|
|
|
37
42
|
const nuName = attr.replace(/_/g, '-').replace(`ti2-${pluginName}-`, '');
|
|
38
43
|
params[nuName] = value;
|
|
39
44
|
});
|
|
40
|
-
|
|
45
|
+
const pluginInstance = await new Plugin({ name: pluginName, ...params });
|
|
46
|
+
return pluginInstance;
|
|
47
|
+
});
|
|
48
|
+
if (worker) {
|
|
49
|
+
return require('./worker/index')({ plugins });
|
|
50
|
+
}
|
|
51
|
+
// make sure all plugins have a DB entry
|
|
52
|
+
const pluginNames = plugins.map(R.prop('name'));
|
|
53
|
+
const matchedIntegrations = await Integration.findAll({
|
|
54
|
+
attributes: ['name'],
|
|
55
|
+
where: {
|
|
56
|
+
name: {
|
|
57
|
+
[Op.in]: pluginNames,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
raw: true,
|
|
41
61
|
});
|
|
42
|
-
|
|
62
|
+
const missingIntegrations = R.difference(pluginNames, matchedIntegrations.map(R.prop('name')));
|
|
63
|
+
if (missingIntegrations.length > 0) {
|
|
64
|
+
// need to crete the missing integrations
|
|
65
|
+
await Integration.bulkCreate(missingIntegrations.map(name => ({
|
|
66
|
+
name,
|
|
67
|
+
packageName: `ti2-${name}`,
|
|
68
|
+
adminEmail: `ti2+${name}@localhost.local`,
|
|
69
|
+
})));
|
|
70
|
+
}
|
|
71
|
+
// create the API Web server
|
|
72
|
+
const app = express();
|
|
43
73
|
const api = {
|
|
44
74
|
...pingController,
|
|
45
75
|
...adminController,
|
|
@@ -141,9 +171,12 @@ module.exports = ({
|
|
|
141
171
|
connect(app);
|
|
142
172
|
app.use(middleware.mock());
|
|
143
173
|
// global error Handling
|
|
144
|
-
app.use((err, req, res
|
|
174
|
+
app.use((err, req, res) => {
|
|
145
175
|
// console.log(req.headers.['X-Request-Id'], err);
|
|
146
176
|
res.status(err.status || 500);
|
|
177
|
+
if ((process.env.JEST_WORKER_ID)) {
|
|
178
|
+
console.debug(err);
|
|
179
|
+
}
|
|
147
180
|
return res.json({
|
|
148
181
|
message: err.message || 'Internal Error',
|
|
149
182
|
});
|
|
@@ -156,8 +189,11 @@ module.exports = ({
|
|
|
156
189
|
// first create a generic "terminator"
|
|
157
190
|
const terminator = sig => {
|
|
158
191
|
if (typeof sig === 'string') {
|
|
159
|
-
console.log(
|
|
160
|
-
|
|
192
|
+
console.log(
|
|
193
|
+
'%s: Received %s - terminating ti2 ...',
|
|
194
|
+
Date(Date.now()),
|
|
195
|
+
sig,
|
|
196
|
+
);
|
|
161
197
|
process.exit(1);
|
|
162
198
|
}
|
|
163
199
|
console.log('%s: Node server stopped.', Date(Date.now()));
|
package/jest.setup.js
ADDED
|
@@ -3,8 +3,8 @@ module.exports = {
|
|
|
3
3
|
type: Sequelize.TEXT,
|
|
4
4
|
allowNull: false,
|
|
5
5
|
}),
|
|
6
|
-
down: (queryInterface, Sequelize) =>
|
|
6
|
+
down: (queryInterface, Sequelize) => queryInterface.changeColumn('UserAppKeys', 'appKey', {
|
|
7
7
|
type: Sequelize.STRING,
|
|
8
8
|
allowNull: false,
|
|
9
|
-
})
|
|
9
|
+
}),
|
|
10
10
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
up: (queryInterface, Sequelize) => queryInterface
|
|
3
|
+
.createTable('CronJobs', {
|
|
4
|
+
pluginName: {
|
|
5
|
+
allowNull: false,
|
|
6
|
+
primaryKey: true,
|
|
7
|
+
type: Sequelize.STRING,
|
|
8
|
+
},
|
|
9
|
+
userId: {
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
type: Sequelize.STRING,
|
|
13
|
+
},
|
|
14
|
+
hint: {
|
|
15
|
+
allowNull: false,
|
|
16
|
+
primaryKey: true,
|
|
17
|
+
type: Sequelize.STRING,
|
|
18
|
+
},
|
|
19
|
+
pluginJobId: {
|
|
20
|
+
allowNull: false,
|
|
21
|
+
type: Sequelize.STRING,
|
|
22
|
+
},
|
|
23
|
+
bullJobId: {
|
|
24
|
+
allowNull: false,
|
|
25
|
+
type: Sequelize.STRING,
|
|
26
|
+
},
|
|
27
|
+
cron: {
|
|
28
|
+
allowNull: false,
|
|
29
|
+
type: Sequelize.STRING,
|
|
30
|
+
},
|
|
31
|
+
createdAt: {
|
|
32
|
+
allowNull: false,
|
|
33
|
+
type: Sequelize.DATE,
|
|
34
|
+
},
|
|
35
|
+
updatedAt: {
|
|
36
|
+
allowNull: false,
|
|
37
|
+
type: Sequelize.DATE,
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
down: queryInterface => queryInterface.dropTable('CronJobs'),
|
|
41
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const Sequelize = require('sequelize');
|
|
2
|
+
const db = require('./db');
|
|
3
|
+
|
|
4
|
+
const CronJobs = db.define('CronJobs', {
|
|
5
|
+
pluginName: {
|
|
6
|
+
type: Sequelize.STRING,
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
},
|
|
9
|
+
userId: {
|
|
10
|
+
type: Sequelize.STRING,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
},
|
|
13
|
+
hint: {
|
|
14
|
+
type: Sequelize.STRING,
|
|
15
|
+
primaryKey: true,
|
|
16
|
+
},
|
|
17
|
+
pluginJobId: {
|
|
18
|
+
type: Sequelize.STRING,
|
|
19
|
+
primaryKey: true,
|
|
20
|
+
},
|
|
21
|
+
bullJobId: {
|
|
22
|
+
type: Sequelize.STRING,
|
|
23
|
+
allowNull: false,
|
|
24
|
+
},
|
|
25
|
+
cron: {
|
|
26
|
+
type: Sequelize.STRING,
|
|
27
|
+
allowNull: false,
|
|
28
|
+
},
|
|
29
|
+
}, {});
|
|
30
|
+
|
|
31
|
+
module.exports = CronJobs;
|
package/models/db.js
CHANGED
package/models/index.js
CHANGED
|
@@ -10,6 +10,7 @@ fs
|
|
|
10
10
|
.readdirSync(__dirname)
|
|
11
11
|
.filter(file => (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'))
|
|
12
12
|
.forEach(file => {
|
|
13
|
+
// eslint-disable-next-line import/no-dynamic-require, global-require
|
|
13
14
|
const model = require(path.join(__dirname, file));
|
|
14
15
|
models[model.name] = model;
|
|
15
16
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ti2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Tourist Industry Exchange (TI2)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,19 +8,28 @@
|
|
|
8
8
|
"sequelize": "sequelize-cli",
|
|
9
9
|
"doc": "jsdoc -c jsdoc.conf.json -u tutorials"
|
|
10
10
|
},
|
|
11
|
+
"jest": {
|
|
12
|
+
"setupFilesAfterEnv": [
|
|
13
|
+
"./jest.setup.js"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
11
16
|
"repository": {
|
|
12
17
|
"type": "git",
|
|
13
18
|
"url": "git+https://github.com/ti2travel/ti2.git"
|
|
14
19
|
},
|
|
20
|
+
"bin": "./cli.js",
|
|
15
21
|
"author": "",
|
|
16
|
-
"license": "GPL-3",
|
|
22
|
+
"license": "GPL-3.0",
|
|
17
23
|
"bugs": {
|
|
18
24
|
"url": "https://github.com/ti2travel/ti2/issues"
|
|
19
25
|
},
|
|
20
26
|
"homepage": "https://github.com/ti2travel/ti2#readme",
|
|
21
27
|
"dependencies": {
|
|
22
28
|
"bluebird": "^3.7.2",
|
|
29
|
+
"bull": "^4.8.2",
|
|
30
|
+
"commander": "^9.2.0",
|
|
23
31
|
"express": "^4.17.2",
|
|
32
|
+
"ioredis": "^5.0.4",
|
|
24
33
|
"js-yaml": "^4.1.0",
|
|
25
34
|
"jsonwebtoken": "^8.5.1",
|
|
26
35
|
"jwt-promise": "^1.0.0",
|
|
@@ -32,16 +41,18 @@
|
|
|
32
41
|
"swagger-express-middleware": "3.0.0-alpha.5",
|
|
33
42
|
"swagger-routes-express": "3.3.1",
|
|
34
43
|
"swagger-ui-express": "4.3.0",
|
|
44
|
+
"throng": "^5.0.0",
|
|
45
|
+
"umzug": "^3.1.1",
|
|
35
46
|
"uninstall": "^0.0.0"
|
|
36
47
|
},
|
|
37
48
|
"devDependencies": {
|
|
49
|
+
"@salvador/minami": "^1.2.3",
|
|
38
50
|
"chance": "^1.1.8",
|
|
39
51
|
"eslint": "^8.7.0",
|
|
40
52
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
41
53
|
"eslint-plugin-import": "^2.25.4",
|
|
42
54
|
"jest": "^27.4.7",
|
|
43
55
|
"jsdoc": "^3.6.10",
|
|
44
|
-
"minami": "git+https://github.com/MyNameReallySux/minami.git#sort-data",
|
|
45
56
|
"supertest": "^6.2.2"
|
|
46
57
|
}
|
|
47
58
|
}
|
package/test/plugin.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
/* global jest, expect */
|
|
2
|
+
/* eslint class-methods-use-this: "off", max-len: "off" */
|
|
3
|
+
|
|
4
|
+
const jestPlugin = (() => {
|
|
5
|
+
if (typeof jest === 'undefined') {
|
|
6
|
+
return {
|
|
7
|
+
fn: fn => fn,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return jest;
|
|
11
|
+
})();
|
|
12
|
+
|
|
2
13
|
const chance = require('chance').Chance();
|
|
3
14
|
|
|
4
15
|
class Plugin {
|
|
@@ -15,16 +26,16 @@ class Plugin {
|
|
|
15
26
|
this[attr] = value;
|
|
16
27
|
});
|
|
17
28
|
// mock implementations
|
|
18
|
-
this.validateToken =
|
|
19
|
-
this.getProfile =
|
|
20
|
-
this.updateProfile =
|
|
21
|
-
this.getProduct =
|
|
22
|
-
this.getProducts =
|
|
23
|
-
this.createLocation =
|
|
24
|
-
this.updateLocation =
|
|
25
|
-
this.searchBooking =
|
|
26
|
-
this.searchProducts =
|
|
27
|
-
this.searchAvailability =
|
|
29
|
+
this.validateToken = jestPlugin.fn(() => true);
|
|
30
|
+
this.getProfile = jestPlugin.fn(() => {});
|
|
31
|
+
this.updateProfile = jestPlugin.fn(() => {});
|
|
32
|
+
this.getProduct = jestPlugin.fn(() => ({ products: [] }));
|
|
33
|
+
this.getProducts = jestPlugin.fn(() => true);
|
|
34
|
+
this.createLocation = jestPlugin.fn(() => ({ locationId: chance.guid() }));
|
|
35
|
+
this.updateLocation = jestPlugin.fn(() => true);
|
|
36
|
+
this.searchBooking = jestPlugin.fn(() => ({ bookings: [] }));
|
|
37
|
+
this.searchProducts = jestPlugin.fn(() => ({ bookings: [], products: [] }));
|
|
38
|
+
this.searchAvailability = jestPlugin.fn(({
|
|
28
39
|
token,
|
|
29
40
|
payload: {
|
|
30
41
|
travelDateStart,
|
|
@@ -40,9 +51,23 @@ class Plugin {
|
|
|
40
51
|
expect(Array.isArray(occupancies)).toBeTruthy();
|
|
41
52
|
return { availability: [{ id: chance.guid() }] };
|
|
42
53
|
});
|
|
54
|
+
this.searchQuote = jestPlugin.fn(() => ({ quote: [{ id: chance.guid() }] }));
|
|
55
|
+
this.createBooking = jestPlugin.fn(() => {});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Background and schedule Jos
|
|
59
|
+
*/
|
|
60
|
+
this.jobs = [{
|
|
61
|
+
method: 'dailyReport',
|
|
62
|
+
payload: {
|
|
63
|
+
action: 'report',
|
|
64
|
+
},
|
|
65
|
+
cron: '0 9 * * *',
|
|
66
|
+
}];
|
|
43
67
|
|
|
44
|
-
this.
|
|
45
|
-
|
|
68
|
+
this.dailyReport = jestPlugin.fn(() => ({
|
|
69
|
+
someVal: true,
|
|
70
|
+
}));
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
/**
|
|
@@ -258,7 +283,7 @@ class Plugin {
|
|
|
258
283
|
return true;
|
|
259
284
|
}
|
|
260
285
|
|
|
261
|
-
searchHotelBooking =
|
|
286
|
+
searchHotelBooking = jestPlugin.fn(() => ({ bookings: [] }));
|
|
262
287
|
|
|
263
288
|
/**
|
|
264
289
|
* Booking Object
|
|
@@ -364,6 +389,23 @@ class Plugin {
|
|
|
364
389
|
// return (args);
|
|
365
390
|
}
|
|
366
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Retrieve an availability calendar over a date range
|
|
394
|
+
* @async
|
|
395
|
+
* @param {Object} args - Availability search arguments.
|
|
396
|
+
* @param {Object} args.token - A token definition, it's content varies between integrations.
|
|
397
|
+
* @param {Object} args.payload - Search spect object.
|
|
398
|
+
* @param {string} args.payload.dateFormat - Date format sent, using [momentjs formatting](https://momentjs.com/docs/#/parsing/string-format/).
|
|
399
|
+
* @param {string} args.payload.travelDateStart - Date of travel start.
|
|
400
|
+
* @param {string} args.payload.travelDateEnd - Date of travel end.
|
|
401
|
+
* @param {Occupancy[]} args.payload.occupancies - Occupancy detail.
|
|
402
|
+
* @returns {object} retVal - the return object
|
|
403
|
+
* @returns {Availability[]} retVal.availability - Array of availability objects
|
|
404
|
+
*/
|
|
405
|
+
availabilityCalndar() {
|
|
406
|
+
// return (args);
|
|
407
|
+
}
|
|
408
|
+
|
|
367
409
|
/**
|
|
368
410
|
* A quote return spec
|
|
369
411
|
* @typedef {Object} Quote
|
package/test/utils.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const chance = require('chance').Chance();
|
|
4
4
|
const request = require('supertest');
|
|
5
5
|
const assert = require('assert');
|
|
6
|
+
|
|
6
7
|
const appReq = require('../index');
|
|
7
8
|
const Plugin = require('./plugin');
|
|
8
9
|
const slugify = require('./slugify');
|
|
@@ -10,12 +11,10 @@ const sqldb = require('../models/db');
|
|
|
10
11
|
|
|
11
12
|
const { env: { adminKey } } = process;
|
|
12
13
|
|
|
13
|
-
const timeout = ms => (new Promise(resolve => setTimeout(resolve, ms)));
|
|
14
|
-
|
|
15
14
|
afterAll(async () => {
|
|
16
15
|
await sqldb.connectionManager.close();
|
|
17
16
|
});
|
|
18
|
-
module.exports = (appParams = {}) => {
|
|
17
|
+
module.exports = async (appParams = {}) => {
|
|
19
18
|
const plugins = (() => {
|
|
20
19
|
if (Array.isArray(appParams.plugins)) {
|
|
21
20
|
const retVal = {};
|
|
@@ -28,7 +27,7 @@ module.exports = (appParams = {}) => {
|
|
|
28
27
|
mockName: Plugin,
|
|
29
28
|
};
|
|
30
29
|
})();
|
|
31
|
-
const app = appReq({
|
|
30
|
+
const app = await appReq({
|
|
32
31
|
startServer: false,
|
|
33
32
|
...appParams,
|
|
34
33
|
plugins,
|
|
@@ -104,7 +103,6 @@ module.exports = (appParams = {}) => {
|
|
|
104
103
|
doApiPost,
|
|
105
104
|
doApiPut,
|
|
106
105
|
slugify,
|
|
107
|
-
timeout,
|
|
108
106
|
plugins: app.plugins,
|
|
109
107
|
};
|
|
110
108
|
};
|