ti2 1.0.30 → 1.0.33

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/api.yml CHANGED
@@ -1342,3 +1342,104 @@ paths:
1342
1342
  application/json:
1343
1343
  schema:
1344
1344
  $ref: '#/components/schemas/AnyValue'
1345
+ /allotment/{appKey}/{userId}:
1346
+ get:
1347
+ security:
1348
+ - bearerAuth: ['admin', 'user']
1349
+ tags:
1350
+ - allotment
1351
+ summary: Query the allotment
1352
+ operationId: queryAllotment
1353
+ parameters:
1354
+ - name: appKey
1355
+ in: path
1356
+ description: app to create the booking for, defaults to first token
1357
+ required: true
1358
+ example: 'tourplan'
1359
+ schema:
1360
+ type: string
1361
+ - name: userId
1362
+ in: path
1363
+ description: the user/company that the appKey belongs to
1364
+ required: true
1365
+ schema:
1366
+ type: string
1367
+ - in: query
1368
+ name: startDate
1369
+ schema:
1370
+ type: string
1371
+ description: the start date of the requested allotment (dd/mm/yyyy)
1372
+ default: "01/04/2023"
1373
+ - in: query
1374
+ name: endDate
1375
+ schema:
1376
+ type: string
1377
+ description: the end date of the requested allotment (dd/mm/yyyy)
1378
+ default: "03/04/2023"
1379
+ - in: query
1380
+ name: keyPath
1381
+ schema:
1382
+ type: string
1383
+ description: a string with the product path separater by pipes ie. supplierId|locationId|productID
1384
+ default: "MAGLUX|7CQLDACMAGLUXDELSUI"
1385
+ responses:
1386
+ '200':
1387
+ description: Allotment Details
1388
+ content:
1389
+ application/json:
1390
+ schema:
1391
+ $ref: '#/components/schemas/AnyValue'
1392
+ /allotment/{appKey}/{hint}/{userId}:
1393
+ get:
1394
+ security:
1395
+ - bearerAuth: ['admin', 'user']
1396
+ tags:
1397
+ - allotment
1398
+ summary: Query the allotment with an specific hit
1399
+ operationId: queryAllotment
1400
+ parameters:
1401
+ - name: appKey
1402
+ in: path
1403
+ description: app to create the booking for, defaults to first token
1404
+ required: true
1405
+ example: 'tourplan'
1406
+ schema:
1407
+ type: string
1408
+ - name: hint
1409
+ in: path
1410
+ description: a specific token hint to be used
1411
+ required: true
1412
+ example: 'tourplan-australia'
1413
+ schema:
1414
+ type: string
1415
+ - name: userId
1416
+ in: path
1417
+ description: the user/company that the appKey belongs to
1418
+ required: true
1419
+ schema:
1420
+ type: string
1421
+ - in: query
1422
+ name: startDate
1423
+ schema:
1424
+ type: string
1425
+ description: the start date of the requested allotment (dd/mm/yyyy)
1426
+ default: "01/04/2023"
1427
+ - in: query
1428
+ name: endDate
1429
+ schema:
1430
+ type: string
1431
+ description: the end date of the requested allotment (dd/mm/yyyy)
1432
+ default: "03/04/2023"
1433
+ - in: query
1434
+ name: keyPath
1435
+ schema:
1436
+ type: string
1437
+ description: a string with the product path separater by pipes ie. supplierId|locationId|productID
1438
+ default: "MAGLUX|7CQLDACMAGLUXDELSUI"
1439
+ responses:
1440
+ '200':
1441
+ description: Allotment Details
1442
+ content:
1443
+ application/json:
1444
+ schema:
1445
+ $ref: '#/components/schemas/AnyValue'
@@ -0,0 +1,106 @@
1
+ /* globals beforeAll describe it expect */
2
+
3
+ const chance = require('chance').Chance();
4
+ const testUtils = require('../../test/utils');
5
+
6
+ const { env: { adminKey } } = process;
7
+
8
+ describe('allotment', () => {
9
+ const newApp = {
10
+ name: 'tourplantest',
11
+ packageName: 'ti2-tourplanTest',
12
+ adminEmail: 'engineering+tourplantest@tourconnect.com',
13
+ };
14
+ const appKey = newApp.name;
15
+ const userId = '551394be5ac58e5c76000019';
16
+ const token = {
17
+ endpoint: 'https://someendpoint.com',
18
+ username: chance.guid(),
19
+ password: chance.string(),
20
+ };
21
+ const newIntegration = {
22
+ tokenHint: 'testingToken',
23
+ token,
24
+ };
25
+ let doApiDelete;
26
+ let doApiGet;
27
+ let doApiPost;
28
+ let plugins;
29
+ beforeAll(async () => {
30
+ ({
31
+ doApiDelete,
32
+ doApiGet,
33
+ doApiPost,
34
+ plugins,
35
+ } = await testUtils({
36
+ plugins: [appKey],
37
+ }));
38
+ });
39
+
40
+ let userToken;
41
+ it('drop any existing user integration', async () => {
42
+ try {
43
+ await doApiDelete({
44
+ url: `/${appKey}/${userId}`,
45
+ token: adminKey,
46
+ payload: { tokenHint: newIntegration.tokenHint },
47
+ });
48
+ } catch (err) {
49
+ // console.debug(err);
50
+ }
51
+ });
52
+ it('create the user', async () => {
53
+ ({ value: userToken } = await doApiPost({
54
+ url: '/user',
55
+ token: adminKey,
56
+ payload: { userId },
57
+ }));
58
+ expect(userToken).toBeTruthy();
59
+ });
60
+ it('create the App+User setup', async () => {
61
+ const { userAppKeys } = await doApiGet({
62
+ url: `/user/${userId}/apps`,
63
+ token: userToken,
64
+ });
65
+ if (!userAppKeys.map(e => e.integrationId).includes(appKey)) {
66
+ // relation does not exits
67
+ const { value } = await doApiPost({
68
+ url: `/${appKey}/${userId}`,
69
+ token: userToken,
70
+ payload: newIntegration,
71
+ });
72
+ expect(value).toBeTruthy();
73
+ }
74
+ });
75
+ const payload = {
76
+ dateFormat: 'DD-MM-YYYY',
77
+ startDate: '01-12-2022',
78
+ endDate: '15-12-2022',
79
+ keyPath: `${chance.guid()}|${chance.guid()}`,
80
+ };
81
+ const urlParams = new URLSearchParams(payload).toString();
82
+ it('should be able to get some allotments', async () => {
83
+ const { allotments } = await doApiGet({
84
+ url: `/allotment/${appKey}/${userId}?${urlParams}`,
85
+ token: userToken,
86
+ payload,
87
+ });
88
+ expect(plugins[0].queryAllotment).toHaveBeenCalled();
89
+ expect(Array.isArray(allotments)).toBeTruthy();
90
+ const call = plugins[0].queryAllotment.mock.calls[0][0];
91
+ expect(call.payload).toEqual(payload);
92
+ expect(call.token).toEqual(token);
93
+ });
94
+ it('should be able to get some allotments with a hint', async () => {
95
+ const { allotments } = await doApiGet({
96
+ url: `/allotment/${appKey}/${newIntegration.tokenHint}/${userId}?${urlParams}`,
97
+ token: userToken,
98
+ payload,
99
+ });
100
+ expect(plugins[0].queryAllotment).toHaveBeenCalled();
101
+ expect(Array.isArray(allotments)).toBeTruthy();
102
+ const call = plugins[0].queryAllotment.mock.calls[0][0];
103
+ expect(call.payload).toEqual(payload);
104
+ expect(call.token).toEqual(token);
105
+ });
106
+ });
@@ -89,7 +89,7 @@ describe('user: booking search', () => {
89
89
  });
