nodebb-plugin-dbsearch 6.1.0 → 6.2.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/lib/dbsearch.js +106 -8
- package/lib/mongo.js +81 -12
- package/lib/postgres.js +55 -0
- package/lib/redis.js +34 -3
- package/package.json +3 -3
- package/plugin.json +7 -1
- package/public/admin.js +2 -0
- package/templates/admin/plugins/dbsearch.tpl +9 -0
package/lib/dbsearch.js
CHANGED
|
@@ -8,6 +8,7 @@ const nconf = require.main.require('nconf');
|
|
|
8
8
|
const db = require.main.require('./src/database');
|
|
9
9
|
const topics = require.main.require('./src/topics');
|
|
10
10
|
const posts = require.main.require('./src/posts');
|
|
11
|
+
const messaging = require.main.require('./src/messaging');
|
|
11
12
|
const utils = require.main.require('./src/utils');
|
|
12
13
|
const socketAdmin = require.main.require('./src/socket.io/admin');
|
|
13
14
|
const batch = require.main.require('./src/batch');
|
|
@@ -156,6 +157,22 @@ search.actionTopicChangeOwner = function (hookData) {
|
|
|
156
157
|
topicsSave(hookData.topics);
|
|
157
158
|
};
|
|
158
159
|
|
|
160
|
+
search.actionMessagingSave = async function (hookData) {
|
|
161
|
+
await messagesSave([hookData.message]);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
search.actionMessagingDelete = async function (hookData) {
|
|
165
|
+
await searchRemove('chat', [hookData.message.mid]);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
search.actionMessagingRestore = async function (hookData) {
|
|
169
|
+
await messagesSave([hookData.message]);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
search.actionMessagingEdit = async function (hookData) {
|
|
173
|
+
await messagesSave([hookData.message]);
|
|
174
|
+
};
|
|
175
|
+
|
|
159
176
|
search.filterSearchQuery = async function (data) {
|
|
160
177
|
if (!data || !data.index) {
|
|
161
178
|
return data;
|
|
@@ -199,15 +216,43 @@ search.filterSearchTopic = async function (hookData) {
|
|
|
199
216
|
return hookData;
|
|
200
217
|
};
|
|
201
218
|
|
|
219
|
+
search.filterMessagingSearchMessages = async function (data) {
|
|
220
|
+
if (!data || !data.content) {
|
|
221
|
+
return data;
|
|
222
|
+
}
|
|
223
|
+
const limit = 100;
|
|
224
|
+
const query = {};
|
|
225
|
+
if (data.hasOwnProperty('roomId') && data.roomId) {
|
|
226
|
+
query.roomId = data.roomId;
|
|
227
|
+
}
|
|
228
|
+
if (data.hasOwnProperty('uid') && data.uid) {
|
|
229
|
+
query.uid = data.uid;
|
|
230
|
+
}
|
|
231
|
+
if (data.hasOwnProperty('content') && data.content) {
|
|
232
|
+
query.content = data.content;
|
|
233
|
+
}
|
|
234
|
+
if (!Object.keys(query).length) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
if (data.hasOwnProperty('matchWords')) {
|
|
238
|
+
query.matchWords = data.matchWords;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
data.ids = data.ids.concat(await searchModule.chat.search(query, limit));
|
|
242
|
+
return data;
|
|
243
|
+
};
|
|
244
|
+
|
|
202
245
|
search.reindex = async function () {
|
|
203
246
|
await db.setObject('nodebb-plugin-dbsearch', {
|
|
204
247
|
topicsIndexed: 0,
|
|
205
248
|
postsIndexed: 0,
|
|
249
|
+
messagesIndexed: 0,
|
|
206
250
|
working: 1,
|
|
207
251
|
});
|
|
208
252
|
await Promise.all([
|
|
209
253
|
reIndexTopics(),
|
|
210
254
|
reIndexPosts(),
|
|
255
|
+
reIndexMessages(),
|
|
211
256
|
]);
|
|
212
257
|
await db.setObject('nodebb-plugin-dbsearch', {
|
|
213
258
|
working: 0,
|
|
@@ -308,12 +353,55 @@ async function postsSave(posts) {
|
|
|
308
353
|
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'postsIndexed', result.pids.length);
|
|
309
354
|
}
|
|
310
355
|
|
|
356
|
+
async function reIndexMessages() {
|
|
357
|
+
await batch.processSortedSet(`messages:mid`, async (mids) => {
|
|
358
|
+
let messageData = await messaging.getMessagesFields(mids, ['mid', 'content', 'roomId', 'fromuid', 'deleted', 'system']);
|
|
359
|
+
messageData = messageData.filter(p => p && p.deleted !== 1 && p.system !== 1);
|
|
360
|
+
await messagesSave(messageData);
|
|
361
|
+
}, {
|
|
362
|
+
batch: batchSize,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function messagesSave(msgs) {
|
|
367
|
+
msgs = msgs.filter(m => m && m.mid && parseInt(m.deleted, 10) !== 1 && parseInt(m.system, 10) !== 1);
|
|
368
|
+
|
|
369
|
+
let data = msgs.map((msgData) => {
|
|
370
|
+
const indexData = {};
|
|
371
|
+
if (msgData.content) {
|
|
372
|
+
indexData.content = msgData.content;
|
|
373
|
+
}
|
|
374
|
+
if (msgData.roomId) {
|
|
375
|
+
indexData.roomId = msgData.roomId;
|
|
376
|
+
}
|
|
377
|
+
if (msgData.fromuid) {
|
|
378
|
+
indexData.uid = msgData.fromuid;
|
|
379
|
+
}
|
|
380
|
+
if (!Object.keys(indexData).length) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
return indexData;
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const mids = msgs.filter((msg, index) => !!data[index]).map(msg => msg.mid);
|
|
387
|
+
data = data.filter(Boolean);
|
|
388
|
+
if (!data.length) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const result = await plugins.hooks.fire('filter:search.indexMessages', { data: data, mids: mids, messages: msgs });
|
|
393
|
+
await searchModule.chat.index(result.data, result.mids);
|
|
394
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'messagesIndexed', result.mids.length);
|
|
395
|
+
}
|
|
396
|
+
|
|
311
397
|
async function searchRemove(key, ids) {
|
|
312
398
|
await db.searchRemove(key, ids);
|
|
313
399
|
if (key === 'topic') {
|
|
314
400
|
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'topicsIndexed', -ids.length);
|
|
315
401
|
} else if (key === 'post') {
|
|
316
402
|
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'postsIndexed', -ids.length);
|
|
403
|
+
} else if (key === 'chat') {
|
|
404
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'messagesIndexed', -ids.length);
|
|
317
405
|
}
|
|
318
406
|
}
|
|
319
407
|
|
|
@@ -398,6 +486,7 @@ async function getPluginData() {
|
|
|
398
486
|
const data = await db.getObject('nodebb-plugin-dbsearch') || {};
|
|
399
487
|
data.topicsIndexed = parseInt(data.topicsIndexed, 10) || 0;
|
|
400
488
|
data.postsIndexed = parseInt(data.postsIndexed, 10) || 0;
|
|
489
|
+
data.messagesIndexed = parseInt(data.messagesIndexed, 10) || 0;
|
|
401
490
|
data.excludeCategories = data.excludeCategories || '[]';
|
|
402
491
|
data.postLimit = data.postLimit || defaultPostLimit;
|
|
403
492
|
data.topicLimit = data.topicLimit || defaultTopicLimit;
|
|
@@ -415,7 +504,7 @@ async function getPluginData() {
|
|
|
415
504
|
|
|
416
505
|
async function getGlobalAndPluginData() {
|
|
417
506
|
const [global, plugin, allCategories] = await Promise.all([
|
|
418
|
-
db.getObjectFields('global', ['topicCount', 'postCount']),
|
|
507
|
+
db.getObjectFields('global', ['topicCount', 'postCount', 'messageCount']),
|
|
419
508
|
getPluginData(),
|
|
420
509
|
categories.buildForSelectAll(['value', 'text']),
|
|
421
510
|
]);
|
|
@@ -431,10 +520,12 @@ async function getGlobalAndPluginData() {
|
|
|
431
520
|
plugin.allCategories = allCategories;
|
|
432
521
|
plugin.topicCount = parseInt(global.topicCount, 10);
|
|
433
522
|
plugin.postCount = parseInt(global.postCount, 10);
|
|
523
|
+
plugin.messageCount = parseInt(global.messageCount, 10);
|
|
434
524
|
plugin.topicLimit = plugin.topicLimit || defaultTopicLimit;
|
|
435
525
|
plugin.postLimit = plugin.postLimit || defaultPostLimit;
|
|
436
526
|
plugin.topicsIndexed = plugin.topicsIndexed > plugin.topicCount ? plugin.topicCount : plugin.topicsIndexed;
|
|
437
527
|
plugin.postsIndexed = plugin.postsIndexed > plugin.postCount ? plugin.postCount : plugin.postsIndexed;
|
|
528
|
+
plugin.messagesIndexed = plugin.messagesIndexed > plugin.messageCount ? plugin.messageCount : plugin.messagesIndexed;
|
|
438
529
|
plugin.languageSupported = languageSupported;
|
|
439
530
|
plugin.languages = languages;
|
|
440
531
|
plugin.indexLanguage = plugin.indexLanguage || 'en';
|
|
@@ -451,35 +542,40 @@ async function getGlobalAndPluginData() {
|
|
|
451
542
|
|
|
452
543
|
async function getProgress() {
|
|
453
544
|
const [global, pluginData] = await Promise.all([
|
|
454
|
-
db.getObjectFields('global', ['topicCount', 'postCount']),
|
|
545
|
+
db.getObjectFields('global', ['topicCount', 'postCount', 'messageCount']),
|
|
455
546
|
getPluginData(),
|
|
456
547
|
]);
|
|
457
548
|
const topicCount = parseInt(global.topicCount, 10);
|
|
458
549
|
const postCount = parseInt(global.postCount, 10);
|
|
550
|
+
const messageCount = parseInt(global.messageCount, 10);
|
|
459
551
|
const topicsPercent = topicCount ? (pluginData.topicsIndexed / topicCount) * 100 : 0;
|
|
460
552
|
const postsPercent = postCount ? (pluginData.postsIndexed / postCount) * 100 : 0;
|
|
553
|
+
const messagesPercent = messageCount ? (pluginData.messagesIndexed / messageCount) * 100 : 0;
|
|
461
554
|
return {
|
|
462
555
|
topicsPercent: Math.max(0, Math.min(100, topicsPercent.toFixed(2))),
|
|
463
556
|
postsPercent: Math.max(0, Math.min(100, postsPercent.toFixed(2))),
|
|
557
|
+
messagesPercent: Math.max(0, Math.min(100, messagesPercent.toFixed(2))),
|
|
464
558
|
topicsIndexed: topicsPercent >= 100 ? topicCount : Math.max(0, pluginData.topicsIndexed),
|
|
465
559
|
postsIndexed: postsPercent >= 100 ? postCount : Math.max(0, pluginData.postsIndexed),
|
|
560
|
+
messagesIndexed: messagesPercent >= 100 ? messageCount : Math.max(0, pluginData.messagesIndexed),
|
|
466
561
|
working: pluginData.working,
|
|
467
562
|
};
|
|
468
563
|
}
|
|
469
564
|
|
|
470
565
|
socketAdmin.plugins.dbsearch.reindex = function (socket, data, callback) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
566
|
+
setTimeout(async () => {
|
|
567
|
+
try {
|
|
568
|
+
await search.reindex();
|
|
569
|
+
} catch (err) {
|
|
570
|
+
winston.error(err);
|
|
571
|
+
}
|
|
572
|
+
}, 0);
|
|
476
573
|
callback();
|
|
477
574
|
};
|
|
478
575
|
|
|
479
576
|
socketAdmin.plugins.dbsearch.clearIndex = async function () {
|
|
480
577
|
setTimeout(async () => {
|
|
481
578
|
try {
|
|
482
|
-
console.log('clear called');
|
|
483
579
|
await clearIndex();
|
|
484
580
|
} catch (err) {
|
|
485
581
|
winston.error(err.stack);
|
|
@@ -495,11 +591,13 @@ async function clearIndex() {
|
|
|
495
591
|
await Promise.all([
|
|
496
592
|
clearSet('topics:tid', 'topic'),
|
|
497
593
|
clearSet('posts:pid', 'post'),
|
|
594
|
+
clearSet('messages:mid', 'chat'),
|
|
498
595
|
]);
|
|
499
596
|
|
|
500
597
|
await db.setObject('nodebb-plugin-dbsearch', {
|
|
501
598
|
postsIndexed: 0,
|
|
502
599
|
topicsIndexed: 0,
|
|
600
|
+
messagesIndexed: 0,
|
|
503
601
|
working: 0,
|
|
504
602
|
});
|
|
505
603
|
}
|
package/lib/mongo.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.createIndices = async function (language) {
|
|
|
13
13
|
if (nconf.get('isPrimary') && !nconf.get('jobsDisabled')) {
|
|
14
14
|
await db.client.collection('searchtopic').createIndex({ content: 'text', uid: 1, cid: 1 }, options);
|
|
15
15
|
await db.client.collection('searchpost').createIndex({ content: 'text', uid: 1, cid: 1 }, options);
|
|
16
|
+
await db.client.collection('searchchat').createIndex({ content: 'text', roomId: 1, uid: 1 }, options);
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -26,6 +27,8 @@ exports.changeIndexLanguage = async function (language) {
|
|
|
26
27
|
await db.client.collection('searchtopic').createIndex(indexSpec, options);
|
|
27
28
|
await db.client.collection('searchpost').dropIndex('content_text_uid_1_cid_1');
|
|
28
29
|
await db.client.collection('searchpost').createIndex(indexSpec, options);
|
|
30
|
+
await db.client.collection('searchchat').dropIndex('content_text_roomId_1_uid_1');
|
|
31
|
+
await db.client.collection('searchchat').createIndex({ content: 'text', roomId: 1, uid: 1 }, options);
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
exports.searchIndex = async function (key, data, ids) {
|
|
@@ -53,18 +56,7 @@ exports.searchIndex = async function (key, data, ids) {
|
|
|
53
56
|
exports.search = async function (key, data, limit) {
|
|
54
57
|
const searchQuery = {};
|
|
55
58
|
if (data.content) {
|
|
56
|
-
|
|
57
|
-
const allQuoted = data.content.startsWith('"') && data.content.endsWith('"');
|
|
58
|
-
if (data.matchWords === 'all' && !allQuoted) {
|
|
59
|
-
words = words.map((word) => {
|
|
60
|
-
if (!word.startsWith('"') && !word.endsWith('"')) {
|
|
61
|
-
return `"${word}"`;
|
|
62
|
-
}
|
|
63
|
-
return word;
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
searchQuery.$text = { $search: words.join(' ') };
|
|
59
|
+
searchQuery.$text = buildTextQuery(data.content, data.matchWords);
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
if (Array.isArray(data.cid) && data.cid.length) {
|
|
@@ -112,3 +104,80 @@ exports.searchRemove = async function (key, ids) {
|
|
|
112
104
|
|
|
113
105
|
await db.client.collection(`search${key}`).deleteMany({ _id: { $in: ids } });
|
|
114
106
|
};
|
|
107
|
+
|
|
108
|
+
exports.chat = {};
|
|
109
|
+
exports.chat.index = async (data, ids) => {
|
|
110
|
+
if (!ids.length) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ids = ids.map(id => parseInt(id, 10));
|
|
115
|
+
|
|
116
|
+
const bulk = db.client.collection(`searchchat`).initializeUnorderedBulkOp();
|
|
117
|
+
ids.forEach((id, index) => {
|
|
118
|
+
const d = data[index];
|
|
119
|
+
if (d && d.content && d.uid && d.roomId) {
|
|
120
|
+
bulk.find({ _id: id }).upsert().updateOne({
|
|
121
|
+
$set: {
|
|
122
|
+
content: String(d.content),
|
|
123
|
+
roomId: String(d.roomId),
|
|
124
|
+
uid: String(d.uid),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await bulk.execute();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
exports.chat.search = async (data, limit) => {
|
|
134
|
+
const searchQuery = {};
|
|
135
|
+
if (!data.content) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
searchQuery.$text = buildTextQuery(data.content, data.matchWords);
|
|
139
|
+
|
|
140
|
+
if (Array.isArray(data.roomId) && data.roomId.filter(Boolean).length) {
|
|
141
|
+
if (data.roomId.length > 1) {
|
|
142
|
+
searchQuery.roomId = { $in: data.roomId.filter(Boolean).map(String) };
|
|
143
|
+
} else {
|
|
144
|
+
searchQuery.roomId = String(data.roomId[0]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (Array.isArray(data.uid) && data.uid.filter(Boolean).length) {
|
|
149
|
+
if (data.uid.length > 1) {
|
|
150
|
+
searchQuery.uid = { $in: data.uid.filter(Boolean).map(String) };
|
|
151
|
+
} else {
|
|
152
|
+
searchQuery.uid = String(data.uid[0]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const collection = db.client.collection(`searchchat`);
|
|
157
|
+
const results = await collection.aggregate([
|
|
158
|
+
{ $match: searchQuery },
|
|
159
|
+
{ $sort: { score: { $meta: 'textScore' } } },
|
|
160
|
+
{ $limit: parseInt(limit, 10) },
|
|
161
|
+
{ $project: { _id: 1 } },
|
|
162
|
+
]).toArray();
|
|
163
|
+
if (!results || !results.length) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return results.map(item => item._id);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function buildTextQuery(content, matchWords) {
|
|
170
|
+
let words = content.split(' ');
|
|
171
|
+
const allQuoted = content.startsWith('"') && content.endsWith('"');
|
|
172
|
+
if (matchWords === 'all' && !allQuoted) {
|
|
173
|
+
words = words.map((word) => {
|
|
174
|
+
if (!word.startsWith('"') && !word.endsWith('"')) {
|
|
175
|
+
return `"${word}"`;
|
|
176
|
+
}
|
|
177
|
+
return word;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { $search: words.join(' ') };
|
|
182
|
+
}
|
|
183
|
+
|
package/lib/postgres.js
CHANGED
|
@@ -17,10 +17,16 @@ async function initDB() {
|
|
|
17
17
|
await db.pool.query(`CREATE INDEX IF NOT EXISTS "idx__searchtopic__content" ON "searchtopic" USING GIN (to_tsvector('${searchLanguage}', "content"))`);
|
|
18
18
|
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchtopic__uid" ON "searchtopic"("uid")');
|
|
19
19
|
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchtopic__cid" ON "searchtopic"("cid")');
|
|
20
|
+
|
|
20
21
|
await db.pool.query('CREATE TABLE IF NOT EXISTS "searchpost" ( "id" BIGINT NOT NULL PRIMARY KEY, "content" TEXT, "uid" BIGINT, "cid" BIGINT )');
|
|
21
22
|
await db.pool.query(`CREATE INDEX IF NOT EXISTS "idx__searchpost__content" ON "searchpost" USING GIN (to_tsvector('${searchLanguage}', "content"))`);
|
|
22
23
|
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchpost__uid" ON "searchpost"("uid")');
|
|
23
24
|
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchpost__cid" ON "searchpost"("cid")');
|
|
25
|
+
|
|
26
|
+
await db.pool.query('CREATE TABLE IF NOT EXISTS "searchchat" ( "id" BIGINT NOT NULL PRIMARY KEY, "content" TEXT, "rid" BIGINT, "uid" BIGINT, )');
|
|
27
|
+
await db.pool.query(`CREATE INDEX IF NOT EXISTS "idx__searchchat__content" ON "searchchat" USING GIN (to_tsvector('${searchLanguage}', "content"))`);
|
|
28
|
+
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchchat__rid" ON "searchchat"("rid")');
|
|
29
|
+
await db.pool.query('CREATE INDEX IF NOT EXISTS "idx__searchchat__uid" ON "searchchat"("uid")');
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
async function handleError(err) {
|
|
@@ -45,8 +51,12 @@ exports.changeIndexLanguage = async function (language) {
|
|
|
45
51
|
pubsub.publish('dbsearch-language-changed', language);
|
|
46
52
|
await db.pool.query('DROP INDEX "idx__searchtopic__content"');
|
|
47
53
|
await db.pool.query(`CREATE INDEX "idx__searchtopic__content" ON "searchtopic" USING GIN (to_tsvector('${language}', "content"))`);
|
|
54
|
+
|
|
48
55
|
await db.pool.query('DROP INDEX "idx__searchpost__content"');
|
|
49
56
|
await db.pool.query(`CREATE INDEX "idx__searchpost__content" ON "searchpost" USING GIN (to_tsvector('${language}', "content"))`);
|
|
57
|
+
|
|
58
|
+
await db.pool.query('DROP INDEX "idx__searchchat__content"');
|
|
59
|
+
await db.pool.query(`CREATE INDEX "idx__searchchat__content" ON "searchchat" USING GIN (to_tsvector('${language}', "content"))`);
|
|
50
60
|
};
|
|
51
61
|
|
|
52
62
|
exports.searchIndex = async function (key, data, ids) {
|
|
@@ -109,3 +119,48 @@ exports.searchRemove = async function (key, ids) {
|
|
|
109
119
|
await handleError(err);
|
|
110
120
|
}
|
|
111
121
|
};
|
|
122
|
+
|
|
123
|
+
exports.chat = {};
|
|
124
|
+
exports.chat.index = async (data, ids) => {
|
|
125
|
+
if (!ids.length) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ids = ids.map(id => parseInt(id, 10));
|
|
130
|
+
try {
|
|
131
|
+
await db.pool.query({
|
|
132
|
+
name: `dbsearch-searchIndex-chat`,
|
|
133
|
+
text: `INSERT INTO "searchchat" SELECT d."id", d."data"->>'content' "content", (d."data"->>'uid')::bigint "uid", (d."data"->>'roomId')::bigint "roomId" FROM UNNEST($1::bigint[], $2::jsonb[]) d("id", "data") ON CONFLICT ("id") DO UPDATE SET "content" = COALESCE(EXCLUDED."content", "searchchat"."content"), "uid" = COALESCE(EXCLUDED."uid", "searchchat"."uid"), "roomId" = COALESCE(EXCLUDED."roomId", "searchchat"."roomId")`,
|
|
134
|
+
values: [ids, data],
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
winston.error(`Error indexing ${err.stack}`);
|
|
138
|
+
await handleError(err);
|
|
139
|
+
await exports.chat.index(data, ids);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
exports.chat.search = async (data, limit) => {
|
|
144
|
+
if (Array.isArray(data.uid) && data.uid.filter(Boolean).length) {
|
|
145
|
+
data.uid = data.uid.filter(Boolean);
|
|
146
|
+
} else {
|
|
147
|
+
data.uid = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (Array.isArray(data.roomId) && data.roomId.filter(Boolean).length) {
|
|
151
|
+
data.roomId = data.roomId.filter(Boolean);
|
|
152
|
+
} else {
|
|
153
|
+
data.roomId = null;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const res = await db.pool.query({
|
|
157
|
+
name: `dbsearch-search-chat`,
|
|
158
|
+
text: `SELECT ARRAY(SELECT s."id" FROM "searchchat" s WHERE ($1::text IS NULL OR to_tsvector($5::regconfig, "content") @@ plainto_tsquery($5::regconfig, $1::text)) AND ($2::bigint[] IS NULL OR "uid" = ANY($2::bigint[])) AND ($3::bigint[] IS NULL OR "roomId" = ANY($3::bigint[])) ORDER BY ts_rank_cd(to_tsvector($5::regconfig, "content"), plainto_tsquery($5::regconfig, $1::text)) DESC, s."id" ASC LIMIT $4::integer) r`,
|
|
159
|
+
values: [data.content, data.uid, data.roomId, parseInt(limit, 10), searchLanguage],
|
|
160
|
+
});
|
|
161
|
+
return res.rows[0].r;
|
|
162
|
+
} catch (err) {
|
|
163
|
+
await handleError(err);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
};
|
package/lib/redis.js
CHANGED
|
@@ -9,6 +9,7 @@ const db = require.main.require('./src/database');
|
|
|
9
9
|
exports.createIndices = async function () {
|
|
10
10
|
db.postSearch = redisSearch.createSearch('nodebbpostsearch', db.client);
|
|
11
11
|
db.topicSearch = redisSearch.createSearch('nodebbtopicsearch', db.client);
|
|
12
|
+
db.chatSearch = redisSearch.createSearch('nodebbchatsearch', db.client);
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
exports.changeIndexLanguage = async function () {
|
|
@@ -44,9 +45,39 @@ exports.searchRemove = async function (key, ids) {
|
|
|
44
45
|
if (!key || !ids.length) {
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
|
-
const method = key === 'post' ? db.postSearch : db.topicSearch;
|
|
48
|
-
|
|
49
48
|
await async.eachLimit(ids, 500, async (ids) => {
|
|
50
|
-
await
|
|
49
|
+
await db[`${key}Search`].remove(ids);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
exports.chat = {};
|
|
54
|
+
exports.chat.index = async (data, ids) => {
|
|
55
|
+
if (!ids.length) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const indexData = ids.map((id, index) => ({
|
|
59
|
+
id: id,
|
|
60
|
+
data: {
|
|
61
|
+
content: String(data[index].content),
|
|
62
|
+
roomId: String(data[index].roomId),
|
|
63
|
+
uid: String(data[index].uid),
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
await async.eachLimit(indexData, 500, async (indexData) => {
|
|
68
|
+
await db.chatSearch.index(indexData.data, indexData.id);
|
|
51
69
|
});
|
|
52
70
|
};
|
|
71
|
+
|
|
72
|
+
exports.chat.search = async (data, limit) => {
|
|
73
|
+
const query = {
|
|
74
|
+
matchWords: data.matchWords,
|
|
75
|
+
query: {
|
|
76
|
+
roomId: data.roomId,
|
|
77
|
+
uid: data.uid,
|
|
78
|
+
content: data.content,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return await db.chatSearch.query(query, 0, limit - 1);
|
|
83
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-dbsearch",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "A Plugin that lets users search posts and topics",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "BSD-2-Clause",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"lodash": "4.17.21",
|
|
22
|
-
"redisearch": "^1.0.
|
|
22
|
+
"redisearch": "^1.0.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"eslint": "7.32.0",
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"eslint-plugin-import": "2.25.2"
|
|
28
28
|
},
|
|
29
29
|
"nbbpm": {
|
|
30
|
-
"compatibility": "^3.
|
|
30
|
+
"compatibility": "^3.3.0"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/plugin.json
CHANGED
|
@@ -27,8 +27,14 @@
|
|
|
27
27
|
{ "hook": "action:topic.move", "method": "actionTopicMove" },
|
|
28
28
|
{ "hook": "action:topic.changeOwner", "method": "actionTopicChangeOwner" },
|
|
29
29
|
|
|
30
|
+
{ "hook": "action:messaging.save", "method": "actionMessagingSave" },
|
|
31
|
+
{ "hook": "action:messaging.delete", "method": "actionMessagingDelete" },
|
|
32
|
+
{ "hook": "action:messaging.restore", "method": "actionMessagingRestore" },
|
|
33
|
+
{ "hook": "action:messaging.edit", "method": "actionMessagingEdit" },
|
|
34
|
+
|
|
30
35
|
{ "hook": "filter:search.query", "method": "filterSearchQuery" },
|
|
31
|
-
{ "hook": "filter:topic.search", "method": "filterSearchTopic" }
|
|
36
|
+
{ "hook": "filter:topic.search", "method": "filterSearchTopic" },
|
|
37
|
+
{ "hook": "filter:messaging.searchMessages", "method": "filterMessagingSearchMessages" }
|
|
32
38
|
],
|
|
33
39
|
"modules": {
|
|
34
40
|
"../admin/plugins/dbsearch.js": "public/admin.js"
|
package/public/admin.js
CHANGED
|
@@ -113,8 +113,10 @@ define('admin/plugins/dbsearch', [
|
|
|
113
113
|
|
|
114
114
|
$('#topics-indexed').text(progress.topicsIndexed);
|
|
115
115
|
$('#posts-indexed').text(progress.postsIndexed);
|
|
116
|
+
$('#messages-indexed').text(progress.messagesIndexed);
|
|
116
117
|
$('.topic-progress').css('width', progress.topicsPercent + '%').text(progress.topicsPercent + '%');
|
|
117
118
|
$('.post-progress').css('width', progress.postsPercent + '%').text(progress.postsPercent + '%');
|
|
119
|
+
$('.message-progress').css('width', progress.messagesPercent + '%').text(progress.messagesPercent + '%');
|
|
118
120
|
});
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
+
<div class="mb-3">
|
|
29
|
+
<div class="alert alert-info">
|
|
30
|
+
Messages Indexed: <strong id="messages-indexed">{messagesIndexed}</strong> / <strong>{messageCount}</strong>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="progress" style="height:24px;">
|
|
33
|
+
<div class="message-progress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:{progressData.messagesPercent}%;min-width: 2em;">{progressData.messagesPercent}%</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
28
37
|
<button class="btn btn-warning" id="reindex" <!-- IF working -->disabled<!-- ENDIF working -->>Re Index</button>
|
|
29
38
|
<button class="btn btn-danger" id="clear-index">Clear Index</button>
|
|
30
39
|
<span id="work-in-progress" class="<!-- IF !working -->hidden<!-- ENDIF !working -->">
|