chat-platform 1.3.2 → 2.0.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/.eslintrc +23 -16
- package/__tests__/context-provider-memory.js +148 -189
- package/__tests__/context-provider-sqlite.js +49 -82
- package/__tests__/universal-platform.js +37 -86
- package/chat-platform.js +69 -89
- package/package.json +4 -3
- package/providers/memory.js +69 -29
- package/providers/plain-file.js +32 -3
- package/providers/sqlite.js +109 -39
- package/__tests__/context-provider-plain-file.js +0 -174
package/providers/sqlite.js
CHANGED
|
@@ -10,16 +10,16 @@ const Op = Sequelize.Op;
|
|
|
10
10
|
|
|
11
11
|
const isEmpty = value => value == null || value === '';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function SQLiteStore(chatId, userId, statics = {}, warnings = false) {
|
|
17
|
-
this.chatId = chatId != null ? String(chatId) : null;
|
|
13
|
+
function SQLiteStore(userId, chatbotId, statics = {}, warnings = false) {
|
|
18
14
|
this.userId = userId != null ? String(userId) : null;
|
|
15
|
+
this.chatbotId = chatbotId;
|
|
19
16
|
// make sure userId is always a string
|
|
20
17
|
this.statics = Object.assign({}, statics, { userId: statics.userId != null ? String(statics.userId) : undefined });
|
|
21
18
|
if (warnings && _.isEmpty(statics)) {
|
|
22
|
-
console.trace('Warning: empty statics vars')
|
|
19
|
+
console.trace('Warning: empty statics vars');
|
|
20
|
+
}
|
|
21
|
+
if (warnings && _.isEmpty(chatbotId)) {
|
|
22
|
+
console.trace('Warning: empty chatbotId');
|
|
23
23
|
}
|
|
24
24
|
return this;
|
|
25
25
|
}
|
|
@@ -30,6 +30,9 @@ function SQLiteFactory(params) {
|
|
|
30
30
|
if (_.isEmpty(params.dbPath)) {
|
|
31
31
|
throw 'SQLite context provider: missing parameter "dbPath"';
|
|
32
32
|
}
|
|
33
|
+
if (_.isEmpty(params.chatbotId)) {
|
|
34
|
+
throw 'SQLite context provider: missing parameter "chatbotId"';
|
|
35
|
+
}
|
|
33
36
|
if (!fs.existsSync(params.dbPath)) {
|
|
34
37
|
//throw 'SQLite context provider: "dbPath" (' + params.path + ') doesn\'t exist';
|
|
35
38
|
try {
|
|
@@ -39,6 +42,7 @@ function SQLiteFactory(params) {
|
|
|
39
42
|
throw 'SQLite context provider: "dbPath" (' + params.path + ') doesn\'t exist and unable to create';
|
|
40
43
|
}
|
|
41
44
|
}
|
|
45
|
+
this.chatbotId = params.chatbotId;
|
|
42
46
|
|
|
43
47
|
const sequelize = new Sequelize('mission_control', '', '', {
|
|
44
48
|
host: 'localhost',
|
|
@@ -49,16 +53,28 @@ function SQLiteFactory(params) {
|
|
|
49
53
|
|
|
50
54
|
const Context = sequelize.define('context', {
|
|
51
55
|
userId: { type: Sequelize.STRING },
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
payload: { type: Sequelize.STRING },
|
|
57
|
+
chatbotId: { type: Sequelize.STRING }
|
|
54
58
|
}, {
|
|
55
59
|
indexes: [
|
|
56
|
-
{ name: '
|
|
57
|
-
{ name: '
|
|
60
|
+
{ name: 'context_userid', using: 'BTREE', fields: ['userId'] },
|
|
61
|
+
{ name: 'context_chatbotId', using: 'BTREE', fields: ['chatbotId'] }
|
|
58
62
|
]
|
|
59
63
|
});
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
const ChatId = sequelize.define('chatid', {
|
|
66
|
+
userId: { type: Sequelize.STRING, allowNull: false },
|
|
67
|
+
chatId: { type: Sequelize.STRING, allowNull: false },
|
|
68
|
+
transport: { type: Sequelize.STRING, allowNull: false },
|
|
69
|
+
chatbotId: { type: Sequelize.TEXT }
|
|
70
|
+
}, {
|
|
71
|
+
indexes: [
|
|
72
|
+
{ name: 'chatid_userid', using: 'BTREE', fields: ['userId'] },
|
|
73
|
+
{ name: 'chatid_chatid', using: 'BTREE', fields: ['chatId'] },
|
|
74
|
+
{ name: 'chatid_transport', using: 'BTREE', fields: ['transport'] },
|
|
75
|
+
{ name: 'chatid_chatbotId', using: 'BTREE', fields: ['chatbotId'] }
|
|
76
|
+
]
|
|
77
|
+
});
|
|
62
78
|
|
|
63
79
|
// **
|
|
64
80
|
// Start definition if SQLite store, with closure I can spare passing a Context as configuration
|
|
@@ -97,30 +113,14 @@ function SQLiteFactory(params) {
|
|
|
97
113
|
},
|
|
98
114
|
|
|
99
115
|
async getPayload() {
|
|
100
|
-
// get payload using
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{ userId: this.userId }
|
|
105
|
-
]
|
|
116
|
+
// get payload using userId (it generally defaults to chatId)
|
|
117
|
+
const context = await Context.findOne({ where: {
|
|
118
|
+
userId: this.userId,
|
|
119
|
+
chatbotId: this.chatbotId
|
|
106
120
|
}});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// if not present then create the row
|
|
111
|
-
context = await Context.create({ payload: JSON.stringify({}), chatId: this.chatId, userId: this.userId });
|
|
112
|
-
payload = {};
|
|
113
|
-
} else {
|
|
114
|
-
// if by any change there are two matched rows, one for the chatId and one for userId
|
|
115
|
-
// always prefer the userId (that could happen if the user star using the sqlite provider) as
|
|
116
|
-
// is and at some point the MC_store assign the context to the user
|
|
117
|
-
context = contexts.find(context => context.userId === this.userId);
|
|
118
|
-
if (context == null) {
|
|
119
|
-
context = contexts.find(context => context.chatId === this.chatId);
|
|
120
|
-
}
|
|
121
|
-
if (context == null) {
|
|
122
|
-
context = contexts[0];
|
|
123
|
-
}
|
|
121
|
+
|
|
122
|
+
if (context != null) {
|
|
123
|
+
let payload;
|
|
124
124
|
// finally decode
|
|
125
125
|
try {
|
|
126
126
|
payload = JSON.parse(context.payload);
|
|
@@ -128,8 +128,16 @@ function SQLiteFactory(params) {
|
|
|
128
128
|
// default if error
|
|
129
129
|
payload = {};
|
|
130
130
|
}
|
|
131
|
+
return { payload, id: context.id };
|
|
132
|
+
} else {
|
|
133
|
+
// if not present then create the row
|
|
134
|
+
const context = await Context.create({
|
|
135
|
+
payload: JSON.stringify({}),
|
|
136
|
+
chatbotId: this.chatbotId,
|
|
137
|
+
userId: this.userId
|
|
138
|
+
});
|
|
139
|
+
return { payload: {}, id: context.id };
|
|
131
140
|
}
|
|
132
|
-
return { payload, id: context.id };
|
|
133
141
|
},
|
|
134
142
|
|
|
135
143
|
async set(key, value) {
|
|
@@ -168,20 +176,82 @@ function SQLiteFactory(params) {
|
|
|
168
176
|
return this;
|
|
169
177
|
}
|
|
170
178
|
});
|
|
179
|
+
|
|
171
180
|
// **
|
|
172
181
|
// End definition if SQLite store
|
|
173
182
|
// **
|
|
174
183
|
|
|
184
|
+
this.getOrCreateUserId = async function(chatId, transport, defaultUserId) {
|
|
185
|
+
// try to find a userId given then chatId / transport for the current chatbot
|
|
186
|
+
const binding = await ChatId.findOne({
|
|
187
|
+
where: { chatId, transport, chatbotId: this.chatbotId }
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (binding != null) {
|
|
191
|
+
return binding.userId;
|
|
192
|
+
} else {
|
|
193
|
+
const userId = !_.isEmpty(defaultUserId) ? defaultUserId : chatId;
|
|
194
|
+
await ChatId.create({ chatId, transport, chatbotId: this.chatbotId, userId });
|
|
195
|
+
return userId;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* getUserId
|
|
201
|
+
* Get the userId from chatId, if doesn't exist, defaults to chatId
|
|
202
|
+
*/
|
|
203
|
+
this.getUserId = async function(chatId, transport) {
|
|
204
|
+
const binding = await ChatId.findOne({
|
|
205
|
+
where: { chatId, transport, chatbotId: this.chatbotId }
|
|
206
|
+
});
|
|
207
|
+
return binding != null ? binding.userId : chatId;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this.getChatId = async function(userId, transport) {
|
|
211
|
+
const binding = await ChatId.findOne({
|
|
212
|
+
where: { userId, transport, chatbotId: this.chatbotId }
|
|
213
|
+
});
|
|
214
|
+
return binding != null ? binding.chatId : null;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
this.getOrCreateContext = async function(userId, statics) {
|
|
218
|
+
if (isEmpty(userId)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// just create an class that just wraps userId, chatbotId and static value (cline)
|
|
222
|
+
const store = new SQLiteStore(userId, this.chatbotId, { ...statics });
|
|
223
|
+
return store;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
this.mergeUserId = async function(fromUserId, toUserId) {
|
|
227
|
+
const fromChatIds = await ChatId.findAll({ where: { userId: fromUserId, chatbotId: this.chatbotId }});
|
|
228
|
+
const toChatIds = await ChatId.findAll({ where: { userId: toUserId, chatbotId: this.chatbotId }});
|
|
229
|
+
|
|
230
|
+
// turn only chatIds that don't already exists
|
|
231
|
+
for (const item of fromChatIds) {
|
|
232
|
+
const hasTransport = toChatIds.filter(({ transport }) => transport === item.transport).length !== 0;
|
|
233
|
+
if (!hasTransport) {
|
|
234
|
+
await ChatId.update({ userId: toUserId }, { where: { id: item.id }});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// deprecated below
|
|
240
|
+
|
|
175
241
|
this.getOrCreate = async function(chatId, userId, statics) {
|
|
176
242
|
if (isEmpty(chatId) && isEmpty(userId)) {
|
|
177
243
|
return null;
|
|
178
244
|
}
|
|
179
|
-
// just create an class that just wraps
|
|
180
|
-
const store = new SQLiteStore(
|
|
245
|
+
// just create an class that just wraps userId, chatbotId and static value (cline)
|
|
246
|
+
const store = new SQLiteStore(userId, this.chatbotId, { ...statics });
|
|
181
247
|
return store;
|
|
182
248
|
};
|
|
183
249
|
this.get = function(chatId, userId, statics) {
|
|
184
|
-
|
|
250
|
+
console.warn('This is deprectated, use getContext instead')
|
|
251
|
+
return new SQLiteStore(userId, this.chatbotId, statics);
|
|
252
|
+
};
|
|
253
|
+
this.getContext = function(userId, statics) {
|
|
254
|
+
return new SQLiteStore(userId, this.chatbotId, statics);
|
|
185
255
|
};
|
|
186
256
|
this.assignToUser = async (userId, context) => {
|
|
187
257
|
};
|
|
@@ -213,6 +283,7 @@ function SQLiteFactory(params) {
|
|
|
213
283
|
try {
|
|
214
284
|
if (createTable) {
|
|
215
285
|
await Context.sync();
|
|
286
|
+
await ChatId.sync();
|
|
216
287
|
}
|
|
217
288
|
// then log, don't move, keep the log lines together
|
|
218
289
|
console.log(lcd.timestamp() + 'SQLite context provider configuration:');
|
|
@@ -263,5 +334,4 @@ _.extend(SQLiteFactory.prototype, {
|
|
|
263
334
|
}
|
|
264
335
|
});
|
|
265
336
|
|
|
266
|
-
|
|
267
337
|
module.exports = SQLiteFactory;
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
const _ = require('underscore');
|
|
2
|
-
const assert = require('chai').assert;
|
|
3
|
-
const RED = require('../lib/red-stub')();
|
|
4
|
-
const ContextProviders = require('../chat-context-factory');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const { when } = require('../lib/utils');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
describe('Chat context provider file', () => {
|
|
11
|
-
|
|
12
|
-
const contextProviders = ContextProviders(RED);
|
|
13
|
-
//const path = os.tmpdir();
|
|
14
|
-
const path = __dirname + '/test-files';
|
|
15
|
-
const provider = contextProviders.getProvider('plain-file', { path });
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
return provider.reset({ path });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterAll(() => {
|
|
22
|
-
return provider.reset({ path });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should create a context provider with some default params', () => {
|
|
26
|
-
|
|
27
|
-
assert.isTrue(contextProviders.hasProvider('plain-file'));
|
|
28
|
-
const provider = contextProviders.getProvider('plain-file', { path });
|
|
29
|
-
assert.isFunction(provider.getOrCreate);
|
|
30
|
-
assert.isFunction(provider.get);
|
|
31
|
-
|
|
32
|
-
return when(provider.getOrCreate(42, null, { myVariable: 'initial value'}))
|
|
33
|
-
.then(chatContext => {
|
|
34
|
-
assert.isFunction(chatContext.get);
|
|
35
|
-
assert.isFunction(chatContext.set);
|
|
36
|
-
assert.isFunction(chatContext.all);
|
|
37
|
-
assert.isFunction(chatContext.remove);
|
|
38
|
-
assert.isFunction(chatContext.clear);
|
|
39
|
-
return chatContext.get('myVariable');
|
|
40
|
-
}).then(myVariable => {
|
|
41
|
-
assert.equal(myVariable, 'initial value');
|
|
42
|
-
// assert.isTrue(fs.existsSync(`${path}/store-7369f3c86bf3c0a354615432832d9e8f.json`));
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should set some value and then get and remove it', async () => {
|
|
47
|
-
const chatContext = await provider.getOrCreate(42, null, {});
|
|
48
|
-
await chatContext.set('firstName', 'Guidone');
|
|
49
|
-
let firstName =await chatContext.get('firstName');
|
|
50
|
-
assert.equal(firstName, 'Guidone');
|
|
51
|
-
await chatContext.remove('firstName');
|
|
52
|
-
firstName = await chatContext.get('firstName');
|
|
53
|
-
assert.isUndefined(firstName);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
it('should set some values and then get and remove it', async () => {
|
|
58
|
-
const chatContext = await provider.getOrCreate(42, null, {});
|
|
59
|
-
await chatContext.set({firstName: 'Guido', lastName: 'Bellomo'});
|
|
60
|
-
let firstName = await chatContext.get('firstName');
|
|
61
|
-
assert.equal(firstName, 'Guido');
|
|
62
|
-
let lastName = await chatContext.get('lastName');
|
|
63
|
-
assert.equal(lastName, 'Bellomo');
|
|
64
|
-
let json = await chatContext.get('firstName', 'lastName');
|
|
65
|
-
assert.isObject(json);
|
|
66
|
-
assert.equal(json.firstName, 'Guido');
|
|
67
|
-
assert.equal(json.lastName, 'Bellomo');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should set some values and then get and remove it with userId', async () => {
|
|
71
|
-
const chatContext = await provider.getOrCreate(null, 43, {});
|
|
72
|
-
await chatContext.set({ firstName: 'Guido', lastName: 'Bellomo' });
|
|
73
|
-
let firstName = await chatContext.get('firstName');
|
|
74
|
-
assert.equal(firstName, 'Guido');
|
|
75
|
-
let lastName = await chatContext.get('lastName');
|
|
76
|
-
assert.equal(lastName, 'Bellomo');
|
|
77
|
-
let json = await chatContext.get('firstName', 'lastName');
|
|
78
|
-
assert.isObject(json);
|
|
79
|
-
assert.equal(json.firstName, 'Guido');
|
|
80
|
-
assert.equal(json.lastName, 'Bellomo');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should set some values and get the dump with chatId', async () => {
|
|
84
|
-
const chatContext = await provider.getOrCreate(42, null, {});
|
|
85
|
-
await chatContext.set({firstName: 'Guido', lastName: 'Bellomo', email: 'spam@gmail.com'});
|
|
86
|
-
const json = await chatContext.all();
|
|
87
|
-
assert.isObject(json);
|
|
88
|
-
assert.equal(json.firstName, 'Guido');
|
|
89
|
-
assert.equal(json.lastName, 'Bellomo');
|
|
90
|
-
assert.equal(json.email, 'spam@gmail.com');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should set some values and get the dump with userId', async () => {
|
|
94
|
-
const chatContext = await provider.getOrCreate(null, 43, {});
|
|
95
|
-
await chatContext.set({firstName: 'Guido', lastName: 'Bellomo', email: 'spam@gmail.com'});
|
|
96
|
-
const json = await chatContext.all();
|
|
97
|
-
assert.isObject(json);
|
|
98
|
-
assert.equal(json.firstName, 'Guido');
|
|
99
|
-
assert.equal(json.lastName, 'Bellomo');
|
|
100
|
-
assert.equal(json.email, 'spam@gmail.com');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should set some values and remove all for chatId', async () => {
|
|
104
|
-
const chatContext = await provider.getOrCreate(42, {});
|
|
105
|
-
await chatContext.set({firstName: 'Guido', lastName: 'Bellomo'});
|
|
106
|
-
await chatContext.clear();
|
|
107
|
-
let firstName = await chatContext.get('firstName');
|
|
108
|
-
assert.isUndefined(firstName);
|
|
109
|
-
let lastName = await chatContext.get('lastName');
|
|
110
|
-
assert.isUndefined(lastName);
|
|
111
|
-
const json = await chatContext.all();
|
|
112
|
-
assert.isObject(json);
|
|
113
|
-
assert.isTrue(_.isEmpty(json));
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should set some values and remove all for userId', async () => {
|
|
117
|
-
const chatContext = await provider.getOrCreate(null, 43, {});
|
|
118
|
-
await chatContext.set({firstName: 'Guido', lastName: 'Bellomo'});
|
|
119
|
-
await chatContext.clear();
|
|
120
|
-
const firstName = await chatContext.get('firstName');
|
|
121
|
-
assert.isUndefined(firstName)
|
|
122
|
-
const lastName = await chatContext.get('lastName');
|
|
123
|
-
assert.isUndefined(lastName)
|
|
124
|
-
const json = await chatContext.all();
|
|
125
|
-
assert.isObject(json);
|
|
126
|
-
assert.isTrue(_.isEmpty(json));
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should set some value the remove with multiple arguments for chatId', async () => {
|
|
130
|
-
const chatContext = await provider.getOrCreate(42, {});
|
|
131
|
-
await chatContext.set({firstName: 'Guidone', lastName: 'Bellomo', email: 'some@email'})
|
|
132
|
-
const firstName = await chatContext.get('firstName');
|
|
133
|
-
assert.equal(firstName, 'Guidone');
|
|
134
|
-
await chatContext.remove('firstName', 'lastName', 'email');
|
|
135
|
-
const json = await chatContext.all();
|
|
136
|
-
assert.isUndefined(json.firstName);
|
|
137
|
-
assert.isUndefined(json.lastName);
|
|
138
|
-
assert.isUndefined(json.email);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should set some value the remove with multiple arguments for userId', async () => {
|
|
142
|
-
const chatContext = await provider.getOrCreate(null, 43, {});
|
|
143
|
-
await chatContext.set({firstName: 'Guidone', lastName: 'Bellomo', email: 'some@email'});
|
|
144
|
-
const firstName = await chatContext.get('firstName');
|
|
145
|
-
assert.equal(firstName, 'Guidone');
|
|
146
|
-
await chatContext.remove('firstName', 'lastName', 'email');
|
|
147
|
-
const json = await chatContext.all();
|
|
148
|
-
assert.isUndefined(json.firstName);
|
|
149
|
-
assert.isUndefined(json.lastName);
|
|
150
|
-
assert.isUndefined(json.email);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should set some value with chatId and userId', async () => {
|
|
154
|
-
const chatContext = await provider.getOrCreate(42, 44, { userId: 44, chatId: 42 });
|
|
155
|
-
await chatContext.set('firstName', 'Guidone');
|
|
156
|
-
let firstName = await chatContext.get('firstName');
|
|
157
|
-
assert.equal(firstName, 'Guidone');
|
|
158
|
-
let userId = await chatContext.get('userId');
|
|
159
|
-
assert.equal(userId, 44);
|
|
160
|
-
let chatId = await chatContext.get('chatId');
|
|
161
|
-
assert.equal(chatId, 42);
|
|
162
|
-
const chatContext2 = await provider.getOrCreate(null, 44, { userId: 44, chatId: 42 });
|
|
163
|
-
firstName = await chatContext2.get('firstName');
|
|
164
|
-
assert.equal(firstName, 'Guidone');
|
|
165
|
-
userId = await chatContext2.get('userId');
|
|
166
|
-
assert.equal(userId, 44);
|
|
167
|
-
chatId = await chatContext2.get('chatId');
|
|
168
|
-
assert.equal(chatId, 42);
|
|
169
|
-
await chatContext2.remove('firstName');
|
|
170
|
-
firstName = await chatContext2.get('firstName');
|
|
171
|
-
assert.isUndefined(firstName);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
});
|