ti2 1.0.107 → 1.0.108-beta.0
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 +184 -0
- package/controllers/__tests__/allotments.js +8 -1
- package/controllers/__tests__/bookings.js +66 -0
- package/controllers/allotment.js +3 -1
- package/controllers/bookings.js +61 -129
- package/package.json +1 -1
- package/test/plugin.js +1 -0
- package/.windsurfrules +0 -9
- package/CLAUDE.md +0 -159
package/api.yml
CHANGED
|
@@ -151,6 +151,10 @@ components:
|
|
|
151
151
|
payload:
|
|
152
152
|
type: object
|
|
153
153
|
example: random lorem
|
|
154
|
+
integrationCredentials:
|
|
155
|
+
description: Optional external-session credentials forwarded by the client
|
|
156
|
+
type: object
|
|
157
|
+
additionalProperties: true
|
|
154
158
|
UserApp:
|
|
155
159
|
description: A user + App configuration
|
|
156
160
|
type: object
|
|
@@ -385,6 +389,9 @@ components:
|
|
|
385
389
|
required: false
|
|
386
390
|
description: Product Name wildcard match
|
|
387
391
|
example: '*bus*'
|
|
392
|
+
credentials:
|
|
393
|
+
required: false
|
|
394
|
+
$ref: '#/components/schemas/integrationCredentials'
|
|
388
395
|
bookingsProductSearchReturn:
|
|
389
396
|
type: object
|
|
390
397
|
additionalProperties: true
|
|
@@ -454,6 +461,9 @@ components:
|
|
|
454
461
|
shellOnly:
|
|
455
462
|
type: boolean
|
|
456
463
|
description: Optional itinerary-assist flag to create/update booking header only without adding a service line
|
|
464
|
+
credentials:
|
|
465
|
+
required: false
|
|
466
|
+
$ref: '#/components/schemas/integrationCredentials'
|
|
457
467
|
settingsBody:
|
|
458
468
|
type: object
|
|
459
469
|
required: true
|
|
@@ -1410,6 +1420,40 @@ paths:
|
|
|
1410
1420
|
$ref: '#/components/schemas/desk'
|
|
1411
1421
|
/affiliate/{appKey}/{userId}/agents:
|
|
1412
1422
|
get:
|
|
1423
|
+
deprecated: true
|
|
1424
|
+
security:
|
|
1425
|
+
- bearerAuth: ['admin', 'user']
|
|
1426
|
+
tags:
|
|
1427
|
+
- bookings
|
|
1428
|
+
summary: Gets all agents for this affiliate
|
|
1429
|
+
operationId: getAffiliateAgents
|
|
1430
|
+
parameters:
|
|
1431
|
+
- name: appKey
|
|
1432
|
+
in: path
|
|
1433
|
+
description: name of the app to get the agents for (defaults to first token found)
|
|
1434
|
+
required: true
|
|
1435
|
+
example: 'fareharbor'
|
|
1436
|
+
schema:
|
|
1437
|
+
type: string
|
|
1438
|
+
- name: userId
|
|
1439
|
+
in: path
|
|
1440
|
+
description: the user/company that the appKey belongs to
|
|
1441
|
+
required: true
|
|
1442
|
+
schema:
|
|
1443
|
+
type: string
|
|
1444
|
+
responses:
|
|
1445
|
+
'200':
|
|
1446
|
+
description: agents
|
|
1447
|
+
content:
|
|
1448
|
+
application/json:
|
|
1449
|
+
schema:
|
|
1450
|
+
type: object
|
|
1451
|
+
properties:
|
|
1452
|
+
agents:
|
|
1453
|
+
type: array
|
|
1454
|
+
items:
|
|
1455
|
+
$ref: '#/components/schemas/agent'
|
|
1456
|
+
post:
|
|
1413
1457
|
security:
|
|
1414
1458
|
- bearerAuth: ['admin', 'user']
|
|
1415
1459
|
tags:
|
|
@@ -1430,6 +1474,13 @@ paths:
|
|
|
1430
1474
|
required: true
|
|
1431
1475
|
schema:
|
|
1432
1476
|
type: string
|
|
1477
|
+
requestBody:
|
|
1478
|
+
description: Optional request metadata
|
|
1479
|
+
required: false
|
|
1480
|
+
content:
|
|
1481
|
+
application/json:
|
|
1482
|
+
schema:
|
|
1483
|
+
$ref: '#/components/schemas/AnyValue'
|
|
1433
1484
|
responses:
|
|
1434
1485
|
'200':
|
|
1435
1486
|
description: agents
|
|
@@ -1444,6 +1495,7 @@ paths:
|
|
|
1444
1495
|
$ref: '#/components/schemas/agent'
|
|
1445
1496
|
/affiliate/{appKey}/{userId}/{hint}/agents:
|
|
1446
1497
|
get:
|
|
1498
|
+
deprecated: true
|
|
1447
1499
|
security:
|
|
1448
1500
|
- bearerAuth: ['admin', 'user']
|
|
1449
1501
|
tags:
|
|
@@ -1483,6 +1535,53 @@ paths:
|
|
|
1483
1535
|
type: array
|
|
1484
1536
|
items:
|
|
1485
1537
|
$ref: '#/components/schemas/agent'
|
|
1538
|
+
post:
|
|
1539
|
+
security:
|
|
1540
|
+
- bearerAuth: ['admin', 'user']
|
|
1541
|
+
tags:
|
|
1542
|
+
- bookings
|
|
1543
|
+
summary: Gets all agents for this affiliate
|
|
1544
|
+
operationId: getAffiliateAgents
|
|
1545
|
+
parameters:
|
|
1546
|
+
- name: appKey
|
|
1547
|
+
in: path
|
|
1548
|
+
description: name of the app to get the agents for the token using the hint
|
|
1549
|
+
required: true
|
|
1550
|
+
example: 'fareharbor'
|
|
1551
|
+
schema:
|
|
1552
|
+
type: string
|
|
1553
|
+
- name: userId
|
|
1554
|
+
in: path
|
|
1555
|
+
description: the user/company that the appKey belongs to
|
|
1556
|
+
required: true
|
|
1557
|
+
schema:
|
|
1558
|
+
type: string
|
|
1559
|
+
- name: hint
|
|
1560
|
+
in: path
|
|
1561
|
+
description: a specific token hint to be used
|
|
1562
|
+
required: true
|
|
1563
|
+
example: 'fareharbor-helloworld'
|
|
1564
|
+
schema:
|
|
1565
|
+
type: string
|
|
1566
|
+
requestBody:
|
|
1567
|
+
description: Optional request metadata
|
|
1568
|
+
required: false
|
|
1569
|
+
content:
|
|
1570
|
+
application/json:
|
|
1571
|
+
schema:
|
|
1572
|
+
$ref: '#/components/schemas/AnyValue'
|
|
1573
|
+
responses:
|
|
1574
|
+
'200':
|
|
1575
|
+
description: agents
|
|
1576
|
+
content:
|
|
1577
|
+
application/json:
|
|
1578
|
+
schema:
|
|
1579
|
+
type: object
|
|
1580
|
+
properties:
|
|
1581
|
+
agents:
|
|
1582
|
+
type: array
|
|
1583
|
+
items:
|
|
1584
|
+
$ref: '#/components/schemas/agent'
|
|
1486
1585
|
/{appKey}/{userId}/pickup-points:
|
|
1487
1586
|
get:
|
|
1488
1587
|
security:
|
|
@@ -2256,6 +2355,38 @@ paths:
|
|
|
2256
2355
|
$ref: '#/components/schemas/AnyValue'
|
|
2257
2356
|
/bookings/{appKey}/{userId}/custom-fields:
|
|
2258
2357
|
get:
|
|
2358
|
+
deprecated: true
|
|
2359
|
+
security:
|
|
2360
|
+
- bearerAuth: ['admin', 'user']
|
|
2361
|
+
tags:
|
|
2362
|
+
- bookings
|
|
2363
|
+
summary: Gets all custom fields for creating a booking
|
|
2364
|
+
operationId: getCreateBookingFields
|
|
2365
|
+
parameters:
|
|
2366
|
+
- name: appKey
|
|
2367
|
+
in: path
|
|
2368
|
+
description: name of the app to get the resource for (defaults to first token found)
|
|
2369
|
+
required: true
|
|
2370
|
+
example: 'fareharbor'
|
|
2371
|
+
schema:
|
|
2372
|
+
type: string
|
|
2373
|
+
- name: userId
|
|
2374
|
+
in: path
|
|
2375
|
+
description: the user/company that the appKey belongs to
|
|
2376
|
+
required: true
|
|
2377
|
+
schema:
|
|
2378
|
+
type: string
|
|
2379
|
+
responses:
|
|
2380
|
+
'200':
|
|
2381
|
+
description: fields
|
|
2382
|
+
content:
|
|
2383
|
+
application/json:
|
|
2384
|
+
schema:
|
|
2385
|
+
type: object
|
|
2386
|
+
properties:
|
|
2387
|
+
fields:
|
|
2388
|
+
type: array
|
|
2389
|
+
post:
|
|
2259
2390
|
security:
|
|
2260
2391
|
- bearerAuth: ['admin', 'user']
|
|
2261
2392
|
tags:
|
|
@@ -2276,6 +2407,13 @@ paths:
|
|
|
2276
2407
|
required: true
|
|
2277
2408
|
schema:
|
|
2278
2409
|
type: string
|
|
2410
|
+
requestBody:
|
|
2411
|
+
description: Optional field context and credentials
|
|
2412
|
+
required: false
|
|
2413
|
+
content:
|
|
2414
|
+
application/json:
|
|
2415
|
+
schema:
|
|
2416
|
+
$ref: '#/components/schemas/AnyValue'
|
|
2279
2417
|
responses:
|
|
2280
2418
|
'200':
|
|
2281
2419
|
description: fields
|
|
@@ -2288,6 +2426,7 @@ paths:
|
|
|
2288
2426
|
type: array
|
|
2289
2427
|
/bookings/{appKey}/{userId}/{hint}/custom-fields:
|
|
2290
2428
|
get:
|
|
2429
|
+
deprecated: true
|
|
2291
2430
|
security:
|
|
2292
2431
|
- bearerAuth: ['admin', 'user']
|
|
2293
2432
|
tags:
|
|
@@ -2325,6 +2464,51 @@ paths:
|
|
|
2325
2464
|
properties:
|
|
2326
2465
|
fields:
|
|
2327
2466
|
type: array
|
|
2467
|
+
post:
|
|
2468
|
+
security:
|
|
2469
|
+
- bearerAuth: ['admin', 'user']
|
|
2470
|
+
tags:
|
|
2471
|
+
- bookings
|
|
2472
|
+
summary: Gets all custom fields for creating a booking
|
|
2473
|
+
operationId: getCreateBookingFields
|
|
2474
|
+
parameters:
|
|
2475
|
+
- name: appKey
|
|
2476
|
+
in: path
|
|
2477
|
+
description: name of the app to get the resource for the token using the hint
|
|
2478
|
+
required: true
|
|
2479
|
+
example: 'xola'
|
|
2480
|
+
schema:
|
|
2481
|
+
type: string
|
|
2482
|
+
- name: userId
|
|
2483
|
+
in: path
|
|
2484
|
+
description: the user/company that the appKey belongs to
|
|
2485
|
+
required: true
|
|
2486
|
+
schema:
|
|
2487
|
+
type: string
|
|
2488
|
+
- name: hint
|
|
2489
|
+
in: path
|
|
2490
|
+
description: a specific token hint to be used
|
|
2491
|
+
required: true
|
|
2492
|
+
example: 'xola-helloworld'
|
|
2493
|
+
schema:
|
|
2494
|
+
type: string
|
|
2495
|
+
requestBody:
|
|
2496
|
+
description: Optional field context and credentials
|
|
2497
|
+
required: false
|
|
2498
|
+
content:
|
|
2499
|
+
application/json:
|
|
2500
|
+
schema:
|
|
2501
|
+
$ref: '#/components/schemas/AnyValue'
|
|
2502
|
+
responses:
|
|
2503
|
+
'200':
|
|
2504
|
+
description: fields
|
|
2505
|
+
content:
|
|
2506
|
+
application/json:
|
|
2507
|
+
schema:
|
|
2508
|
+
type: object
|
|
2509
|
+
properties:
|
|
2510
|
+
fields:
|
|
2511
|
+
type: array
|
|
2328
2512
|
|
|
2329
2513
|
/allotment/{appKey}/{userId}:
|
|
2330
2514
|
get:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* globals beforeAll describe it expect jest */
|
|
1
|
+
/* globals beforeAll beforeEach describe it expect jest */
|
|
2
2
|
const axios = require('axios');
|
|
3
3
|
const MockAdapter = require('axios-mock-adapter');
|
|
4
4
|
const chance = require('chance').Chance();
|
|
@@ -28,6 +28,9 @@ describe('allotment', () => {
|
|
|
28
28
|
let doApiPost;
|
|
29
29
|
let plugins;
|
|
30
30
|
let userToken;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
});
|
|
31
34
|
beforeAll(async () => {
|
|
32
35
|
({
|
|
33
36
|
doApiGet,
|
|
@@ -72,6 +75,8 @@ describe('allotment', () => {
|
|
|
72
75
|
const call = plugins[0].queryAllotment.mock.calls[0][0];
|
|
73
76
|
expect(call.payload).toEqual(payload);
|
|
74
77
|
expect(call.token).toEqual(token);
|
|
78
|
+
expect(call.userId).toEqual(userId);
|
|
79
|
+
expect(call.hint).toBeUndefined();
|
|
75
80
|
});
|
|
76
81
|
it('should be able to get some allotments with a hint', async () => {
|
|
77
82
|
const { allotments } = await doApiGet({
|
|
@@ -84,6 +89,8 @@ describe('allotment', () => {
|
|
|
84
89
|
const call = plugins[0].queryAllotment.mock.calls[0][0];
|
|
85
90
|
expect(call.payload).toEqual(payload);
|
|
86
91
|
expect(call.token).toEqual(token);
|
|
92
|
+
expect(call.userId).toEqual(userId);
|
|
93
|
+
expect(call.hint).toEqual(newIntegration.tokenHint);
|
|
87
94
|
});
|
|
88
95
|
it('should be able to receive an axios error', async () => {
|
|
89
96
|
// Temporarily silence console.error for this test since we're expecting an error
|
|
@@ -170,6 +170,52 @@ describe('user: bookings controller', () => {
|
|
|
170
170
|
expect(plugins[0].createBooking.mock.calls[0][0].payload).toEqual(payload);
|
|
171
171
|
expect(plugins[0].createBooking.mock.calls[0][0].token).toEqual(token);
|
|
172
172
|
});
|
|
173
|
+
it('should forward inline credentials directly to create booking', async () => {
|
|
174
|
+
const payload = {
|
|
175
|
+
id: chance.guid(),
|
|
176
|
+
credentials: {
|
|
177
|
+
sessionValue: 'abc123',
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
await doApiPost({
|
|
181
|
+
url: `/bookings/${appKey}/${userId}/testingToken/booking`,
|
|
182
|
+
token: userToken,
|
|
183
|
+
payload,
|
|
184
|
+
});
|
|
185
|
+
expect(plugins[0].createBooking).toHaveBeenCalled();
|
|
186
|
+
expect(plugins[0].createBooking.mock.calls[0][0].payload).toEqual(payload);
|
|
187
|
+
});
|
|
188
|
+
it('should support POST affiliate agents with inline credentials', async () => {
|
|
189
|
+
const payload = {
|
|
190
|
+
credentials: {
|
|
191
|
+
sessionValue: 'agent-credential',
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const { agents } = await doApiPost({
|
|
195
|
+
url: `/affiliate/${appKey}/${userId}/testingToken/agents`,
|
|
196
|
+
token: userToken,
|
|
197
|
+
payload,
|
|
198
|
+
});
|
|
199
|
+
expect(Array.isArray(agents)).toBeTruthy();
|
|
200
|
+
expect(plugins[0].getAffiliateAgents).toHaveBeenCalledTimes(1);
|
|
201
|
+
expect(plugins[0].getAffiliateAgents.mock.calls[0][0].payload).toEqual(payload);
|
|
202
|
+
});
|
|
203
|
+
it('should support POST custom-fields with inline credentials', async () => {
|
|
204
|
+
const payload = {
|
|
205
|
+
productId: 'prod-1',
|
|
206
|
+
credentials: {
|
|
207
|
+
sessionValue: 'fields-credential',
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const response = await doApiPost({
|
|
211
|
+
url: `/bookings/${appKey}/${userId}/testingToken/custom-fields`,
|
|
212
|
+
token: userToken,
|
|
213
|
+
payload,
|
|
214
|
+
});
|
|
215
|
+
expect(response.customFields).toEqual([]);
|
|
216
|
+
expect(plugins[0].getCreateItineraryFields).toHaveBeenCalledTimes(1);
|
|
217
|
+
expect(plugins[0].getCreateItineraryFields.mock.calls[0][0].payload).toEqual(payload);
|
|
218
|
+
});
|
|
173
219
|
it('should return 422 when stored app token is corrupted on bookings endpoint', async () => {
|
|
174
220
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
175
221
|
const [originalRows] = await sqldb.query(
|
|
@@ -301,5 +347,25 @@ describe('user: bookings controller', () => {
|
|
|
301
347
|
expect(products).toHaveLength(1);
|
|
302
348
|
expect(products[0].productName).toBe('Cached Product');
|
|
303
349
|
});
|
|
350
|
+
|
|
351
|
+
it('should forward inline credentials directly to product search', async () => {
|
|
352
|
+
const payload = {
|
|
353
|
+
credentials: {
|
|
354
|
+
sessionValue: 'product-credential',
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await doApiPost({
|
|
359
|
+
url: `/products/${appKey}/${userId}/testingToken/search`,
|
|
360
|
+
token: userToken,
|
|
361
|
+
payload,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(plugins[0].searchProducts).toHaveBeenCalledTimes(1);
|
|
365
|
+
expect(plugins[0].searchProducts.mock.calls[0][0].payload).toEqual({
|
|
366
|
+
...payload,
|
|
367
|
+
forceRefresh: false,
|
|
368
|
+
});
|
|
369
|
+
});
|
|
304
370
|
});
|
|
305
371
|
});
|
package/controllers/allotment.js
CHANGED
|
@@ -19,7 +19,7 @@ const queryAllotment = plugins => async (req, res, next) => {
|
|
|
19
19
|
where: {
|
|
20
20
|
userId,
|
|
21
21
|
integrationId: appKey,
|
|
22
|
-
...(hint
|
|
22
|
+
...(hint && { hint }),
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
25
|
assert(userAppKeys, 'could not find the app key');
|
|
@@ -29,6 +29,8 @@ const queryAllotment = plugins => async (req, res, next) => {
|
|
|
29
29
|
axios,
|
|
30
30
|
token,
|
|
31
31
|
payload: query,
|
|
32
|
+
userId,
|
|
33
|
+
hint,
|
|
32
34
|
requestId: req.requestId,
|
|
33
35
|
});
|
|
34
36
|
return res.json(results);
|
package/controllers/bookings.js
CHANGED
|
@@ -28,6 +28,21 @@ const typeDefsAndQueries = {
|
|
|
28
28
|
itineraryBookingQuery,
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
const getAppAndToken = async ({ plugins, appKey, userId, hint }) => {
|
|
32
|
+
const app = plugins.find(({ name }) => name === appKey);
|
|
33
|
+
assert(app, 'could not find the app ' + appKey);
|
|
34
|
+
const userAppKeys = await UserAppKey.findOne({
|
|
35
|
+
where: {
|
|
36
|
+
userId,
|
|
37
|
+
integrationId: appKey,
|
|
38
|
+
...(hint && { hint }),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
assert(userAppKeys, 'could not find the app key');
|
|
42
|
+
const token = await userAppKeys.token;
|
|
43
|
+
return { app, token };
|
|
44
|
+
};
|
|
45
|
+
|
|
31
46
|
const bookingsSearch = plugins => async (req, res, next) => {
|
|
32
47
|
const {
|
|
33
48
|
axios,
|
|
@@ -35,16 +50,7 @@ const bookingsSearch = plugins => async (req, res, next) => {
|
|
|
35
50
|
body,
|
|
36
51
|
} = req;
|
|
37
52
|
try {
|
|
38
|
-
const app =
|
|
39
|
-
const userAppKeys = await UserAppKey.findOne({
|
|
40
|
-
where: {
|
|
41
|
-
userId,
|
|
42
|
-
integrationId: appKey,
|
|
43
|
-
...(hint ? { hint } : {}),
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
assert(userAppKeys, 'could not find the app key');
|
|
47
|
-
const token = await userAppKeys.token;
|
|
53
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
48
54
|
assert(app.searchItineraries || app.searchHotelBooking || app.searchBooking, `searchItineraries or searchHotelBooking or searchBooking is not available for ${appKey}`);
|
|
49
55
|
const search = (app.searchHotelBooking || app.searchBooking || app.searchItineraries).bind(app);
|
|
50
56
|
const results = await search({
|
|
@@ -52,6 +58,8 @@ const bookingsSearch = plugins => async (req, res, next) => {
|
|
|
52
58
|
token,
|
|
53
59
|
payload: body,
|
|
54
60
|
typeDefsAndQueries,
|
|
61
|
+
userId,
|
|
62
|
+
hint,
|
|
55
63
|
requestId: req.requestId,
|
|
56
64
|
});
|
|
57
65
|
return res.json(results);
|
|
@@ -67,22 +75,14 @@ const bookingsCancel = plugins => async (req, res, next) => {
|
|
|
67
75
|
body,
|
|
68
76
|
} = req;
|
|
69
77
|
try {
|
|
70
|
-
const app =
|
|
71
|
-
// const app = load(appKey);
|
|
72
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
73
|
-
where: {
|
|
74
|
-
userId,
|
|
75
|
-
integrationId: appKey,
|
|
76
|
-
...(hint ? { hint } : {}),
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
assert(userAppKeys, 'could not find the app key');
|
|
80
|
-
const token = await userAppKeys.token;
|
|
78
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
81
79
|
const results = await app.cancelBooking({
|
|
82
80
|
axios,
|
|
83
81
|
token,
|
|
84
82
|
payload: body,
|
|
85
83
|
typeDefsAndQueries,
|
|
84
|
+
userId,
|
|
85
|
+
hint,
|
|
86
86
|
requestId: req.requestId,
|
|
87
87
|
});
|
|
88
88
|
return res.json(results);
|
|
@@ -140,16 +140,12 @@ const $bookingsProductSearch = plugins => async ({
|
|
|
140
140
|
|
|
141
141
|
// Payload for the plugin function (func) - pass forceRefresh so plugins can trigger background cache rebuilds
|
|
142
142
|
const payloadForPlugin = { ...R.omit(['forceRefresh'], originalRequestBody), forceRefresh };
|
|
143
|
+
const payloadForBackgroundJob = R.omit(['credentials'], payloadForPlugin);
|
|
143
144
|
|
|
144
|
-
const app =
|
|
145
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
145
146
|
assert(userId, 'userId is required');
|
|
146
147
|
assert(appKey, 'appKey is required');
|
|
147
148
|
assert(app.searchProducts || app.searchProductsForItinerary, `searchProducts or searchProductsForItinerary is not available for ${appKey}`);
|
|
148
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
149
|
-
where: { userId, integrationId: appKey, ...(hint ? { hint } : {}) },
|
|
150
|
-
}));
|
|
151
|
-
assert(userAppKeys, 'could not find the app key');
|
|
152
|
-
const token = await userAppKeys.token;
|
|
153
149
|
const func = (app.searchProducts || app.searchProductsForItinerary).bind(app);
|
|
154
150
|
|
|
155
151
|
const cacheKey = hash({ userId, hint, operationId: 'bookingsProductSearch' });
|
|
@@ -170,7 +166,13 @@ const $bookingsProductSearch = plugins => async ({
|
|
|
170
166
|
let pluginResults;
|
|
171
167
|
try {
|
|
172
168
|
pluginResults = await func({
|
|
173
|
-
axios,
|
|
169
|
+
axios,
|
|
170
|
+
token,
|
|
171
|
+
payload: payloadForPlugin,
|
|
172
|
+
typeDefsAndQueries,
|
|
173
|
+
requestId,
|
|
174
|
+
userId,
|
|
175
|
+
hint,
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
// This function is responsible for caching if it fetched good results.
|
|
@@ -244,7 +246,7 @@ const $bookingsProductSearch = plugins => async ({
|
|
|
244
246
|
pluginName: appKey,
|
|
245
247
|
method: app.searchProducts ? 'searchProducts' : 'searchProductsForItinerary',
|
|
246
248
|
token,
|
|
247
|
-
payload: { payload:
|
|
249
|
+
payload: { payload: payloadForBackgroundJob, userId, hint },
|
|
248
250
|
postProcess: {
|
|
249
251
|
controller: 'bookings',
|
|
250
252
|
action: '$updateProductSearchCache',
|
|
@@ -294,23 +296,15 @@ const getProductPackages = plugins => async (req, res, next) => {
|
|
|
294
296
|
body: payload,
|
|
295
297
|
} = req;
|
|
296
298
|
try {
|
|
297
|
-
const app =
|
|
298
|
-
// const app = load(appKey);
|
|
299
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
300
|
-
where: {
|
|
301
|
-
userId,
|
|
302
|
-
integrationId: appKey,
|
|
303
|
-
...(hint ? { hint } : {}),
|
|
304
|
-
},
|
|
305
|
-
}));
|
|
306
|
-
assert(userAppKeys, 'could not find the app key');
|
|
299
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
307
300
|
assert(app.getProductPackages, `getProductPackages is not available for ${appKey}`);
|
|
308
|
-
const token = await userAppKeys.token;
|
|
309
301
|
const results = await app.getProductPackages({
|
|
310
302
|
axios,
|
|
311
303
|
token,
|
|
312
304
|
payload,
|
|
313
305
|
typeDefsAndQueries,
|
|
306
|
+
userId,
|
|
307
|
+
hint,
|
|
314
308
|
requestId: req.requestId,
|
|
315
309
|
});
|
|
316
310
|
return res.json(results);
|
|
@@ -326,23 +320,15 @@ const bookingsAvailabilitySearch = plugins => async (req, res, next) => {
|
|
|
326
320
|
body: payload,
|
|
327
321
|
} = req;
|
|
328
322
|
try {
|
|
329
|
-
const app =
|
|
330
|
-
// const app = load(appKey);
|
|
331
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
332
|
-
where: {
|
|
333
|
-
userId,
|
|
334
|
-
integrationId: appKey,
|
|
335
|
-
...(hint ? { hint } : {}),
|
|
336
|
-
},
|
|
337
|
-
}));
|
|
338
|
-
assert(userAppKeys, 'could not find the app key');
|
|
339
|
-
const token = await userAppKeys.token;
|
|
323
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
340
324
|
const func = (app.searchAvailability || app.searchAvailabilityForItinerary).bind(app);
|
|
341
325
|
const results = await func({
|
|
342
326
|
axios,
|
|
343
327
|
token,
|
|
344
328
|
payload,
|
|
345
329
|
typeDefsAndQueries,
|
|
330
|
+
userId,
|
|
331
|
+
hint,
|
|
346
332
|
requestId: req.requestId,
|
|
347
333
|
});
|
|
348
334
|
return res.json(results);
|
|
@@ -359,23 +345,15 @@ const $bookingsAvailabilityCalendar = plugins => async ({
|
|
|
359
345
|
payload,
|
|
360
346
|
requestId,
|
|
361
347
|
}) => {
|
|
362
|
-
const app =
|
|
363
|
-
// const app = load(appKey);
|
|
364
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
365
|
-
where: {
|
|
366
|
-
userId,
|
|
367
|
-
integrationId: appKey,
|
|
368
|
-
...(hint ? { hint } : {}),
|
|
369
|
-
},
|
|
370
|
-
}));
|
|
371
|
-
assert(userAppKeys, 'could not find the app key');
|
|
348
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
372
349
|
assert(app.availabilityCalendar, `availabilityCalendar is not available for ${appKey}`);
|
|
373
|
-
const token = await userAppKeys.token;
|
|
374
350
|
return app.availabilityCalendar({
|
|
375
351
|
axios,
|
|
376
352
|
token,
|
|
377
353
|
payload,
|
|
378
354
|
typeDefsAndQueries,
|
|
355
|
+
userId,
|
|
356
|
+
hint,
|
|
379
357
|
requestId,
|
|
380
358
|
});
|
|
381
359
|
};
|
|
@@ -406,22 +384,14 @@ const searchQuote = plugins => async (req, res, next) => {
|
|
|
406
384
|
body: payload,
|
|
407
385
|
} = req;
|
|
408
386
|
try {
|
|
409
|
-
const app =
|
|
410
|
-
// const app = load(appKey);
|
|
411
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
412
|
-
where: {
|
|
413
|
-
userId,
|
|
414
|
-
integrationId: appKey,
|
|
415
|
-
...(hint ? { hint } : {}),
|
|
416
|
-
},
|
|
417
|
-
}));
|
|
418
|
-
assert(userAppKeys, 'could not find the app key');
|
|
419
|
-
const token = await userAppKeys.token;
|
|
387
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
420
388
|
const results = await app.searchQuote({
|
|
421
389
|
axios,
|
|
422
390
|
token,
|
|
423
391
|
payload,
|
|
424
392
|
typeDefsAndQueries,
|
|
393
|
+
userId,
|
|
394
|
+
hint,
|
|
425
395
|
requestId: req.requestId,
|
|
426
396
|
});
|
|
427
397
|
return res.json(results);
|
|
@@ -437,17 +407,7 @@ const createBooking = plugins => async (req, res, next) => {
|
|
|
437
407
|
body: payload,
|
|
438
408
|
} = req;
|
|
439
409
|
try {
|
|
440
|
-
const app =
|
|
441
|
-
// const app = load(appKey);
|
|
442
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
443
|
-
where: {
|
|
444
|
-
userId,
|
|
445
|
-
integrationId: appKey,
|
|
446
|
-
...(hint ? { hint } : {}),
|
|
447
|
-
},
|
|
448
|
-
}));
|
|
449
|
-
assert(userAppKeys, 'could not find the app key');
|
|
450
|
-
const token = await userAppKeys.token;
|
|
410
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
451
411
|
const func = (app.createBooking || app.addServiceToItinerary).bind(app);
|
|
452
412
|
let results;
|
|
453
413
|
if (payload.mock) {
|
|
@@ -458,10 +418,12 @@ const createBooking = plugins => async (req, res, next) => {
|
|
|
458
418
|
token,
|
|
459
419
|
payload,
|
|
460
420
|
typeDefsAndQueries,
|
|
421
|
+
userId,
|
|
422
|
+
hint,
|
|
461
423
|
requestId: req.requestId,
|
|
462
424
|
});
|
|
463
425
|
}
|
|
464
|
-
console.
|
|
426
|
+
console.debug(`emitting bookingsCreateBooking event for ${appKey}, user ${userId}, hint ${hint}, results: ${JSON.stringify(results)}`);
|
|
465
427
|
app.events.emit('bookingsCreateBooking', {
|
|
466
428
|
userId,
|
|
467
429
|
hint,
|
|
@@ -483,22 +445,14 @@ const getAffiliateAgents = plugins => async (req, res, next) => {
|
|
|
483
445
|
body: payload,
|
|
484
446
|
} = req;
|
|
485
447
|
try {
|
|
486
|
-
const app =
|
|
487
|
-
// const app = load(appKey);
|
|
488
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
489
|
-
where: {
|
|
490
|
-
userId,
|
|
491
|
-
integrationId: appKey,
|
|
492
|
-
...(hint ? { hint } : {}),
|
|
493
|
-
},
|
|
494
|
-
}));
|
|
495
|
-
assert(userAppKeys, 'could not find the app key');
|
|
496
|
-
const token = await userAppKeys.token;
|
|
448
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
497
449
|
assert(app.getAffiliateAgents, `getAffiliateAgents is not available for ${appKey}`);
|
|
498
450
|
const results = await app.getAffiliateAgents({
|
|
499
451
|
axios,
|
|
500
452
|
token,
|
|
501
453
|
payload,
|
|
454
|
+
userId,
|
|
455
|
+
hint,
|
|
502
456
|
requestId: req.requestId,
|
|
503
457
|
});
|
|
504
458
|
return res.json(results);
|
|
@@ -514,22 +468,14 @@ const getAffiliateDesks = plugins => async (req, res, next) => {
|
|
|
514
468
|
body: payload,
|
|
515
469
|
} = req;
|
|
516
470
|
try {
|
|
517
|
-
const app =
|
|
518
|
-
// const app = load(appKey);
|
|
519
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
520
|
-
where: {
|
|
521
|
-
userId,
|
|
522
|
-
integrationId: appKey,
|
|
523
|
-
...(hint ? { hint } : {}),
|
|
524
|
-
},
|
|
525
|
-
}));
|
|
526
|
-
assert(userAppKeys, 'could not find the app key');
|
|
527
|
-
const token = await userAppKeys.token;
|
|
471
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
528
472
|
assert(app.getAffiliateDesks, `getAffiliateDesks is not available for ${appKey}`);
|
|
529
473
|
const results = await app.getAffiliateDesks({
|
|
530
474
|
axios,
|
|
531
475
|
token,
|
|
532
476
|
payload,
|
|
477
|
+
userId,
|
|
478
|
+
hint,
|
|
533
479
|
requestId: req.requestId,
|
|
534
480
|
});
|
|
535
481
|
return res.json(results);
|
|
@@ -545,23 +491,16 @@ const getPickupPoints = plugins => async (req, res, next) => {
|
|
|
545
491
|
body: payload,
|
|
546
492
|
} = req;
|
|
547
493
|
try {
|
|
548
|
-
const app =
|
|
549
|
-
// const app = load(appKey);
|
|
550
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
551
|
-
where: {
|
|
552
|
-
userId,
|
|
553
|
-
integrationId: appKey,
|
|
554
|
-
...(hint ? { hint } : {}),
|
|
555
|
-
},
|
|
556
|
-
}));
|
|
557
|
-
assert(userAppKeys, 'could not find the app key');
|
|
558
|
-
const token = await userAppKeys.token;
|
|
494
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
559
495
|
assert(app.getPickupPoints, `getPickupPoints is not available for ${appKey}`);
|
|
560
496
|
const results = await app.getPickupPoints({
|
|
561
497
|
axios,
|
|
562
498
|
token,
|
|
563
499
|
payload,
|
|
564
500
|
typeDefsAndQueries,
|
|
501
|
+
userId,
|
|
502
|
+
hint,
|
|
503
|
+
requestId: req.requestId,
|
|
565
504
|
});
|
|
566
505
|
return res.json(results);
|
|
567
506
|
} catch (err) {
|
|
@@ -577,17 +516,7 @@ const getCreateBookingFields = plugins => async (req, res, next) => {
|
|
|
577
516
|
body: payload,
|
|
578
517
|
} = req;
|
|
579
518
|
try {
|
|
580
|
-
const app =
|
|
581
|
-
// const app = load(appKey);
|
|
582
|
-
const userAppKeys = (await UserAppKey.findOne({
|
|
583
|
-
where: {
|
|
584
|
-
userId,
|
|
585
|
-
integrationId: appKey,
|
|
586
|
-
...(hint ? { hint } : {}),
|
|
587
|
-
},
|
|
588
|
-
}));
|
|
589
|
-
assert(userAppKeys, 'could not find the app key');
|
|
590
|
-
const token = await userAppKeys.token;
|
|
519
|
+
const { app, token } = await getAppAndToken({ plugins, appKey, userId, hint });
|
|
591
520
|
assert(app.getCreateBookingFields || app.getCreateItineraryFields, `getCreateBookingFields or getCreateItineraryFields is not available for ${appKey}`);
|
|
592
521
|
const func = (app.getCreateItineraryFields || app.getCreateBookingFields).bind(app);
|
|
593
522
|
const results = await func({
|
|
@@ -596,6 +525,9 @@ const getCreateBookingFields = plugins => async (req, res, next) => {
|
|
|
596
525
|
payload,
|
|
597
526
|
query,
|
|
598
527
|
typeDefsAndQueries,
|
|
528
|
+
userId,
|
|
529
|
+
hint,
|
|
530
|
+
requestId: req.requestId,
|
|
599
531
|
});
|
|
600
532
|
return res.json(results);
|
|
601
533
|
} catch (err) {
|
package/package.json
CHANGED
package/test/plugin.js
CHANGED
|
@@ -75,6 +75,7 @@ class Plugin {
|
|
|
75
75
|
});
|
|
76
76
|
this.searchQuote = jestPlugin.fn(() => ({ quote: [{ id: chance.guid() }] }));
|
|
77
77
|
this.createBooking = jestPlugin.fn(() => {});
|
|
78
|
+
this.getAffiliateAgents = jestPlugin.fn(() => ({ agents: [] }));
|
|
78
79
|
this.searchItineraries = jestPlugin.fn(() => ({ bookings: [] }));
|
|
79
80
|
this.searchProductsForItinerary = jestPlugin.fn(() => ({ products: [] }));
|
|
80
81
|
this.searchAvailabilityForItinerary = jestPlugin.fn(({
|
package/.windsurfrules
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
This is a Node.js project using Sequelize for database operations and Express.js for the web server.
|
|
2
|
-
The API is defined in api.yml using OpenAPI 3.0.2
|
|
3
|
-
The database is defined in models/index.js
|
|
4
|
-
The models are defined in models/
|
|
5
|
-
to run tests you have to run them on the ti2 named docker container, and consider this codebase is part of node_modules/ti2 path for example :
|
|
6
|
-
`` $ docker exec ti2 bash -c "cd /ti2 && npx jest lib/__tests__/callback.js --forceExit"``
|
|
7
|
-
When writing / editing tests, make use of test/utils.js and review their implementation to understand how to mock the environment.
|
|
8
|
-
Do not make any changes until you 95% confident of the changes you want to make, ask follow up questions until you have that confidence.
|
|
9
|
-
when sending jobs to the background worker using the queue, asssume a worker is always runing to execute it but is on a different instance thread, so to review / wait for jobs we have to use the wokrer/queue.js listJobs fn to check for it
|
package/CLAUDE.md
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
Ti2 (Tourism Information Interchange) is an open-source integration framework for the tourism industry. It provides standardized functions for bookings, content, and rates management through a plugin-based architecture.
|
|
8
|
-
|
|
9
|
-
## Critical Development Rules
|
|
10
|
-
|
|
11
|
-
1. **Docker Container Execution**: ALL commands must be run inside the `ti2` Docker container
|
|
12
|
-
- The codebase is located at `/ti2` path inside the container
|
|
13
|
-
- This project is part of `node_modules/ti2` in the container environment
|
|
14
|
-
- **NEVER run npm, node, or npx commands on the host machine**
|
|
15
|
-
- Always use: `docker exec ti2 bash -c "cd /ti2 && <command>"`
|
|
16
|
-
- Example for npm install: `docker exec ti2 bash -c "cd /ti2 && npm install <package>"`
|
|
17
|
-
|
|
18
|
-
2. **Testing Rules**:
|
|
19
|
-
- Tests MUST be run inside the Docker container
|
|
20
|
-
- Example: `docker exec ti2 bash -c "cd /ti2 && npx jest lib/__tests__/callback.js --forceExit"`
|
|
21
|
-
- Use `test/utils.js` for mocking the environment
|
|
22
|
-
- Review test utility implementations before writing/editing tests
|
|
23
|
-
|
|
24
|
-
3. **Background Jobs**:
|
|
25
|
-
- Jobs sent to the queue run on a separate worker thread/instance
|
|
26
|
-
- Use `worker/queue.js` `listJobs` function to check job status
|
|
27
|
-
- Workers are always assumed to be running
|
|
28
|
-
|
|
29
|
-
4. **Code Confidence**: Do not make changes until 95% confident. Ask follow-up questions if uncertain.
|
|
30
|
-
|
|
31
|
-
## Common Development Commands
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Run tests (inside Docker container)
|
|
35
|
-
docker exec ti2 bash -c "cd /ti2 && npm test"
|
|
36
|
-
docker exec ti2 bash -c "cd /ti2 && npx jest [test-file] --forceExit"
|
|
37
|
-
|
|
38
|
-
# Run specific test file
|
|
39
|
-
docker exec ti2 bash -c "cd /ti2 && npx jest controllers/__tests__/bookings.js --forceExit"
|
|
40
|
-
|
|
41
|
-
# Database migrations
|
|
42
|
-
docker exec ti2 bash -c "cd /ti2 && npm run sequelize db:migrate"
|
|
43
|
-
|
|
44
|
-
# Generate documentation
|
|
45
|
-
docker exec ti2 bash -c "cd /ti2 && npm run doc"
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Architecture
|
|
49
|
-
|
|
50
|
-
### Core Components
|
|
51
|
-
|
|
52
|
-
1. **Main Entry Point** (`index.js`):
|
|
53
|
-
- Initializes Express server on port 10010 (default)
|
|
54
|
-
- Sets up Swagger documentation at `/api-docs`
|
|
55
|
-
- Handles plugin instantiation and middleware
|
|
56
|
-
- Implements caching mechanism
|
|
57
|
-
- Manages background job processing via `worker/queue.js`
|
|
58
|
-
|
|
59
|
-
2. **API Definition** (`api.yml`):
|
|
60
|
-
- OpenAPI 3.0.0 specification
|
|
61
|
-
- Defines all endpoints and schemas
|
|
62
|
-
- JWT Bearer authentication
|
|
63
|
-
|
|
64
|
-
3. **Plugin System**:
|
|
65
|
-
- Two types: Integration Plugins (connect systems) and App Plugins (value-added tools)
|
|
66
|
-
- Plugins are instantiated with cache, axios, events, and configuration
|
|
67
|
-
- Plugin schemas are merged with main API schema
|
|
68
|
-
- Each plugin gets its own routes under `/apps/{pluginName}`
|
|
69
|
-
|
|
70
|
-
4. **Controllers** (`controllers/`):
|
|
71
|
-
- `app.js`: Core application controller
|
|
72
|
-
- `bookings.js`: Booking operations (searchProducts, createBooking, etc.)
|
|
73
|
-
- `admin.js`: Administrative functions
|
|
74
|
-
- `user.js`: User management
|
|
75
|
-
- `ping.js`: Health check endpoints
|
|
76
|
-
- `allotment.js`: Allotment management
|
|
77
|
-
|
|
78
|
-
5. **Database Models** (`models/`):
|
|
79
|
-
- Sequelize ORM with MySQL
|
|
80
|
-
- Key models: Integration, User, UserAppKey, CronJobs, ApiCronJobs
|
|
81
|
-
- Migrations in `migrations/` directory
|
|
82
|
-
|
|
83
|
-
6. **Worker System** (`worker/`):
|
|
84
|
-
- Bull queue for background job processing
|
|
85
|
-
- Redis-backed job queue
|
|
86
|
-
- Supports plugin jobs, API jobs, and callback jobs
|
|
87
|
-
- Multi-worker support with throng
|
|
88
|
-
|
|
89
|
-
7. **Authentication** (`auth/authHandler.js`):
|
|
90
|
-
- JWT-based authentication
|
|
91
|
-
- Bearer token scheme
|
|
92
|
-
|
|
93
|
-
### Key Features
|
|
94
|
-
|
|
95
|
-
- **Caching**: Redis-based caching with configurable TTL
|
|
96
|
-
- **Event System**: EventEmitter2 for plugin communication
|
|
97
|
-
- **Background Jobs**: Async job processing with Bull/Redis
|
|
98
|
-
- **Cron Jobs**: Scheduled task execution
|
|
99
|
-
- **Multi-tenancy**: Support for multiple integrations/plugins
|
|
100
|
-
|
|
101
|
-
### Plugin Development
|
|
102
|
-
|
|
103
|
-
Plugins must implement standard methods:
|
|
104
|
-
- `validateToken`: Token validation
|
|
105
|
-
- `tokenTemplate`: Token configuration template
|
|
106
|
-
- Booking methods: `searchProducts`, `searchAvailability`, `createBooking`, `cancelBooking`
|
|
107
|
-
- Content methods: `getProducts`, `getProduct`, `createProduct`, `updateProduct`
|
|
108
|
-
|
|
109
|
-
### Testing Strategy
|
|
110
|
-
|
|
111
|
-
- Jest testing framework
|
|
112
|
-
- Test fixtures in `__fixtures__/`
|
|
113
|
-
- Mock plugins in `__mocks__/`
|
|
114
|
-
- Use `test/utils.js` for creating test environments
|
|
115
|
-
- Tests organized by controller in `controllers/__tests__/`
|
|
116
|
-
|
|
117
|
-
## Database
|
|
118
|
-
|
|
119
|
-
- Sequelize ORM with MySQL/MariaDB
|
|
120
|
-
- Connection managed through `models/db.js`
|
|
121
|
-
- Migrations handled via sequelize-cli
|
|
122
|
-
- Models auto-loaded from `models/` directory
|
|
123
|
-
|
|
124
|
-
## Environment Variables
|
|
125
|
-
|
|
126
|
-
Key variables:
|
|
127
|
-
- `PORT`: Server port (default: 10010)
|
|
128
|
-
- `REDIS_URL`: Redis connection (default: redis://redis:6379)
|
|
129
|
-
- `jwtSecret`: JWT signing secret
|
|
130
|
-
- `adminKey`: Admin authentication key
|
|
131
|
-
- `WEB_CONCURRENCY`: Number of workers
|
|
132
|
-
- `MAX_JOBS_PER_WORKER`: Job concurrency per worker
|
|
133
|
-
- `SSL_INSECURE_ALLOWED_DOMAINS`: Pipe-separated list of domains where SSL certificate verification is skipped (e.g., `localhost|staging.example.com|192.168.1.100`)
|
|
134
|
-
|
|
135
|
-
Plugin-specific variables follow pattern: `ti2_{pluginName}_{setting}`
|
|
136
|
-
|
|
137
|
-
### SSL Configuration
|
|
138
|
-
|
|
139
|
-
The `SSL_INSECURE_ALLOWED_DOMAINS` environment variable allows you to specify domains where SSL certificate verification should be skipped. This is useful for:
|
|
140
|
-
- Development environments with self-signed certificates
|
|
141
|
-
- Testing against staging servers with invalid certificates
|
|
142
|
-
- Local development with HTTPS
|
|
143
|
-
|
|
144
|
-
Example usage:
|
|
145
|
-
```bash
|
|
146
|
-
# Skip SSL verification for specific domains
|
|
147
|
-
export SSL_INSECURE_ALLOWED_DOMAINS="localhost|staging.api.com|192.168.1.100"
|
|
148
|
-
|
|
149
|
-
# Multiple domains separated by pipe
|
|
150
|
-
export SSL_INSECURE_ALLOWED_DOMAINS="dev.example.com|test.example.com"
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Important Implementation Notes**:
|
|
154
|
-
- SSL configuration is ONLY applied to plugin axios instances (for external API calls) and worker axios instances
|
|
155
|
-
- Internal communication (callbacks, per-request axios) do NOT use SSL configuration
|
|
156
|
-
- SSL configuration uses a per-request interceptor approach for better isolation
|
|
157
|
-
- The configuration checks domains dynamically on each request
|
|
158
|
-
|
|
159
|
-
**Warning**: Only use this in development/testing environments. Never skip SSL verification in production for security reasons.
|