ti2 1.0.13 → 1.0.16
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/.github/workflows/tests.yml +1 -3
- package/README.md +1 -13
- package/api.yml +91 -0
- package/cli.js +36 -0
- package/controllers/admin.js +46 -1
- package/controllers/app.js +52 -1
- package/controllers/bookings.js +28 -0
- package/jsdoc.conf.json +4 -1
- package/models/db.js +0 -1
- package/package.json +4 -1
- package/test/plugin.js +20 -5
- package/tutorials/index.json +1 -1
- package/tutorials/plugin-development.md +36 -23
- package/tutorials/setup-your-instance.md +33 -12
package/README.md
CHANGED
|
@@ -22,21 +22,9 @@ App Plugins are value added tools that only speak to Ti2 and other plugins. For
|
|
|
22
22
|
|
|
23
23
|
Ti2 can be deployed on your own servers, or you can connect to a Ti2 instance that another company is hosting.
|
|
24
24
|
|
|
25
|
-
### Security
|
|
26
|
-
|
|
27
|
-
An admin api key is set trough an environment variable called adminKey; using this API key is possible to create Users and/or application authentications.
|
|
28
|
-
|
|
29
|
-
A new User / User Authentication can be created using the endpoint [createUserToken](https://ti2.tourconnect.dev/api-docs/#/admin/createUserToken); the return value consists of a JWT token for further user intraction with the App; the admin API key is required; no password is saved on the server side.
|
|
30
|
-
|
|
31
|
-
Apps / Integrations can be created using the admin API Key; this after the plugin had been added to the codebase; the app can be created using the endpoint [createApp](https://ti2.tourconnect.dev/api-docs/#/admin/createApp); such endpoint returns a key that can be used by the app to interact with user Ids; such interactions allow the app to push for changes related to user integrations, the app can list the users currently configured for it using the endpoint [listAppTokens](https://ti2.tourconnect.dev/api-docs/#/app/listAppTokens); the passwords for Apps are encripted onde the database using aes-256-cbc, more details can be found on the [``models/integrations.js``](https://github.com/ti2travel/ti2/blob/main/models/integration.js) file.
|
|
32
|
-
|
|
33
|
-
User + app integration credentials can be added after the app has been added to the system; these can be added by either the user, admin or the app itself using the endpoint [createAppToken](https://ti2.tourconnect.dev/api-docs/#/app/createAppToken). These keys are saved in JWT format on the database.
|
|
34
|
-
|
|
35
25
|
## Getting Started
|
|
36
26
|
|
|
37
|
-
You can
|
|
38
|
-
|
|
39
|
-
By default a ti2 instance serves a documentation page on the url (http://[yourinstance]/api-docs/) that serves the available API restfull methods, you can review the [swagger documentation page online](https://ti2-staging.tourconnect.com/api-docs/) .
|
|
27
|
+
You can write your [own plugin]{@tutorial plugin-development} that hooks up to a Ti2 instance or you can run a server that that hosts your [own **Ti2** instance]{@tutorial setup-your-instance} with any plugins (your own or from the plugin library).
|
|
40
28
|
|
|
41
29
|
|
|
42
30
|
## Plugins
|
package/api.yml
CHANGED
|
@@ -816,6 +816,97 @@ paths:
|
|
|
816
816
|
application/json:
|
|
817
817
|
schema:
|
|
818
818
|
$ref: '#/components/schemas/bookingsProductSearchReturn'
|
|
819
|
+
/bookings/{appKey}/{userId}/availabilitycalendar:
|
|
820
|
+
post:
|
|
821
|
+
security:
|
|
822
|
+
- bearerAuth: ['admin', 'user']
|
|
823
|
+
tags:
|
|
824
|
+
- bookings
|
|
825
|
+
summary: Availability Search
|
|
826
|
+
operationId: bookingsAvailabilityCalendar
|
|
827
|
+
parameters:
|
|
828
|
+
- name: appKey
|
|
829
|
+
in: path
|
|
830
|
+
description: app to search availability for, defaults to first token
|
|
831
|
+
required: true
|
|
832
|
+
example: 'travelgate-hotelx'
|
|
833
|
+
schema:
|
|
834
|
+
type: string
|
|
835
|
+
- name: userId
|
|
836
|
+
in: path
|
|
837
|
+
description: the user/company that the appKey belongs to
|
|
838
|
+
required: true
|
|
839
|
+
schema:
|
|
840
|
+
type: string
|
|
841
|
+
requestBody:
|
|
842
|
+
description: both app details
|
|
843
|
+
required: true
|
|
844
|
+
content:
|
|
845
|
+
application/json:
|
|
846
|
+
schema:
|
|
847
|
+
$ref: '#/components/schemas/AnyValue'
|
|
848
|
+
responses:
|
|
849
|
+
'200':
|
|
850
|
+
description: search results
|
|
851
|
+
content:
|
|
852
|
+
application/json:
|
|
853
|
+
schema:
|
|
854
|
+
type: object
|
|
855
|
+
properties:
|
|
856
|
+
availability:
|
|
857
|
+
type: array
|
|
858
|
+
required: true
|
|
859
|
+
items:
|
|
860
|
+
$ref: '#/components/schemas/availabilityItem'
|
|
861
|
+
/bookings/{appKey}/{userId}/{hint}/availabilitycalendar:
|
|
862
|
+
post:
|
|
863
|
+
security:
|
|
864
|
+
- bearerAuth: ['admin', 'user']
|
|
865
|
+
tags:
|
|
866
|
+
- bookings
|
|
867
|
+
summary: Availability Search for an specific hint
|
|
868
|
+
operationId: bookingsAvailabilityCalendar
|
|
869
|
+
parameters:
|
|
870
|
+
- name: appKey
|
|
871
|
+
in: path
|
|
872
|
+
description: app to search availability for, defaults to first token
|
|
873
|
+
required: true
|
|
874
|
+
example: 'travelgate-hotelx'
|
|
875
|
+
schema:
|
|
876
|
+
type: string
|
|
877
|
+
- name: userId
|
|
878
|
+
in: path
|
|
879
|
+
description: the user/company that the appKey belongs to
|
|
880
|
+
required: true
|
|
881
|
+
schema:
|
|
882
|
+
type: string
|
|
883
|
+
- name: hint
|
|
884
|
+
in: path
|
|
885
|
+
description: a specific token hint to be used
|
|
886
|
+
required: true
|
|
887
|
+
example: 'travelgate-australia'
|
|
888
|
+
schema:
|
|
889
|
+
type: string
|
|
890
|
+
requestBody:
|
|
891
|
+
description: both app details
|
|
892
|
+
required: true
|
|
893
|
+
content:
|
|
894
|
+
application/json:
|
|
895
|
+
schema:
|
|
896
|
+
$ref: '#/components/schemas/AnyValue'
|
|
897
|
+
responses:
|
|
898
|
+
'200':
|
|
899
|
+
description: search results
|
|
900
|
+
content:
|
|
901
|
+
application/json:
|
|
902
|
+
schema:
|
|
903
|
+
type: object
|
|
904
|
+
properties:
|
|
905
|
+
availability:
|
|
906
|
+
type: array
|
|
907
|
+
required: true
|
|
908
|
+
items:
|
|
909
|
+
$ref: '#/components/schemas/availabilityItem'
|
|
819
910
|
/bookings/{appKey}/{userId}/availability:
|
|
820
911
|
post:
|
|
821
912
|
security:
|
package/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command, Argument } = require('commander');
|
|
4
|
+
|
|
5
|
+
const { migrateApp } = require('./controllers/app');
|
|
6
|
+
const { migrate } = require('./controllers/admin');
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('ti2-cli')
|
|
12
|
+
.description('TI2 Command line interface utilities')
|
|
13
|
+
.version((require('./package.json')).version);
|
|
14
|
+
|
|
15
|
+
program.command('dbapp')
|
|
16
|
+
.description('Handle database migrations for apps')
|
|
17
|
+
.argument('<string>', 'integration to operate on')
|
|
18
|
+
.addArgument(
|
|
19
|
+
new Argument('<action>', 'action to take', 'migrate')
|
|
20
|
+
.choices(['migrate', 'revert']),
|
|
21
|
+
)
|
|
22
|
+
.action(async (integration, action) => {
|
|
23
|
+
await migrateApp({ integrationId: integration, action });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
program.command('db')
|
|
27
|
+
.description('Handle database migrations for integrations')
|
|
28
|
+
.addArgument(
|
|
29
|
+
new Argument('<action>', 'action to take', 'migrate')
|
|
30
|
+
.choices(['migrate', 'revert']),
|
|
31
|
+
)
|
|
32
|
+
.action(async action => {
|
|
33
|
+
await migrate({ action });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program.parse();
|
package/controllers/admin.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const jwt = require('jsonwebtoken');
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { omit } = require('ramda');
|
|
4
|
+
const { Umzug, SequelizeStorage } = require('umzug');
|
|
5
|
+
const Sequelize = require('sequelize');
|
|
6
|
+
const fs = require('fs').promises;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
4
9
|
const sqldb = require('../models');
|
|
5
10
|
|
|
6
11
|
const { env: { jwtSecret } } = process;
|
|
@@ -91,10 +96,50 @@ const resetIntegrationToken = async (req, res, next) => {
|
|
|
91
96
|
}
|
|
92
97
|
};
|
|
93
98
|
|
|
99
|
+
const migrate = async ({ action }) => {
|
|
100
|
+
const { sequelize } = sqldb;
|
|
101
|
+
const migrationsPath = path.join(
|
|
102
|
+
__dirname,
|
|
103
|
+
'../',
|
|
104
|
+
'migrations',
|
|
105
|
+
);
|
|
106
|
+
try {
|
|
107
|
+
await fs.access(migrationsPath);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
throw Error(`Could not find any migrations on ${migrationsPath}`);
|
|
110
|
+
}
|
|
111
|
+
const umzug = new Umzug({
|
|
112
|
+
migrations: {
|
|
113
|
+
glob: `${migrationsPath}/*.js`,
|
|
114
|
+
resolve: ({ name, path: migPath, context }) => {
|
|
115
|
+
const migration = require(migPath);
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
up: async () => migration.up(context, Sequelize),
|
|
119
|
+
down: async () => migration.down(context, Sequelize),
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
context: sequelize.getQueryInterface(),
|
|
124
|
+
storage: new SequelizeStorage({
|
|
125
|
+
sequelize,
|
|
126
|
+
}),
|
|
127
|
+
logger: console,
|
|
128
|
+
});
|
|
129
|
+
if (action === 'migrate') {
|
|
130
|
+
return umzug.up();
|
|
131
|
+
}
|
|
132
|
+
if (action === 'revert') {
|
|
133
|
+
return umzug.down({ to: 0 });
|
|
134
|
+
}
|
|
135
|
+
throw Error('No recognized action');
|
|
136
|
+
};
|
|
137
|
+
|
|
94
138
|
module.exports = {
|
|
95
139
|
createApp,
|
|
96
|
-
deleteApp,
|
|
97
140
|
createUserToken,
|
|
141
|
+
deleteApp,
|
|
98
142
|
listApps,
|
|
143
|
+
migrate,
|
|
99
144
|
resetIntegrationToken,
|
|
100
145
|
};
|
package/controllers/app.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
const sqldb = require('../models');
|
|
4
10
|
|
|
5
11
|
const { env: { jwtSecret } } = process;
|
|
@@ -83,9 +89,54 @@ const listAppTokens = async (req, res, next) => {
|
|
|
83
89
|
}
|
|
84
90
|
};
|
|
85
91
|
|
|
92
|
+
const migrateApp = async ({ integrationId, action }) => {
|
|
93
|
+
const { sequelize } = sqldb;
|
|
94
|
+
assert(integrationId);
|
|
95
|
+
assert(action);
|
|
96
|
+
const migrationsPath = path.join(
|
|
97
|
+
__dirname,
|
|
98
|
+
'../',
|
|
99
|
+
'../',
|
|
100
|
+
`ti2-${integrationId}`,
|
|
101
|
+
'migrations',
|
|
102
|
+
);
|
|
103
|
+
try {
|
|
104
|
+
await fs.access(migrationsPath);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw Error(`Could not find any migrations for ${integrationId} on ${migrationsPath}`);
|
|
107
|
+
}
|
|
108
|
+
const umzug = new Umzug({
|
|
109
|
+
migrations: {
|
|
110
|
+
glob: `${migrationsPath}/*.js`,
|
|
111
|
+
resolve: ({ name, path: migPath, context }) => {
|
|
112
|
+
const migration = require(migPath);
|
|
113
|
+
return {
|
|
114
|
+
name,
|
|
115
|
+
up: async () => migration.up(context, Sequelize),
|
|
116
|
+
down: async () => migration.down(context, Sequelize),
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
context: sequelize.getQueryInterface(),
|
|
121
|
+
storage: new SequelizeStorage({
|
|
122
|
+
sequelize,
|
|
123
|
+
tableName: `SequelizeMeta-${integrationId}`,
|
|
124
|
+
}),
|
|
125
|
+
logger: console,
|
|
126
|
+
});
|
|
127
|
+
if (action === 'migrate') {
|
|
128
|
+
return umzug.up();
|
|
129
|
+
}
|
|
130
|
+
if (action === 'revert') {
|
|
131
|
+
return umzug.down({ to: 0 });
|
|
132
|
+
}
|
|
133
|
+
throw Error('No recognized action');
|
|
134
|
+
};
|
|
135
|
+
|
|
86
136
|
module.exports = {
|
|
87
|
-
jwtEncode,
|
|
88
137
|
createAppToken,
|
|
89
138
|
deleteAppToken,
|
|
139
|
+
jwtEncode,
|
|
90
140
|
listAppTokens,
|
|
141
|
+
migrateApp,
|
|
91
142
|
};
|
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/jsdoc.conf.json
CHANGED
package/models/db.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ti2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Tourist Industry Exchange (TI2)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "git+https://github.com/ti2travel/ti2.git"
|
|
14
14
|
},
|
|
15
|
+
"bin": "./cli.js",
|
|
15
16
|
"author": "",
|
|
16
17
|
"license": "GPL-3",
|
|
17
18
|
"bugs": {
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"homepage": "https://github.com/ti2travel/ti2#readme",
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"bluebird": "^3.7.2",
|
|
24
|
+
"commander": "^9.2.0",
|
|
23
25
|
"express": "^4.17.2",
|
|
24
26
|
"js-yaml": "^4.1.0",
|
|
25
27
|
"jsonwebtoken": "^8.5.1",
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
"swagger-express-middleware": "3.0.0-alpha.5",
|
|
33
35
|
"swagger-routes-express": "3.3.1",
|
|
34
36
|
"swagger-ui-express": "4.3.0",
|
|
37
|
+
"umzug": "^3.1.1",
|
|
35
38
|
"uninstall": "^0.0.0"
|
|
36
39
|
},
|
|
37
40
|
"devDependencies": {
|
package/test/plugin.js
CHANGED
|
@@ -3,7 +3,7 @@ const chance = require('chance').Chance();
|
|
|
3
3
|
|
|
4
4
|
class Plugin {
|
|
5
5
|
/**
|
|
6
|
-
* For a comprehensive list of
|
|
6
|
+
* For a comprehensive list of methods visit the
|
|
7
7
|
* [Plugin Development]{@tutorial plugin-development} page.
|
|
8
8
|
*
|
|
9
9
|
* ---
|
|
@@ -46,11 +46,11 @@ class Plugin {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Check the
|
|
49
|
+
* Check the validity of an integration token.
|
|
50
50
|
* @async
|
|
51
51
|
* @param {Object} args - Availability search arguments.
|
|
52
52
|
* @param {Object} args.token - A token definition, it's content varies between integrations.
|
|
53
|
-
* @returns {Boolean} - The token
|
|
53
|
+
* @returns {Boolean} - The token validity True/False
|
|
54
54
|
*/
|
|
55
55
|
validateToken() {
|
|
56
56
|
return Boolean(this);
|
|
@@ -306,7 +306,7 @@ class Plugin {
|
|
|
306
306
|
* @param {string} args.payload.travelDateEnd - Date of travel end.
|
|
307
307
|
* @param {Holder} args.payload.holder - Search for a particular holder.
|
|
308
308
|
* @returns {object} retVal - the return object.
|
|
309
|
-
* @returns {Booking[]} retVal.bookings - Array of Bookings
|
|
309
|
+
* @returns {Booking[]} retVal.bookings - Array of Bookings matching the criteria.
|
|
310
310
|
*/
|
|
311
311
|
searchBooking() {
|
|
312
312
|
}
|
|
@@ -363,7 +363,22 @@ class Plugin {
|
|
|
363
363
|
searchAvailability() {
|
|
364
364
|
// return (args);
|
|
365
365
|
}
|
|
366
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Retrieve an availability calendar over a date range
|
|
368
|
+
* @async
|
|
369
|
+
* @param {Object} args - Availability search arguments.
|
|
370
|
+
* @param {Object} args.token - A token definition, it's content varies between integrations.
|
|
371
|
+
* @param {Object} args.payload - Search spect object.
|
|
372
|
+
* @param {string} args.payload.dateFormat - Date format sent, using [momentjs formatting](https://momentjs.com/docs/#/parsing/string-format/).
|
|
373
|
+
* @param {string} args.payload.travelDateStart - Date of travel start.
|
|
374
|
+
* @param {string} args.payload.travelDateEnd - Date of travel end.
|
|
375
|
+
* @param {Occupancy[]} args.payload.occupancies - Occupancy detail.
|
|
376
|
+
* @returns {object} retVal - the return object
|
|
377
|
+
* @returns {Availability[]} retVal.availability - Array of availability objects
|
|
378
|
+
*/
|
|
379
|
+
availabilityCalndar() {
|
|
380
|
+
// return (args);
|
|
381
|
+
}
|
|
367
382
|
/**
|
|
368
383
|
* A quote return spec
|
|
369
384
|
* @typedef {Object} Quote
|
package/tutorials/index.json
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Getting Started
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plugins can extend the functionality of Ti2 and/or provide access to other platforms; you should use one of the following methods to provide a stadandarized way to access other systems; if you want to implement new functionality that is not encompassed on the available methods you are more than welcome to add new methods and contribute to the Ti2 codebase.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```env
|
|
8
|
-
ti2_tourconnect_apiUrl=http://backend:8080
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Methods
|
|
5
|
+
## Available Methods
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
We are using a set of standardized methods to maintain compatibility; the following methods are available, more can be supported but should be a added to the TI2 spec first to maximize it's compatibility.
|
|
14
8
|
|
|
15
9
|
**General Methods** (should always be included):
|
|
16
10
|
* [validateToken]{@link Plugin#validateToken}
|
|
17
11
|
|
|
18
|
-
**Content Methods** (
|
|
12
|
+
**Content Methods** (marketing information for (company)profile, locations and/or products):
|
|
19
13
|
* [getProfile]{@link Plugin#getProfile}
|
|
20
14
|
* [updateProfile]{@link Plugin#updateProfile}
|
|
21
15
|
* [createLocation]{@link Plugin#createLocation}
|
|
@@ -24,7 +18,7 @@ In oorder for an integration or system apps, we must use a set of standirzed met
|
|
|
24
18
|
* [createProduct]{@link Plugin#createProduct}
|
|
25
19
|
* [updateProduct]{@link Plugin#updateProduct}
|
|
26
20
|
|
|
27
|
-
**Booking Methods** (
|
|
21
|
+
**Booking Methods** (bookings and operations):
|
|
28
22
|
* [searchBooking]{@link Plugin#searchBooking}
|
|
29
23
|
* [searchProducts]{@link Plugin#searchProducts}
|
|
30
24
|
* [searchAvailability]{@link Plugin#searchAvailability}
|
|
@@ -33,10 +27,20 @@ In oorder for an integration or system apps, we must use a set of standirzed met
|
|
|
33
27
|
|
|
34
28
|
---
|
|
35
29
|
|
|
30
|
+
## Environment variables
|
|
31
|
+
|
|
32
|
+
Plugin environment keys are passed down from the hosting ti2 instance when they are generated, this is the preferred way to access environment keys from the plugins, such values are not design to hold client / user's API keys or specific data, they are to be stored in the database via the AppKey collection.
|
|
33
|
+
|
|
34
|
+
Plugin ENV variable name convention: ti2_pluginName_environmenVariableName, for example:
|
|
35
|
+
|
|
36
|
+
```env
|
|
37
|
+
ti2_tourconnect_apiUrl=http://backend:8080
|
|
38
|
+
```
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
## Codebase setup
|
|
38
42
|
|
|
39
|
-
You can review some of the plugins previously developed and use them as a guide, plugin methods are encourage to include tests and return results
|
|
43
|
+
You can review some of the plugins previously developed and use them as a guide, plugin methods are encourage to include tests and to return results with the same format; however, the returned values can include additional information, currently Ti2 is expected to run on node version 12.22.8; we suggest you use the same for your codebase.
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
After initializing your git environment for development; you can initialize your node project; we recommend naming your repo ti2-\<pluginname\> but it is not required.
|
|
@@ -61,7 +65,7 @@ class Plugin {
|
|
|
61
65
|
module.exports = Plugin;
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
The constructor of the plugins should extend the attributes with the received params, this allows the plugin to receive configurations or settings that are to be used by any user
|
|
68
|
+
The constructor of the plugins should extend the attributes with the received params, this allows the plugin to receive configurations or settings that are to be used by any user, for example, settings data that can be shared across accounts.
|
|
65
69
|
|
|
66
70
|
By default Ti2 will pass down all environment variables that match the plugin name to it's constructor; for example, if the following environment variables are defined:
|
|
67
71
|
|
|
@@ -98,7 +102,7 @@ class Plugin {
|
|
|
98
102
|
module.exports = Plugin;
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
The token parameter
|
|
105
|
+
The token parameter should store all the settings for the current configured end user; on these example we are defaulting this settings to the ones configured to the pluging when it was instanced; so if the user settings do not include an apiUrl it will fall back to the environment variable on the running server.
|
|
102
106
|
|
|
103
107
|
## Testing Suite
|
|
104
108
|
|
|
@@ -141,7 +145,9 @@ describe('Base Tests', () => {
|
|
|
141
145
|
$ npx jest
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
##
|
|
148
|
+
## Deploying
|
|
149
|
+
|
|
150
|
+
### To your own Instance
|
|
145
151
|
|
|
146
152
|
After setting up a [Ti2 instance]{@tutorial setup-your-instance} you can add your development folder as a package on your instance's npm repo (not on the plugin's instance), like so:
|
|
147
153
|
|
|
@@ -151,13 +157,17 @@ After setting up a [Ti2 instance]{@tutorial setup-your-instance} you can add you
|
|
|
151
157
|
}
|
|
152
158
|
```
|
|
153
159
|
|
|
160
|
+
### To our hosted instance
|
|
161
|
+
|
|
162
|
+
[Contact us](https://www.ti2.travel/contact-us) with the npm published package to begin the testing and deployment process.
|
|
163
|
+
|
|
154
164
|
## Extending the base API
|
|
155
165
|
|
|
156
|
-
Ti2 uses the [Swagger API specification](https://swagger.io/specification/) standard to define it's own methods, you can review the
|
|
166
|
+
Ti2 uses the [Swagger API specification](https://swagger.io/specification/) standard to define it's own methods, you can review the basic methods [here](https://ti2-staging.tourconnect.com/api-docs/); these methods can be extended using the same format; this allows any plugin to extend the base API endpoints that are linked to any plugin method (part of the basic methods or new ones).
|
|
157
167
|
|
|
158
168
|
All the extender methods would be availble under the /\[plugin name] namespace, i.e.: /ti2-greatPlugin/ping.
|
|
159
169
|
|
|
160
|
-
For Ti2 to pickup the extender definition one must create a new file on the root of the plugin named api.yml, such file would contain only the methods to be
|
|
170
|
+
For Ti2 to pickup the extender definition, one must create a new file on the root of the plugin named api.yml, such file would contain only the methods to be extended and it must comply with the current swagger version used on ti2.
|
|
161
171
|
|
|
162
172
|
```yaml
|
|
163
173
|
# api.yml
|
|
@@ -202,18 +212,21 @@ paths:
|
|
|
202
212
|
|
|
203
213
|
## Database Migrations (plugin's own database tables)
|
|
204
214
|
|
|
205
|
-
Ti2 uses [Sequelize v6.13](https://sequelize.org/v6)
|
|
215
|
+
Ti2 uses [Sequelize v6.13](https://sequelize.org/v6) which is the database ORM we ecourage you to use, you can add your own tables via migrations that should be placed under a migrations folder on the root of the plugin folder.
|
|
206
216
|
|
|
207
217
|
Migrations should be run after the fact, from the root of ti2 instance like so:
|
|
208
218
|
|
|
209
|
-
|
|
210
219
|
```
|
|
211
|
-
$
|
|
220
|
+
$ ti2 dbapp [plugin name] migrate
|
|
212
221
|
```
|
|
213
222
|
|
|
214
|
-
We strongly encourage all the plugin's table names are
|
|
223
|
+
We strongly encourage all the plugin's table names are prefixed with the plugin name (i.e. ti2-pluginName-cacheTable).
|
|
224
|
+
|
|
225
|
+
This should be executed after all ti2 host migrations had been executed.
|
|
215
226
|
|
|
216
|
-
|
|
227
|
+
```
|
|
228
|
+
$ ti2 db migrate
|
|
229
|
+
```
|
|
217
230
|
|
|
218
231
|
## Scheduled Task (to be released on v2)
|
|
219
232
|
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Why would I want to run my own instance?
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
* Scenario 1: You want to test your plugins before submitting it to our Ti2 instance, it is a good idea to spin up your own Ti2 test instance, you can test against all the Ti2 REST Apis (In the future, we will provide a Ti2 sandbox instance).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
* Scenario 2: You want to host your own Ti2 server so you can have everything under your own control.
|
|
6
|
+
|
|
7
|
+
When you are runnning a Ti2 server you can access the documentation page on the url (http://[yourinstance]/api-docs/) that shows the available API methods, you can review the [swagger documentation page online](https://ti2-staging.tourconnect.com/api-docs/) .
|
|
8
|
+
|
|
9
|
+
## Security
|
|
10
|
+
|
|
11
|
+
An admin api key is set trough an environment variable called adminKey; using this API key is possible to create Users and/or application authentications.
|
|
12
|
+
|
|
13
|
+
A new User/User Authentication can be created using the endpoint [createUserToken](https://ti2.tourconnect.dev/api-docs/#/admin/createUserToken); the return value consists of a JWT token for further user intraction with the App; the admin API key is required; no password is saved on the server side.
|
|
14
|
+
|
|
15
|
+
Apps/Integrations can be created using the admin API Key; this after the plugin had been added to the codebase; the app can be created using the endpoint [createApp](https://ti2.tourconnect.dev/api-docs/#/admin/createApp); such endpoint returns a key that can be used by the app to interact with user Ids; such interactions allow the app to push for changes related to user integrations, the app can list the users currently configured for it using the endpoint [listAppTokens](https://ti2.tourconnect.dev/api-docs/#/app/listAppTokens); the passwords for Apps are encripted onde the database using aes-256-cbc, more details can be found on the [``models/integrations.js``](https://github.com/ti2travel/ti2/blob/main/models/integration.js) file.
|
|
16
|
+
|
|
17
|
+
User + app integration credentials can be added after the app has been added to the system; these can be added by either the user, admin or the app itself using the endpoint [createAppToken](https://ti2.tourconnect.dev/api-docs/#/app/createAppToken). These keys are saved in JWT format on the database.
|
|
11
18
|
|
|
12
|
-
##
|
|
19
|
+
## Get Started
|
|
20
|
+
|
|
21
|
+
### Requirements and Evironment variables
|
|
13
22
|
|
|
14
23
|
A MySQl instance is required and the following environment variables are required:
|
|
15
24
|
|
|
@@ -28,13 +37,25 @@ frontendKey=IpOiLahrGQxsQimwoK7Z
|
|
|
28
37
|
adminKey=bCviyz7iKyuEBoOVlODn
|
|
29
38
|
```
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
### Initialize a new NodeJS project
|
|
41
|
+
|
|
42
|
+
We suggest creating a folder with your instance name+i2 (i.e acme-ti2); then initialize an node's npm package and install ti2 along with any plugins to be used.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
$ mkdir acme-ti2
|
|
46
|
+
$ cd acmme-ti2
|
|
47
|
+
$ npm init .
|
|
48
|
+
$ npm i ti2 ti2-tourconnect
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
After installing the ti2 package you need to run the database migrations for ti2:
|
|
32
53
|
|
|
33
54
|
```
|
|
34
|
-
$
|
|
55
|
+
$ ti2 db migrate
|
|
35
56
|
```
|
|
36
57
|
|
|
37
|
-
|
|
58
|
+
### Entry File
|
|
38
59
|
|
|
39
60
|
then just create an entry file like this :
|
|
40
61
|
|