nodebb-plugin-dbsearch 6.0.1 → 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 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');
@@ -60,10 +61,11 @@ function convertLanguageName(name) {
60
61
  }
61
62
 
62
63
  search.init = async function (params) {
63
- params.router.get('/admin/plugins/dbsearch', params.middleware.applyCSRF, params.middleware.admin.buildHeader, renderAdmin);
64
- params.router.get('/api/admin/plugins/dbsearch', params.middleware.applyCSRF, renderAdmin);
64
+ const { router } = params;
65
+ const routeHelpers = require.main.require('./src/routes/helpers');
66
+ routeHelpers.setupAdminPageRoute(router, '/admin/plugins/dbsearch', renderAdmin);
65
67
 
66
- params.router.post('/api/admin/plugins/dbsearch/save', params.middleware.applyCSRF, save);
68
+ router.post('/api/admin/plugins/dbsearch/save', params.middleware.applyCSRF, save);
67
69
 
68
70
  pluginConfig = await getPluginData();
69
71
  await searchModule.createIndices(convertLanguageName(pluginConfig ? pluginConfig.indexLanguage || 'en' : 'en'));
@@ -155,6 +157,22 @@ search.actionTopicChangeOwner = function (hookData) {
155
157
  topicsSave(hookData.topics);
156
158
  };
157
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
+
158
176
  search.filterSearchQuery = async function (data) {
159
177
  if (!data || !data.index) {
160
178
  return data;
@@ -198,15 +216,43 @@ search.filterSearchTopic = async function (hookData) {
198
216
  return hookData;
199
217
  };
200
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
+
201
245
  search.reindex = async function () {
202
246
  await db.setObject('nodebb-plugin-dbsearch', {
203
247
  topicsIndexed: 0,
204
248
  postsIndexed: 0,
249
+ messagesIndexed: 0,
205
250
  working: 1,
206
251
  });
207
252
  await Promise.all([
208
253
  reIndexTopics(),
209
254
  reIndexPosts(),
255
+ reIndexMessages(),
210
256
  ]);
211
257
  await db.setObject('nodebb-plugin-dbsearch', {
212
258
  working: 0,
@@ -307,12 +353,55 @@ async function postsSave(posts) {
307
353
  await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'postsIndexed', result.pids.length);
308
354
  }
309
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
+
310
397
  async function searchRemove(key, ids) {
311
398
  await db.searchRemove(key, ids);
312
399
  if (key === 'topic') {
313
400
  await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'topicsIndexed', -ids.length);
314
401
  } else if (key === 'post') {
315
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);
316
405
  }
317
406
  }
318
407
 
@@ -366,6 +455,7 @@ async function reIndexPids(pids, topic) {
366
455
  async function renderAdmin(req, res) {
367
456
  const results = await getGlobalAndPluginData();
368
457
  results.plugin.progressData = await getProgress();
458
+ results.plugin.title = 'DB Search';
369
459
  res.render('admin/plugins/dbsearch', results.plugin);
370
460
  }
371
461
 
@@ -396,6 +486,7 @@ async function getPluginData() {
396
486
  const data = await db.getObject('nodebb-plugin-dbsearch') || {};
397
487
  data.topicsIndexed = parseInt(data.topicsIndexed, 10) || 0;
398
488
  data.postsIndexed = parseInt(data.postsIndexed, 10) || 0;
489
+ data.messagesIndexed = parseInt(data.messagesIndexed, 10) || 0;
399
490
  data.excludeCategories = data.excludeCategories || '[]';
400
491
  data.postLimit = data.postLimit || defaultPostLimit;
401
492
  data.topicLimit = data.topicLimit || defaultTopicLimit;
@@ -413,7 +504,7 @@ async function getPluginData() {
413
504
 
414
505
  async function getGlobalAndPluginData() {
415
506
  const [global, plugin, allCategories] = await Promise.all([
416
- db.getObjectFields('global', ['topicCount', 'postCount']),
507
+ db.getObjectFields('global', ['topicCount', 'postCount', 'messageCount']),
417
508
  getPluginData(),
418
509
  categories.buildForSelectAll(['value', 'text']),
419
510
  ]);
@@ -429,10 +520,12 @@ async function getGlobalAndPluginData() {
429
520
  plugin.allCategories = allCategories;
430
521
  plugin.topicCount = parseInt(global.topicCount, 10);
431
522
  plugin.postCount = parseInt(global.postCount, 10);
523
+ plugin.messageCount = parseInt(global.messageCount, 10);
432
524
  plugin.topicLimit = plugin.topicLimit || defaultTopicLimit;
433
525
  plugin.postLimit = plugin.postLimit || defaultPostLimit;
434
526
  plugin.topicsIndexed = plugin.topicsIndexed > plugin.topicCount ? plugin.topicCount : plugin.topicsIndexed;
435
527
  plugin.postsIndexed = plugin.postsIndexed > plugin.postCount ? plugin.postCount : plugin.postsIndexed;
528
+ plugin.messagesIndexed = plugin.messagesIndexed > plugin.messageCount ? plugin.messageCount : plugin.messagesIndexed;
436
529
  plugin.languageSupported = languageSupported;
437
530
  plugin.languages = languages;
438
531
  plugin.indexLanguage = plugin.indexLanguage || 'en';
@@ -449,35 +542,40 @@ async function getGlobalAndPluginData() {
449
542
 
450
543
  async function getProgress() {
451
544
  const [global, pluginData] = await Promise.all([
452
- db.getObjectFields('global', ['topicCount', 'postCount']),
545
+ db.getObjectFields('global', ['topicCount', 'postCount', 'messageCount']),
453
546
  getPluginData(),
454
547
  ]);
455
548
  const topicCount = parseInt(global.topicCount, 10);
456
549
  const postCount = parseInt(global.postCount, 10);
550
+ const messageCount = parseInt(global.messageCount, 10);
457
551
  const topicsPercent = topicCount ? (pluginData.topicsIndexed / topicCount) * 100 : 0;
458
552
  const postsPercent = postCount ? (pluginData.postsIndexed / postCount) * 100 : 0;
553
+ const messagesPercent = messageCount ? (pluginData.messagesIndexed / messageCount) * 100 : 0;
459
554
  return {
460
555
  topicsPercent: Math.max(0, Math.min(100, topicsPercent.toFixed(2))),
461
556
  postsPercent: Math.max(0, Math.min(100, postsPercent.toFixed(2))),
557
+ messagesPercent: Math.max(0, Math.min(100, messagesPercent.toFixed(2))),
462
558
  topicsIndexed: topicsPercent >= 100 ? topicCount : Math.max(0, pluginData.topicsIndexed),
463
559
  postsIndexed: postsPercent >= 100 ? postCount : Math.max(0, pluginData.postsIndexed),
560
+ messagesIndexed: messagesPercent >= 100 ? messageCount : Math.max(0, pluginData.messagesIndexed),
464
561
  working: pluginData.working,
465
562
  };
466
563
  }
467
564
 
468
565
  socketAdmin.plugins.dbsearch.reindex = function (socket, data, callback) {
469
- try {
470
- search.reindex();
471
- } catch (err) {
472
- winston.error(err);
473
- }
566
+ setTimeout(async () => {
567
+ try {
568
+ await search.reindex();
569
+ } catch (err) {
570
+ winston.error(err);
571
+ }
572
+ }, 0);
474
573
  callback();
475
574
  };
476
575
 
477
576
  socketAdmin.plugins.dbsearch.clearIndex = async function () {
478
577
  setTimeout(async () => {
479
578
  try {
480
- console.log('clear called');
481
579
  await clearIndex();
482
580
  } catch (err) {
483
581
  winston.error(err.stack);
@@ -493,11 +591,13 @@ async function clearIndex() {
493
591
  await Promise.all([
494
592
  clearSet('topics:tid', 'topic'),
495
593
  clearSet('posts:pid', 'post'),
594
+ clearSet('messages:mid', 'chat'),
496
595
  ]);
497
596
 
498
597
  await db.setObject('nodebb-plugin-dbsearch', {
499
598
  postsIndexed: 0,
500
599
  topicsIndexed: 0,
600
+ messagesIndexed: 0,
501
601
  working: 0,
502
602
  });
503
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
- let words = data.content.split(' ');
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 method.remove(ids);
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.0.1",
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.1"
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.0.0"
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
 
@@ -1,71 +1,82 @@
1
- <div class="row">
2
- <div class="col-12">
3
- <div class="card">
4
- <div class="card-header">DB Search</div>
5
- <div class="card-body row">
6
- <div class="col-6">
7
- <div class="mb-3">
8
- <div class="alert alert-info">
9
- Topics Indexed: <strong id="topics-indexed">{topicsIndexed}</strong> / <strong>{topicCount}</strong>
10
- </div>
1
+ <div class="acp-page-container">
2
+ <!-- IMPORT admin/partials/settings/header.tpl -->
3
+
4
+ <div class="row m-0">
5
+ <div id="spy-container" class="col-12 px-0 mb-4" tabindex="0">
6
+ <div class="card">
7
+ <div class="card-header">DB Search</div>
8
+ <div class="card-body row">
9
+ <div class="col-6">
10
+ <div class="mb-3">
11
+ <div class="alert alert-info">
12
+ Topics Indexed: <strong id="topics-indexed">{topicsIndexed}</strong> / <strong>{topicCount}</strong>
13
+ </div>
11
14
 
12
- <div class="progress" style="height:24px;">
13
- <div class="topic-progress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:{progressData.topicsPercent}%;min-width: 2em;">{progressData.topicsPercent}%</div>
15
+ <div class="progress" style="height:24px;">
16
+ <div class="topic-progress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:{progressData.topicsPercent}%;min-width: 2em;">{progressData.topicsPercent}%</div>
17
+ </div>
14
18
  </div>
15
- </div>
16
- <div class="mb-3">
17
- <div class="alert alert-info">
18
- Posts Indexed: <strong id="posts-indexed">{postsIndexed}</strong> / <strong>{postCount}</strong>
19
+ <div class="mb-3">
20
+ <div class="alert alert-info">
21
+ Posts Indexed: <strong id="posts-indexed">{postsIndexed}</strong> / <strong>{postCount}</strong>
22
+ </div>
23
+ <div class="progress" style="height:24px;">
24
+ <div class="post-progress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:{progressData.postsPercent}%;min-width: 2em;">{progressData.postsPercent}%</div>
25
+ </div>
19
26
  </div>
20
- <div class="progress" style="height:24px;">
21
- <div class="post-progress progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:{progressData.postsPercent}%;min-width: 2em;">{progressData.postsPercent}%</div>
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>
22
35
  </div>
23
- </div>
24
36
 
25
- <button class="btn btn-warning" id="reindex" <!-- IF working -->disabled<!-- ENDIF working -->>Re Index</button>
26
- <button class="btn btn-danger" id="clear-index">Clear Index</button>
27
- <span id="work-in-progress" class="<!-- IF !working -->hidden<!-- ENDIF !working -->">
28
- <i class="fa fa-gear fa-spin"></i> Working...
29
- </span>
37
+ <button class="btn btn-warning" id="reindex" <!-- IF working -->disabled<!-- ENDIF working -->>Re Index</button>
38
+ <button class="btn btn-danger" id="clear-index">Clear Index</button>
39
+ <span id="work-in-progress" class="<!-- IF !working -->hidden<!-- ENDIF !working -->">
40
+ <i class="fa fa-gear fa-spin"></i> Working...
41
+ </span>
30
42
 
31
- <hr/>
43
+ <hr/>
32
44
 
33
- <!-- IF languageSupported -->
34
- <div class="mb-3">
35
- <label class="form-label">Index Language</label>
36
- <select class="form-select" id="indexLanguage">
37
- <!-- BEGIN languages -->
38
- <option value="{languages.value}" <!-- IF languages.selected -->selected<!-- ENDIF languages.selected -->>{languages.name}</option>
39
- <!-- END languages -->
40
- </select>
41
- </div>
42
- <button class="btn btn-primary" id="changeLanguage">Change Language</button>
43
- <hr/>
44
- <!-- ENDIF languageSupported -->
45
+ <!-- IF languageSupported -->
46
+ <div class="mb-3">
47
+ <label class="form-label">Index Language</label>
48
+ <select class="form-select" id="indexLanguage">
49
+ <!-- BEGIN languages -->
50
+ <option value="{languages.value}" <!-- IF languages.selected -->selected<!-- ENDIF languages.selected -->>{languages.name}</option>
51
+ <!-- END languages -->
52
+ </select>
53
+ </div>
54
+ <button class="btn btn-primary" id="changeLanguage">Change Language</button>
55
+ <hr/>
56
+ <!-- ENDIF languageSupported -->
45
57
 
46
- <div class="mb-3">
47
- <label class="form-label">Topic Limit</label>
48
- <input id="topicLimit" type="text" class="form-control" placeholder="Number of topics to return" value="{topicLimit}">
49
- </div>
50
- <div class="mb-3">
51
- <label class="form-label">Post Limit</label>
52
- <input id="postLimit" type="text" class="form-control" placeholder="Number of posts to return" value="{postLimit}">
58
+ <div class="mb-3">
59
+ <label class="form-label">Topic Limit</label>
60
+ <input id="topicLimit" type="text" class="form-control" placeholder="Number of topics to return" value="{topicLimit}">
61
+ </div>
62
+ <div class="mb-3">
63
+ <label class="form-label">Post Limit</label>
64
+ <input id="postLimit" type="text" class="form-control" placeholder="Number of posts to return" value="{postLimit}">
65
+ </div>
53
66
  </div>
54
- </div>
55
67
 
56
- <div class="col-6">
57
- <div class="post-search-item">
58
- <label class="form-label">Select categories to exclude from indexing</label>
59
- <select multiple class="form-select" id="exclude-categories" size="30">
60
- <!-- BEGIN allCategories -->
61
- <option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
62
- <!-- END allCategories -->
63
- </select>
68
+ <div class="col-6">
69
+ <div class="post-search-item">
70
+ <label class="form-label">Select categories to exclude from indexing</label>
71
+ <select multiple class="form-select" id="exclude-categories" size="30">
72
+ <!-- BEGIN allCategories -->
73
+ <option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
74
+ <!-- END allCategories -->
75
+ </select>
76
+ </div>
64
77
  </div>
65
78
  </div>
66
79
  </div>
67
80
  </div>
68
81
  </div>
69
82
  </div>
70
-
71
- <!-- IMPORT admin/partials/save_button.tpl -->