ti2 1.0.115 → 1.0.118

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/README.md CHANGED
@@ -56,6 +56,7 @@ Plugins are the connectores to other systems and/or features you intent to use;
56
56
  |searchAvailability|✓|✓|✓|✓|✓|✓|
57
57
  |searchQuote|✓|✓|✓|✓|✓|✓|
58
58
  |createBooking|✓|✓|✓|✓|✓|✓|
59
+ |confirmBooking|✓|✓|✓|✓|✓|✓|
59
60
  |cancelBooking|✓|✓|✓|✓|✓|✓|
60
61
  |searchProductsForItinerary|||||||||✓|
61
62
  |searchAvailabilityForItinerary|||||||||✓|
@@ -15,6 +15,70 @@ describe('cache', () => {
15
15
  });
16
16
 
17
17
 
18
+ describe('saveIfNotExists', () => {
19
+ it('should only save the first value for a key', async () => {
20
+ const key = 'lock:test';
21
+ testKeys.push(key);
22
+
23
+ const firstSaved = await cache.saveIfNotExists({
24
+ pluginName: testPluginName,
25
+ key,
26
+ value: { owner: 'first' },
27
+ ttl: 60,
28
+ });
29
+ const secondSaved = await cache.saveIfNotExists({
30
+ pluginName: testPluginName,
31
+ key,
32
+ value: { owner: 'second' },
33
+ ttl: 60,
34
+ });
35
+
36
+ expect(firstSaved).toBe(true);
37
+ expect(secondSaved).toBe(false);
38
+ expect(await cache.get({ pluginName: testPluginName, key })).toEqual({ owner: 'first' });
39
+ });
40
+ });
41
+
42
+ describe('owner-token helpers', () => {
43
+ it('should only renew and drop a key when the expected value still owns it', async () => {
44
+ const key = 'owner-lock:test';
45
+ testKeys.push(key);
46
+
47
+ await cache.save({ pluginName: testPluginName, key, value: 'owner-a', ttl: 1 });
48
+
49
+ const renewedByWrongOwner = await cache.expireIfValue({
50
+ pluginName: testPluginName,
51
+ key,
52
+ value: 'owner-b',
53
+ ttl: 60,
54
+ });
55
+ const renewedByOwner = await cache.expireIfValue({
56
+ pluginName: testPluginName,
57
+ key,
58
+ value: 'owner-a',
59
+ ttl: 60,
60
+ });
61
+ const droppedByWrongOwner = await cache.dropIfValue({
62
+ pluginName: testPluginName,
63
+ key,
64
+ value: 'owner-b',
65
+ });
66
+
67
+ expect(renewedByWrongOwner).toBe(false);
68
+ expect(renewedByOwner).toBe(true);
69
+ expect(droppedByWrongOwner).toBe(false);
70
+ expect(await cache.get({ pluginName: testPluginName, key })).toBe('owner-a');
71
+
72
+ const droppedByOwner = await cache.dropIfValue({
73
+ pluginName: testPluginName,
74
+ key,
75
+ value: 'owner-a',
76
+ });
77
+ expect(droppedByOwner).toBe(true);
78
+ expect(await cache.get({ pluginName: testPluginName, key })).toBeFalsy();
79
+ });
80
+ });
81
+
18
82
  describe('scan', () => {
19
83
  it('should return keys matching the pattern without plugin prefix', async () => {
20
84
  // Save some test keys
package/api.yml CHANGED
@@ -358,6 +358,39 @@ components:
358
358
  type: object
359
359
  allOf: # Combines the BasicErrorModel and the inline model
360
360
  - $ref: '#/components/schemas/bookingDetail'
361
+ bookingDetailsForConfirm:
362
+ type: object
363
+ additionalProperties: true
364
+ description: Booking identifier; provide bookingId, id, ref, or reference
365
+ properties:
366
+ bookingId:
367
+ type: string
368
+ id:
369
+ type: string
370
+ ref:
371
+ type: string
372
+ reference:
373
+ type: string
374
+ confirmBookingReturn:
375
+ type: object
376
+ additionalProperties: true
377
+ properties:
378
+ confirmBookingReply:
379
+ type: object
380
+ booking:
381
+ type: object
382
+ properties:
383
+ ref:
384
+ type: string
385
+ id:
386
+ type: string
387
+ status:
388
+ type: string
389
+ serviceLines:
390
+ type: array
391
+ items:
392
+ type: object
393
+ additionalProperties: true
361
394
  packageSearchQuery:
362
395
  type: object
363
396
  additionalProperties: true
@@ -1815,6 +1848,85 @@ paths:
1815
1848
  application/json:
1816
1849
  schema:
1817
1850
  $ref: '#/components/schemas/cancellationReturn'
1851
+ /bookings/{appKey}/{userId}/confirm:
1852
+ post:
1853
+ security:
1854
+ - bearerAuth: ['admin', 'user']
1855
+ tags:
1856
+ - bookings
1857
+ summary: Confirms a quote as a booking
1858
+ operationId: confirmBooking
1859
+ parameters:
1860
+ - name: appKey
1861
+ in: path
1862
+ description: app to confirm the booking for, defaults to first token
1863
+ required: true
1864
+ example: 'travelgate-hotelx'
1865
+ schema:
1866
+ type: string
1867
+ - name: userId
1868
+ in: path
1869
+ description: the user/company that the appKey belongs to
1870
+ required: true
1871
+ schema:
1872
+ type: string
1873
+ requestBody:
1874
+ description: Booking identifier to confirm
1875
+ required: true
1876
+ content:
1877
+ application/json:
1878
+ schema:
1879
+ $ref: '#/components/schemas/bookingDetailsForConfirm'
1880
+ responses:
1881
+ '200':
1882
+ description: Booking confirmation details
1883
+ content:
1884
+ application/json:
1885
+ schema:
1886
+ $ref: '#/components/schemas/confirmBookingReturn'
1887
+ /bookings/{appKey}/{userId}/{hint}/confirm:
1888
+ post:
1889
+ security:
1890
+ - bearerAuth: ['admin', 'user']
1891
+ tags:
1892
+ - bookings
1893
+ summary: Confirms a quote as a booking for a specific hint
1894
+ operationId: confirmBooking
1895
+ parameters:
1896
+ - name: appKey
1897
+ in: path
1898
+ description: app to confirm the booking for
1899
+ required: true
1900
+ example: 'travelgate'
1901
+ schema:
1902
+ type: string
1903
+ - name: userId
1904
+ in: path
1905
+ description: the user/company that the appKey belongs to
1906
+ required: true
1907
+ schema:
1908
+ type: string
1909
+ - name: hint
1910
+ in: path
1911
+ description: a specific token hint to be used
1912
+ required: true
1913
+ example: 'travelgate-australia'
1914
+ schema:
1915
+ type: string
1916
+ requestBody:
1917
+ description: Booking identifier to confirm
1918
+ required: true
1919
+ content:
1920
+ application/json:
1921
+ schema:
1922
+ $ref: '#/components/schemas/bookingDetailsForConfirm'
1923
+ responses:
1924
+ '200':
1925
+ description: Booking confirmation details
1926
+ content:
1927
+ application/json:
1928
+ schema:
1929
+ $ref: '#/components/schemas/confirmBookingReturn'
1818
1930
  /packages/{appKey}/{userId}/search:
1819
1931
  post:
1820
1932
  security:
package/cache.js CHANGED
@@ -5,6 +5,23 @@ const REDIS_URL = process.env.REDIS_URL || 'redis://redis:6379';
5
5
  const cache = new Redis(`${REDIS_URL}/2`);
6
6
  const defaultTTL = 60 * 60; // 1 hour
7
7
 
8
+ const normalizeKey = ({
9
+ key: keyParam,
10
+ fn,
11
+ fnParams,
12
+ useFunctionFallback = false,
13
+ }) => {
14
+ if (useFunctionFallback && !keyParam) {
15
+ return hash({ fn, fnParams });
16
+ }
17
+ if (typeof keyParam !== 'string') {
18
+ return hash(keyParam);
19
+ }
20
+ return keyParam;
21
+ };
22
+
23
+ const buildKey = ({ pluginName, ...args }) => `${pluginName}:${normalizeKey(args)}`;
24
+
8
25
  const save = async ({
9
26
  pluginName,
10
27
  key: keyParam,
@@ -12,12 +29,7 @@ const save = async ({
12
29
  skipTTL,
13
30
  ttl = defaultTTL,
14
31
  }) => {
15
- const key = `${pluginName}:${(() => {
16
- if (typeof keyParam !== 'string') {
17
- return hash(keyParam);
18
- }
19
- return keyParam;
20
- })()}`;
32
+ const key = buildKey({ pluginName, key: keyParam });
21
33
  // console.log('saving key', key);
22
34
  await cache.set(key, JSON.stringify(value));
23
35
  if (!skipTTL) {
@@ -25,16 +37,55 @@ const save = async ({
25
37
  }
26
38
  };
27
39
 
40
+ const saveIfNotExists = async ({
41
+ pluginName,
42
+ key: keyParam,
43
+ value,
44
+ ttl = defaultTTL,
45
+ }) => {
46
+ const key = buildKey({ pluginName, key: keyParam });
47
+ const result = await cache.set(key, JSON.stringify(value), 'EX', ttl, 'NX');
48
+ return result === 'OK';
49
+ };
50
+
51
+ const expireIfValue = async ({
52
+ pluginName,
53
+ key: keyParam,
54
+ value,
55
+ ttl = defaultTTL,
56
+ }) => {
57
+ const key = buildKey({ pluginName, key: keyParam });
58
+ const expectedValue = JSON.stringify(value);
59
+ const result = await cache.eval(`
60
+ if redis.call('get', KEYS[1]) == ARGV[1] then
61
+ return redis.call('expire', KEYS[1], ARGV[2])
62
+ end
63
+ return 0
64
+ `, 1, key, expectedValue, ttl);
65
+ return result === 1;
66
+ };
67
+
68
+ const dropIfValue = async ({
69
+ pluginName,
70
+ key: keyParam,
71
+ value,
72
+ }) => {
73
+ const key = buildKey({ pluginName, key: keyParam });
74
+ const expectedValue = JSON.stringify(value);
75
+ const result = await cache.eval(`
76
+ if redis.call('get', KEYS[1]) == ARGV[1] then
77
+ return redis.call('del', KEYS[1])
78
+ end
79
+ return 0
80
+ `, 1, key, expectedValue);
81
+ return result === 1;
82
+ };
83
+
28
84
  const get = async ({
29
85
  pluginName,
30
86
  key: keyParam,
31
87
  }) => {
32
- const key = `${pluginName}:${(() => {
33
- if (typeof keyParam !== 'string') {
34
- return hash(keyParam);
35
- }
36
- return keyParam;
37
- })()}`;
88
+ const key = buildKey({ pluginName, key: keyParam });
38
89
  const storeVal = await cache.get(key);
39
90
  if (!storeVal) return storeVal;
40
91
  return JSON.parse(storeVal);
@@ -48,15 +99,7 @@ const getOrExec = async ({
48
99
  fnParams,
49
100
  forceRefresh,
50
101
  }) => {
51
- const key = (() => {
52
- if (!keyParam) {
53
- return hash({ fn, fnParams });
54
- }
55
- if (typeof keyParam !== 'string') {
56
- return hash(keyParam);
57
- }
58
- return keyParam;
59
- })();
102
+ const key = normalizeKey({ key: keyParam, fn, fnParams, useFunctionFallback: true });
60
103
  let value;
61
104
  if (!forceRefresh) {
62
105
  value = await get({ pluginName, key });
@@ -78,15 +121,13 @@ const drop = async ({
78
121
  fn,
79
122
  fnParams,
80
123
  }) => {
81
- const key = `${pluginName}:${(() => {
82
- if (!keyParam) {
83
- return hash({ fn, fnParams });
84
- }
85
- if (typeof keyParam !== 'string') {
86
- return hash(keyParam);
87
- }
88
- return keyParam;
89
- })()}`;
124
+ const key = buildKey({
125
+ pluginName,
126
+ key: keyParam,
127
+ fn,
128
+ fnParams,
129
+ useFunctionFallback: true,
130
+ });
90
131
  // console.log('dropping key', key);
91
132
  await cache.del(key);
92
133
  };
@@ -116,6 +157,9 @@ const scan = ({
116
157
  module.exports = {
117
158
  cache,
118
159
  save,
160
+ saveIfNotExists,
161
+ expireIfValue,
162
+ dropIfValue,
119
163
  getOrExec,
120
164
  drop,
121
165
  get,