nodebb-plugin-dbsearch 5.0.4 → 5.1.2
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 +1 -137
- package/lib/dbsearch.js +530 -516
- package/lib/mongo.js +114 -115
- package/lib/postgres.js +111 -111
- package/lib/redis.js +52 -52
- package/package.json +5 -5
- package/plugin.json +4 -7
- package/public/.eslintrc +3 -0
- package/{lib → public}/admin.js +120 -124
- /package/{public/templates → templates}/admin/plugins/dbsearch.tpl +0 -0
package/lib/dbsearch.js
CHANGED
|
@@ -1,516 +1,530 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
|
|
5
|
-
const winston = require.main.require('winston');
|
|
6
|
-
const nconf = require.main.require('nconf');
|
|
7
|
-
|
|
8
|
-
const db = require.main.require('./src/database');
|
|
9
|
-
const topics = require.main.require('./src/topics');
|
|
10
|
-
const posts = require.main.require('./src/posts');
|
|
11
|
-
const utils = require.main.require('./src/utils');
|
|
12
|
-
const socketAdmin = require.main.require('./src/socket.io/admin');
|
|
13
|
-
const batch = require.main.require('./src/batch');
|
|
14
|
-
const plugins = require.main.require('./src/plugins');
|
|
15
|
-
const categories = require.main.require('./src/categories');
|
|
16
|
-
const pubsub = require.main.require('./src/pubsub');
|
|
17
|
-
|
|
18
|
-
const searchModule = require(
|
|
19
|
-
|
|
20
|
-
db.searchIndex = searchModule.searchIndex;
|
|
21
|
-
db.search = searchModule.search;
|
|
22
|
-
db.searchRemove = searchModule.searchRemove;
|
|
23
|
-
|
|
24
|
-
const languageLookup = {
|
|
25
|
-
da: 'danish',
|
|
26
|
-
nl: 'dutch',
|
|
27
|
-
en: 'english',
|
|
28
|
-
fi: 'finnish',
|
|
29
|
-
fr: 'french',
|
|
30
|
-
de: 'german',
|
|
31
|
-
hu: 'hungarian',
|
|
32
|
-
it: 'italian',
|
|
33
|
-
nb: 'norwegian',
|
|
34
|
-
pt: 'portuguese',
|
|
35
|
-
ro: 'romanian',
|
|
36
|
-
ru: 'russian',
|
|
37
|
-
es: 'spanish',
|
|
38
|
-
sv: 'swedish',
|
|
39
|
-
tr: 'turkish',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const defaultPostLimit = 500;
|
|
43
|
-
const defaultTopicLimit = 500;
|
|
44
|
-
|
|
45
|
-
let pluginConfig = {
|
|
46
|
-
postLimit: defaultPostLimit,
|
|
47
|
-
topicLimit: defaultTopicLimit,
|
|
48
|
-
excludeCategories: [],
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const search = module.exports;
|
|
54
|
-
|
|
55
|
-
function convertLanguageName(name) {
|
|
56
|
-
if (nconf.get('database') === 'postgres') {
|
|
57
|
-
return languageLookup[name] || languageLookup.en;
|
|
58
|
-
}
|
|
59
|
-
return name;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
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);
|
|
65
|
-
|
|
66
|
-
params.router.post('/api/admin/plugins/dbsearch/save', params.middleware.applyCSRF, save);
|
|
67
|
-
|
|
68
|
-
pluginConfig = await getPluginData();
|
|
69
|
-
await searchModule.createIndices(convertLanguageName(pluginConfig ? pluginConfig.indexLanguage || 'en' : 'en'));
|
|
70
|
-
|
|
71
|
-
pubsub.on('nodebb-plugin-dbsearch:settings:save',
|
|
72
|
-
Object.assign(pluginConfig, data);
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
search.actionPostSave = async function (data) {
|
|
77
|
-
const isDeleted = await topics.getTopicField(data.post.tid, 'deleted');
|
|
78
|
-
if (!isDeleted) {
|
|
79
|
-
await postsSave([data.post]);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
search.actionPostRestore = function (data) {
|
|
84
|
-
search.actionPostSave(data);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
search.actionPostEdit = function (data) {
|
|
88
|
-
search.actionPostSave(data);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
search.actionPostDelete = function (data) {
|
|
92
|
-
searchRemove('post', [data.post.pid]);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
search.actionPostPurge = function (data) {
|
|
96
|
-
searchRemove('post', [data.post.pid]);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
search.actionPostMove = async function (data) {
|
|
100
|
-
const topicData = await topics.getTopicFields(data.post.tid, ['cid', 'deleted']);
|
|
101
|
-
reIndexPids([data.post.pid], topicData);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
search.actionPostChangeOwner = async function (hookData) {
|
|
105
|
-
const tids = _.uniq(hookData.posts.map(p => p.tid));
|
|
106
|
-
const topicData = await topics.getTopicsFields(tids, ['deleted']);
|
|
107
|
-
const tidToTopic = _.zipObject(tids, topicData);
|
|
108
|
-
const posts = hookData.posts.filter(p => tidToTopic[p.tid] && !tidToTopic[p.tid].deleted);
|
|
109
|
-
posts.forEach((p) => {
|
|
110
|
-
p.uid = hookData.toUid;
|
|
111
|
-
});
|
|
112
|
-
await postsSave(posts);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
search.actionTopicSave = function (data) {
|
|
116
|
-
topicsSave([data.topic]);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
search.actionTopicRestore = function (data) {
|
|
120
|
-
reIndexTids([data.topic.tid]);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
search.actionTopicEdit = function (data) {
|
|
124
|
-
search.actionTopicSave(data);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
search.actionTopicDelete = async function (data) {
|
|
128
|
-
if (!data || !data.topic) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const tid = data.topic
|
|
132
|
-
await Promise.all([
|
|
133
|
-
searchRemove('topic', [tid]),
|
|
134
|
-
searchRemove('post', [data.topic.mainPid]),
|
|
135
|
-
batch.processSortedSet(
|
|
136
|
-
await searchRemove('post', pids);
|
|
137
|
-
}, {
|
|
138
|
-
batch: batchSize,
|
|
139
|
-
}),
|
|
140
|
-
]);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
search.actionTopicPurge = function (data) {
|
|
144
|
-
search.actionTopicDelete(data);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
search.actionTopicMove = function (data) {
|
|
148
|
-
reIndexTids([data.tid]);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
search.actionTopicChangeOwner = function (hookData) {
|
|
152
|
-
hookData.topics.forEach((t) => {
|
|
153
|
-
t.uid = hookData.toUid;
|
|
154
|
-
});
|
|
155
|
-
topicsSave(hookData.topics);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
search.filterSearchQuery = async function (data) {
|
|
159
|
-
if (!data || !data.index) {
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
const limit = data.index === 'post' ? pluginConfig.postLimit : pluginConfig.topicLimit;
|
|
163
|
-
const query = {};
|
|
164
|
-
if (data.hasOwnProperty('cid')) {
|
|
165
|
-
query.cid = data.cid;
|
|
166
|
-
}
|
|
167
|
-
if (data.hasOwnProperty('uid')) {
|
|
168
|
-
query.uid = data.uid;
|
|
169
|
-
}
|
|
170
|
-
if (data.hasOwnProperty('content')) {
|
|
171
|
-
query.content = data.content;
|
|
172
|
-
}
|
|
173
|
-
if (!Object.keys(query).length) {
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
if (data.hasOwnProperty('matchWords')) {
|
|
177
|
-
query.matchWords = data.matchWords;
|
|
178
|
-
}
|
|
179
|
-
query.searchData = data.searchData || {};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
postData.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
postData
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
async function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
data
|
|
398
|
-
data.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
plugin.
|
|
428
|
-
plugin.
|
|
429
|
-
|
|
430
|
-
plugin.
|
|
431
|
-
plugin.
|
|
432
|
-
plugin.
|
|
433
|
-
plugin.
|
|
434
|
-
plugin.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
plugin.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const winston = require.main.require('winston');
|
|
6
|
+
const nconf = require.main.require('nconf');
|
|
7
|
+
|
|
8
|
+
const db = require.main.require('./src/database');
|
|
9
|
+
const topics = require.main.require('./src/topics');
|
|
10
|
+
const posts = require.main.require('./src/posts');
|
|
11
|
+
const utils = require.main.require('./src/utils');
|
|
12
|
+
const socketAdmin = require.main.require('./src/socket.io/admin');
|
|
13
|
+
const batch = require.main.require('./src/batch');
|
|
14
|
+
const plugins = require.main.require('./src/plugins');
|
|
15
|
+
const categories = require.main.require('./src/categories');
|
|
16
|
+
const pubsub = require.main.require('./src/pubsub');
|
|
17
|
+
|
|
18
|
+
const searchModule = require(`./${nconf.get('database')}`);
|
|
19
|
+
|
|
20
|
+
db.searchIndex = searchModule.searchIndex;
|
|
21
|
+
db.search = searchModule.search;
|
|
22
|
+
db.searchRemove = searchModule.searchRemove;
|
|
23
|
+
|
|
24
|
+
const languageLookup = {
|
|
25
|
+
da: 'danish',
|
|
26
|
+
nl: 'dutch',
|
|
27
|
+
en: 'english',
|
|
28
|
+
fi: 'finnish',
|
|
29
|
+
fr: 'french',
|
|
30
|
+
de: 'german',
|
|
31
|
+
hu: 'hungarian',
|
|
32
|
+
it: 'italian',
|
|
33
|
+
nb: 'norwegian',
|
|
34
|
+
pt: 'portuguese',
|
|
35
|
+
ro: 'romanian',
|
|
36
|
+
ru: 'russian',
|
|
37
|
+
es: 'spanish',
|
|
38
|
+
sv: 'swedish',
|
|
39
|
+
tr: 'turkish',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const defaultPostLimit = 500;
|
|
43
|
+
const defaultTopicLimit = 500;
|
|
44
|
+
|
|
45
|
+
let pluginConfig = {
|
|
46
|
+
postLimit: defaultPostLimit,
|
|
47
|
+
topicLimit: defaultTopicLimit,
|
|
48
|
+
excludeCategories: [],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const batchSize = 500;
|
|
52
|
+
|
|
53
|
+
const search = module.exports;
|
|
54
|
+
|
|
55
|
+
function convertLanguageName(name) {
|
|
56
|
+
if (nconf.get('database') === 'postgres') {
|
|
57
|
+
return languageLookup[name] || languageLookup.en;
|
|
58
|
+
}
|
|
59
|
+
return name;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
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);
|
|
65
|
+
|
|
66
|
+
params.router.post('/api/admin/plugins/dbsearch/save', params.middleware.applyCSRF, save);
|
|
67
|
+
|
|
68
|
+
pluginConfig = await getPluginData();
|
|
69
|
+
await searchModule.createIndices(convertLanguageName(pluginConfig ? pluginConfig.indexLanguage || 'en' : 'en'));
|
|
70
|
+
|
|
71
|
+
pubsub.on('nodebb-plugin-dbsearch:settings:save', (data) => {
|
|
72
|
+
Object.assign(pluginConfig, data);
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
search.actionPostSave = async function (data) {
|
|
77
|
+
const isDeleted = await topics.getTopicField(data.post.tid, 'deleted');
|
|
78
|
+
if (!isDeleted) {
|
|
79
|
+
await postsSave([data.post]);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
search.actionPostRestore = function (data) {
|
|
84
|
+
search.actionPostSave(data);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
search.actionPostEdit = function (data) {
|
|
88
|
+
search.actionPostSave(data);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
search.actionPostDelete = function (data) {
|
|
92
|
+
searchRemove('post', [data.post.pid]);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
search.actionPostPurge = function (data) {
|
|
96
|
+
searchRemove('post', [data.post.pid]);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
search.actionPostMove = async function (data) {
|
|
100
|
+
const topicData = await topics.getTopicFields(data.post.tid, ['cid', 'deleted']);
|
|
101
|
+
reIndexPids([data.post.pid], topicData);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
search.actionPostChangeOwner = async function (hookData) {
|
|
105
|
+
const tids = _.uniq(hookData.posts.map(p => p.tid));
|
|
106
|
+
const topicData = await topics.getTopicsFields(tids, ['deleted']);
|
|
107
|
+
const tidToTopic = _.zipObject(tids, topicData);
|
|
108
|
+
const posts = hookData.posts.filter(p => tidToTopic[p.tid] && !tidToTopic[p.tid].deleted);
|
|
109
|
+
posts.forEach((p) => {
|
|
110
|
+
p.uid = hookData.toUid;
|
|
111
|
+
});
|
|
112
|
+
await postsSave(posts);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
search.actionTopicSave = function (data) {
|
|
116
|
+
topicsSave([data.topic]);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
search.actionTopicRestore = function (data) {
|
|
120
|
+
reIndexTids([data.topic.tid]);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
search.actionTopicEdit = function (data) {
|
|
124
|
+
search.actionTopicSave(data);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
search.actionTopicDelete = async function (data) {
|
|
128
|
+
if (!data || !data.topic) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const { tid } = data.topic;
|
|
132
|
+
await Promise.all([
|
|
133
|
+
searchRemove('topic', [tid]),
|
|
134
|
+
searchRemove('post', [data.topic.mainPid]),
|
|
135
|
+
batch.processSortedSet(`tid:${tid}:posts`, async (pids) => {
|
|
136
|
+
await searchRemove('post', pids);
|
|
137
|
+
}, {
|
|
138
|
+
batch: batchSize,
|
|
139
|
+
}),
|
|
140
|
+
]);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
search.actionTopicPurge = function (data) {
|
|
144
|
+
search.actionTopicDelete(data);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
search.actionTopicMove = function (data) {
|
|
148
|
+
reIndexTids([data.tid]);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
search.actionTopicChangeOwner = function (hookData) {
|
|
152
|
+
hookData.topics.forEach((t) => {
|
|
153
|
+
t.uid = hookData.toUid;
|
|
154
|
+
});
|
|
155
|
+
topicsSave(hookData.topics);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
search.filterSearchQuery = async function (data) {
|
|
159
|
+
if (!data || !data.index) {
|
|
160
|
+
return data;
|
|
161
|
+
}
|
|
162
|
+
const limit = data.index === 'post' ? pluginConfig.postLimit : pluginConfig.topicLimit;
|
|
163
|
+
const query = {};
|
|
164
|
+
if (data.hasOwnProperty('cid')) {
|
|
165
|
+
query.cid = data.cid;
|
|
166
|
+
}
|
|
167
|
+
if (data.hasOwnProperty('uid')) {
|
|
168
|
+
query.uid = data.uid;
|
|
169
|
+
}
|
|
170
|
+
if (data.hasOwnProperty('content')) {
|
|
171
|
+
query.content = data.content;
|
|
172
|
+
}
|
|
173
|
+
if (!Object.keys(query).length) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
if (data.hasOwnProperty('matchWords')) {
|
|
177
|
+
query.matchWords = data.matchWords;
|
|
178
|
+
}
|
|
179
|
+
query.searchData = data.searchData || {};
|
|
180
|
+
data.ids = data.ids.concat(await db.search(data.index, query, limit));
|
|
181
|
+
return data;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
search.filterSearchTopic = async function (hookData) {
|
|
185
|
+
if (!hookData.term || !hookData.tid) {
|
|
186
|
+
return hookData;
|
|
187
|
+
}
|
|
188
|
+
const cid = await topics.getTopicField(hookData.tid, 'cid');
|
|
189
|
+
const result = await search.filterSearchQuery({
|
|
190
|
+
index: 'post',
|
|
191
|
+
cid: [cid],
|
|
192
|
+
content: hookData.term,
|
|
193
|
+
ids: [],
|
|
194
|
+
});
|
|
195
|
+
const postData = await posts.getPostsFields(result.ids, ['pid', 'tid']);
|
|
196
|
+
hookData.ids = hookData.ids.concat(postData.filter(p => p && p.tid === parseInt(hookData.tid, 10))
|
|
197
|
+
.map(p => p.pid));
|
|
198
|
+
return hookData;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
search.reindex = async function () {
|
|
202
|
+
await db.setObject('nodebb-plugin-dbsearch', {
|
|
203
|
+
topicsIndexed: 0,
|
|
204
|
+
postsIndexed: 0,
|
|
205
|
+
working: 1,
|
|
206
|
+
});
|
|
207
|
+
await Promise.all([
|
|
208
|
+
reIndexTopics(),
|
|
209
|
+
reIndexPosts(),
|
|
210
|
+
]);
|
|
211
|
+
await db.setObject('nodebb-plugin-dbsearch', {
|
|
212
|
+
working: 0,
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
async function reIndexTopics() {
|
|
217
|
+
await batch.processSortedSet('topics:tid', async (tids) => {
|
|
218
|
+
const topicData = await topics.getTopicsFields(tids, ['tid', 'title', 'uid', 'cid', 'deleted']);
|
|
219
|
+
await topicsSave(topicData);
|
|
220
|
+
}, {
|
|
221
|
+
batch: batchSize,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function topicsSave(topics) {
|
|
226
|
+
topics = topics.filter(
|
|
227
|
+
t => t && t.tid && parseInt(t.deleted, 10) !== 1 && !pluginConfig.excludeCategories.includes(String(t.cid))
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
let data = topics.map((topicData) => {
|
|
231
|
+
const indexData = {};
|
|
232
|
+
if (topicData.title) {
|
|
233
|
+
indexData.content = topicData.title;
|
|
234
|
+
}
|
|
235
|
+
if (topicData.cid) {
|
|
236
|
+
indexData.cid = topicData.cid;
|
|
237
|
+
}
|
|
238
|
+
if (topicData.uid) {
|
|
239
|
+
indexData.uid = topicData.uid;
|
|
240
|
+
}
|
|
241
|
+
if (!Object.keys(indexData).length) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return indexData;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const tids = topics.filter((t, index) => !!data[index]).map(t => t.tid);
|
|
248
|
+
data = data.filter(Boolean);
|
|
249
|
+
if (!data.length) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const result = await plugins.hooks.fire('filter:search.indexTopics', { data: data, tids: tids, topics: topics });
|
|
254
|
+
await db.searchIndex('topic', result.data, result.tids);
|
|
255
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'topicsIndexed', result.tids.length);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function reIndexPosts() {
|
|
259
|
+
await batch.processSortedSet('posts:pid', async (pids) => {
|
|
260
|
+
let postData = await posts.getPostsFields(pids, ['pid', 'content', 'uid', 'tid', 'deleted']);
|
|
261
|
+
postData = postData.filter(p => p && p.deleted !== 1);
|
|
262
|
+
const tids = _.uniq(postData.map(p => p.tid));
|
|
263
|
+
const topicData = await topics.getTopicsFields(tids, ['deleted', 'cid']);
|
|
264
|
+
const tidToTopic = _.zipObject(tids, topicData);
|
|
265
|
+
postData.forEach((post) => {
|
|
266
|
+
if (post && tidToTopic[post.tid]) {
|
|
267
|
+
post.cid = tidToTopic[post.tid].cid;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
postData = postData.filter(post => tidToTopic[post.tid].deleted !== 1);
|
|
271
|
+
await postsSave(postData);
|
|
272
|
+
}, {
|
|
273
|
+
batch: batchSize,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function postsSave(posts) {
|
|
278
|
+
posts = posts.filter(
|
|
279
|
+
p => p && p.pid && parseInt(p.deleted, 10) !== 1 && !pluginConfig.excludeCategories.includes(String(p.cid))
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
let data = posts.map((postData) => {
|
|
283
|
+
const indexData = {};
|
|
284
|
+
if (postData.content) {
|
|
285
|
+
indexData.content = postData.content;
|
|
286
|
+
}
|
|
287
|
+
if (postData.cid) {
|
|
288
|
+
indexData.cid = postData.cid;
|
|
289
|
+
}
|
|
290
|
+
if (postData.uid) {
|
|
291
|
+
indexData.uid = postData.uid;
|
|
292
|
+
}
|
|
293
|
+
if (!Object.keys(indexData).length) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
return indexData;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const pids = posts.filter((p, index) => !!data[index]).map(p => p.pid);
|
|
300
|
+
data = data.filter(Boolean);
|
|
301
|
+
if (!data.length) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const result = await plugins.hooks.fire('filter:search.indexPosts', { data: data, pids: pids, posts: posts });
|
|
306
|
+
await db.searchIndex('post', result.data, result.pids);
|
|
307
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'postsIndexed', result.pids.length);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function searchRemove(key, ids) {
|
|
311
|
+
await db.searchRemove(key, ids);
|
|
312
|
+
if (key === 'topic') {
|
|
313
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'topicsIndexed', -ids.length);
|
|
314
|
+
} else if (key === 'post') {
|
|
315
|
+
await db.incrObjectFieldBy('nodebb-plugin-dbsearch', 'postsIndexed', -ids.length);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function reIndexTids(tids) {
|
|
320
|
+
if (!Array.isArray(tids) || !tids.length) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let topicData = await topics.getTopicsFields(tids, ['tid', 'title', 'uid', 'cid', 'deleted', 'mainPid']);
|
|
325
|
+
topicData = topicData.filter(t => t.tid && t.deleted !== 1);
|
|
326
|
+
if (!topicData.length) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function reIndexTopicsPids(topicData) {
|
|
331
|
+
await Promise.all(topicData.map(t => reIndexTopicPids(t)));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function reIndexTopicPids(topic) {
|
|
335
|
+
await reIndexPids([topic.mainPid], topic);
|
|
336
|
+
await batch.processSortedSet(`tid:${topic.tid}:posts`, async (pids) => {
|
|
337
|
+
await reIndexPids(pids, topic);
|
|
338
|
+
}, {
|
|
339
|
+
batch: batchSize,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
await Promise.all([
|
|
344
|
+
topicsSave(topicData),
|
|
345
|
+
reIndexTopicsPids(topicData),
|
|
346
|
+
]);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function reIndexPids(pids, topic) {
|
|
350
|
+
if (!Array.isArray(pids) || !pids.length) {
|
|
351
|
+
winston.warn('[nodebb-plugin-dbsearch] invalid-pid, skipping');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (parseInt(topic.deleted, 10) === 1) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const postData = await posts.getPostsFields(pids, ['pid', 'content', 'uid', 'tid', 'deleted']);
|
|
358
|
+
postData.forEach((post) => {
|
|
359
|
+
if (post && topic) {
|
|
360
|
+
post.cid = topic.cid;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
await postsSave(postData);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function renderAdmin(req, res) {
|
|
367
|
+
const results = await getGlobalAndPluginData();
|
|
368
|
+
results.plugin.progressData = await getProgress();
|
|
369
|
+
results.plugin.csrf = req.csrfToken();
|
|
370
|
+
res.render('admin/plugins/dbsearch', results.plugin);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function save(req, res) {
|
|
374
|
+
if (utils.isNumber(req.body.postLimit) && utils.isNumber(req.body.topicLimit)) {
|
|
375
|
+
const data = {
|
|
376
|
+
postLimit: req.body.postLimit,
|
|
377
|
+
topicLimit: req.body.topicLimit,
|
|
378
|
+
excludeCategories: JSON.stringify(req.body.excludeCategories || []),
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
await db.setObject('nodebb-plugin-dbsearch', data);
|
|
382
|
+
|
|
383
|
+
pluginConfig.postLimit = data.postLimit;
|
|
384
|
+
pluginConfig.topicLimit = data.topicLimit;
|
|
385
|
+
pluginConfig.excludeCategories = req.body.excludeCategories || [];
|
|
386
|
+
pubsub.publish('nodebb-plugin-dbsearch:settings:save', pluginConfig);
|
|
387
|
+
res.json('Settings saved!');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
socketAdmin.plugins.dbsearch = {};
|
|
392
|
+
socketAdmin.plugins.dbsearch.checkProgress = async function () {
|
|
393
|
+
return await getProgress();
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
async function getPluginData() {
|
|
397
|
+
const data = await db.getObject('nodebb-plugin-dbsearch') || {};
|
|
398
|
+
data.topicsIndexed = parseInt(data.topicsIndexed, 10) || 0;
|
|
399
|
+
data.postsIndexed = parseInt(data.postsIndexed, 10) || 0;
|
|
400
|
+
data.excludeCategories = data.excludeCategories || '[]';
|
|
401
|
+
data.postLimit = data.postLimit || defaultPostLimit;
|
|
402
|
+
data.topicLimit = data.topicLimit || defaultTopicLimit;
|
|
403
|
+
data.indexLanguage = data.indexLanguage || 'en';
|
|
404
|
+
data.working = data.working || 0;
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
data.excludeCategories = JSON.parse(data.excludeCategories);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
winston.error(err);
|
|
410
|
+
data.excludeCategories = [];
|
|
411
|
+
}
|
|
412
|
+
return data;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function getGlobalAndPluginData() {
|
|
416
|
+
const [global, plugin, allCategories] = await Promise.all([
|
|
417
|
+
db.getObjectFields('global', ['topicCount', 'postCount']),
|
|
418
|
+
getPluginData(),
|
|
419
|
+
categories.buildForSelectAll(['value', 'text']),
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
const languageSupported = nconf.get('database') === 'mongo' || nconf.get('database') === 'postgres';
|
|
423
|
+
const languages = Object.keys(languageLookup).map(
|
|
424
|
+
code => ({ name: languageLookup[code], value: code, selected: false })
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
plugin.languageSupported = languageSupported;
|
|
428
|
+
plugin.languages = languages;
|
|
429
|
+
|
|
430
|
+
plugin.allCategories = allCategories;
|
|
431
|
+
plugin.topicCount = parseInt(global.topicCount, 10);
|
|
432
|
+
plugin.postCount = parseInt(global.postCount, 10);
|
|
433
|
+
plugin.topicLimit = plugin.topicLimit || defaultTopicLimit;
|
|
434
|
+
plugin.postLimit = plugin.postLimit || defaultPostLimit;
|
|
435
|
+
plugin.topicsIndexed = plugin.topicsIndexed > plugin.topicCount ? plugin.topicCount : plugin.topicsIndexed;
|
|
436
|
+
plugin.postsIndexed = plugin.postsIndexed > plugin.postCount ? plugin.postCount : plugin.postsIndexed;
|
|
437
|
+
plugin.languageSupported = languageSupported;
|
|
438
|
+
plugin.languages = languages;
|
|
439
|
+
plugin.indexLanguage = plugin.indexLanguage || 'en';
|
|
440
|
+
plugin.languages.forEach((language) => {
|
|
441
|
+
language.selected = language && language.value === plugin.indexLanguage;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
plugin.allCategories.forEach((category) => {
|
|
445
|
+
category.selected = category && plugin.excludeCategories.includes(String(category.value));
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return { global: global, plugin: plugin, allCategories: allCategories };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function getProgress() {
|
|
452
|
+
const [global, pluginData] = await Promise.all([
|
|
453
|
+
db.getObjectFields('global', ['topicCount', 'postCount']),
|
|
454
|
+
getPluginData(),
|
|
455
|
+
]);
|
|
456
|
+
const topicCount = parseInt(global.topicCount, 10);
|
|
457
|
+
const postCount = parseInt(global.postCount, 10);
|
|
458
|
+
const topicsPercent = topicCount ? (pluginData.topicsIndexed / topicCount) * 100 : 0;
|
|
459
|
+
const postsPercent = postCount ? (pluginData.postsIndexed / postCount) * 100 : 0;
|
|
460
|
+
return {
|
|
461
|
+
topicsPercent: Math.max(0, Math.min(100, topicsPercent.toFixed(2))),
|
|
462
|
+
postsPercent: Math.max(0, Math.min(100, postsPercent.toFixed(2))),
|
|
463
|
+
topicsIndexed: topicsPercent >= 100 ? topicCount : Math.max(0, pluginData.topicsIndexed),
|
|
464
|
+
postsIndexed: postsPercent >= 100 ? postCount : Math.max(0, pluginData.postsIndexed),
|
|
465
|
+
working: pluginData.working,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
socketAdmin.plugins.dbsearch.reindex = function (socket, data, callback) {
|
|
470
|
+
try {
|
|
471
|
+
search.reindex();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
winston.error(err);
|
|
474
|
+
}
|
|
475
|
+
callback();
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
socketAdmin.plugins.dbsearch.clearIndex = async function () {
|
|
479
|
+
setTimeout(async () => {
|
|
480
|
+
try {
|
|
481
|
+
console.log('clear called');
|
|
482
|
+
await clearIndex();
|
|
483
|
+
} catch (err) {
|
|
484
|
+
winston.error(err.stack);
|
|
485
|
+
}
|
|
486
|
+
}, 0);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
async function clearIndex() {
|
|
490
|
+
await db.setObject('nodebb-plugin-dbsearch', {
|
|
491
|
+
working: 1,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await Promise.all([
|
|
495
|
+
clearSet('topics:tid', 'topic'),
|
|
496
|
+
clearSet('posts:pid', 'post'),
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
await db.setObject('nodebb-plugin-dbsearch', {
|
|
500
|
+
postsIndexed: 0,
|
|
501
|
+
topicsIndexed: 0,
|
|
502
|
+
working: 0,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function clearSet(set, key) {
|
|
507
|
+
await batch.processSortedSet(set, async (ids) => {
|
|
508
|
+
await searchRemove(key, ids);
|
|
509
|
+
}, {
|
|
510
|
+
batch: batchSize,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
socketAdmin.plugins.dbsearch.changeLanguage = async function (socket, language) {
|
|
515
|
+
await searchModule.changeIndexLanguage(convertLanguageName(language));
|
|
516
|
+
await db.setObject('nodebb-plugin-dbsearch', { indexLanguage: language });
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const admin = {};
|
|
520
|
+
admin.menu = function (custom_header, callback) {
|
|
521
|
+
custom_header.plugins.push({
|
|
522
|
+
route: '/plugins/dbsearch',
|
|
523
|
+
icon: 'fa-search',
|
|
524
|
+
name: 'DB Search',
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
callback(null, custom_header);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
search.admin = admin;
|