90
90
  it('should be able to search a for a booking', async () => {
91
91
  const payload = {
92
- bookingId: '', supplierId: '', name: '',
92
+ bookingId: '', supplierBookingId: '', name: '',
93
93
  };
94
94
  const { bookings } = await doApiPost({
95
95
  url: `/bookings/${appKey}/${userId}/search`,
@@ -0,0 +1,39 @@
1
+ const assert = require('assert');
2
+
3
+ const { UserAppKey } = require('../models/index');
4
+
5
+ const queryAllotment = plugins => async (req, res, next) => {
6
+ const {
7
+ params: {
8
+ appKey,
9
+ hint,
10
+ userId,
11
+ },
12
+ query,
13
+ } = req;
14
+ try {
15
+ const app = plugins.find(({ name }) => name === appKey);
16
+ assert(app, `could not find the app ${appKey}`);
17
+ const userAppKeys = await UserAppKey.findOne({
18
+ where: {
19
+ userId,
20
+ integrationId: appKey,
21
+ ...(hint ? { hint } : {}),
22
+ },
23
+ });
24
+ assert(userAppKeys, 'could not find the app key');
25
+ const token = userAppKeys.appKey;
26
+ assert(app.queryAllotment, 'could not find the allotment method');
27
+ const results = await app.queryAllotment({
28
+ token,
29
+ payload: query,
30
+ });
31
+ return res.json(results);
32
+ } catch (err) {
33
+ return next(err);
34
+ }
35
+ };
36
+
37
+ module.exports = plugins => ({
38
+ queryAllotment: queryAllotment(plugins),
39
+ });
package/index.js CHANGED
@@ -17,6 +17,7 @@ const adminController = require('./controllers/admin');
17
17
  const appController = require('./controllers/app');
18
18
  const userController = require('./controllers/user');
19
19
  const bookingsController = require('./controllers/bookings');
20
+ const allotmentController = require('./controllers/allotment');
20
21
  const { Integration } = require('./models');
21
22
  const cache = require('./cache');
22
23
 
@@ -26,7 +27,6 @@ const rebuild = fn => obj =>
26
27
 
27
28
  const isNumber = value => !Number.isNaN(Number(value));
28
29
 
29
-
30
30
  module.exports = async ({
31
31
  apiDocs = true,
32
32
  elasticLogIndex = 'apilog_ti2',
@@ -67,7 +67,8 @@ module.exports = async ({
67
67
  },
68
68
  raw: true,
69
69
  });
70
- const missingIntegrations = R.difference(pluginNames, matchedIntegrations.map(R.prop('name')));
70
+ const matchedIntegrationsNames = matchedIntegrations.map(R.prop('name'));
71
+ const missingIntegrations = R.difference(pluginNames, matchedIntegrationsNames);
71
72
  if (missingIntegrations.length > 0) {
72
73
  // need to crete the missing integrations
73
74
  await Integration.bulkCreate(missingIntegrations.map(name => ({
@@ -84,6 +85,7 @@ module.exports = async ({
84
85
  ...appController,
85
86
  ...userController(plugins),
86
87
  ...bookingsController(plugins),
88
+ ...allotmentController(plugins),
87
89
  cache: R.omit(['cache'], cache),
88
90
  }; // mehthods that should map to the yaml api spec
89
91
  app.plugins = plugins;
@@ -207,7 +209,10 @@ module.exports = async ({
207
209
  connect(app);
208
210
  app.use(middleware.mock());
209
211
  // global error Handling
210
- app.use((err, req, res) => {
212
+ app.use((err, req, res, next) => {
213
+ if (res.headersSent) {
214
+ return next(err);
215
+ }
211
216
  if (process.env.CONSOLE_ERRORS || process.env.JEST_WORKER_ID) {
212
217
  console.error(err);
213
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2",
3
- "version": "1.0.30",
3
+ "version": "1.0.33",
4
4
  "description": "Tourist Industry Exchange (TI2)",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/plugin.js CHANGED
@@ -53,6 +53,7 @@ class Plugin {
53
53
  });
54
54
  this.searchQuote = jestPlugin.fn(() => ({ quote: [{ id: chance.guid() }] }));
55
55
  this.createBooking = jestPlugin.fn(() => {});
56
+ this.queryAllotment = jestPlugin.fn(() => ({ allotments: [] }));
56
57
 
57
58
  /**
58
59
  * Background and schedule Jos
@@ -292,7 +293,7 @@ class Plugin {
292
293
  * @property {Cancelled|Active|Pending} status The currrent booking status.
293
294
  * @property {Holder} holder Booking holder information.
294
295
  * @property {string} telephone Contact telephone.
295
- * @property {string} supplierId Supplier of the Booking
296
+ * @property {string} supplierBookingId Booking Id for the supplier booking system
296
297
  * @property {string} hotelId A HotelId if the booking is for an accommodation.
297
298
  * @property {string} hotelName A Hotel Name if the booking is for an accommodation.
298
299
  * @property {BookingRoom[]} rooms Booking Room details.
@@ -437,6 +438,25 @@ class Plugin {
437
438
  * @returns {Booking} retVal.booking - A Booking object.
438
439
  */
439
440
  createBooking() {}
441
+
442
+ /**
443
+ * Query Allotment
444
+ * @async
445
+ * @param {Object} args - Allotment query arguments.
446
+ * @param {Object} args.token - A token definition, it's content varies between integrations.
447
+ * @param {Object} args.payload - Search spect object.
448
+ * dateFormat = 'DD/MM/YYYY',
449
+ startDate,
450
+ endDate,
451
+ keyPath,
452
+ * @param {string} args.payload.dateFormat - Date format to use for params and return value (i.e. DD/MM/YYYY
453
+ * @param {string} args.payload.startDate - Start date for the query of allotment objects
454
+ * @param {string} args.payload.endDate - End date for the query of allotment objects
455
+ * @param {string} args.payload.keyPath - End date for the query of allotment objects
456
+ * @returns {object} retVal - the return object.
457
+ * @returns {Booking} retVal.booking - A Booking object.
458
+ */
459
+ queryAllotment() {}
440
460
  }
441
461
 
442
462
  module.exports = Plugin;