mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	feat: fullname search (#8641)
* feat: fullname search * fix: take last element * fix: attempt to fix psql like query * feat: upgrade sript, another fix attempt * fix: psql test * fix: psql scan * feat: add debug for test * feat: test collate * feat: cleanup * fix: upgrade script
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							9389749b79
						
					
				
				
					commit
					4be693f2e7
				
			| @@ -42,7 +42,7 @@ usersController.search = async function (req, res) { | |||||||
| 				match: query, | 				match: query, | ||||||
| 				limit: hardCap, | 				limit: hardCap, | ||||||
| 			}); | 			}); | ||||||
| 			return data.map(data => data.split(':')[1]); | 			return data.map(data => data.split(':').pop()); | ||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -582,15 +582,15 @@ DELETE FROM "legacy_zset" z | |||||||
| 			if (min.match(/^\(/)) { | 			if (min.match(/^\(/)) { | ||||||
| 				q.values.push(min.substr(1)); | 				q.values.push(min.substr(1)); | ||||||
| 				q.suffix += 'GT'; | 				q.suffix += 'GT'; | ||||||
| 				q.where += ` AND z."value" > $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" > $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} else if (min.match(/^\[/)) { | 			} else if (min.match(/^\[/)) { | ||||||
| 				q.values.push(min.substr(1)); | 				q.values.push(min.substr(1)); | ||||||
| 				q.suffix += 'GE'; | 				q.suffix += 'GE'; | ||||||
| 				q.where += ` AND z."value" >= $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" >= $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} else { | 			} else { | ||||||
| 				q.values.push(min); | 				q.values.push(min); | ||||||
| 				q.suffix += 'GE'; | 				q.suffix += 'GE'; | ||||||
| 				q.where += ` AND z."value" >= $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" >= $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -598,15 +598,15 @@ DELETE FROM "legacy_zset" z | |||||||
| 			if (max.match(/^\(/)) { | 			if (max.match(/^\(/)) { | ||||||
| 				q.values.push(max.substr(1)); | 				q.values.push(max.substr(1)); | ||||||
| 				q.suffix += 'LT'; | 				q.suffix += 'LT'; | ||||||
| 				q.where += ` AND z."value" < $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" < $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} else if (max.match(/^\[/)) { | 			} else if (max.match(/^\[/)) { | ||||||
| 				q.values.push(max.substr(1)); | 				q.values.push(max.substr(1)); | ||||||
| 				q.suffix += 'LE'; | 				q.suffix += 'LE'; | ||||||
| 				q.where += ` AND z."value" <= $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" <= $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} else { | 			} else { | ||||||
| 				q.values.push(max); | 				q.values.push(max); | ||||||
| 				q.suffix += 'LE'; | 				q.suffix += 'LE'; | ||||||
| 				q.where += ` AND z."value" <= $` + q.values.length + `::TEXT`; | 				q.where += ` AND z."value" <= $` + q.values.length + `::TEXT COLLATE "C"`; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								src/upgrades/1.15.0/fullname_search_set.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/upgrades/1.15.0/fullname_search_set.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const db = require('../../database'); | ||||||
|  |  | ||||||
|  | const batch = require('../../batch'); | ||||||
|  | const user = require('../../user'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  | 	name: 'Create fullname search set', | ||||||
|  | 	timestamp: Date.UTC(2020, 8, 11), | ||||||
|  | 	method: async function () { | ||||||
|  | 		const progress = this.progress; | ||||||
|  |  | ||||||
|  | 		await batch.processSortedSet('users:joindate', async function (uids) { | ||||||
|  | 			progress.incr(uids.length); | ||||||
|  | 			const userData = await user.getUsersFields(uids, ['uid', 'fullname']); | ||||||
|  | 			const bulkAdd = userData | ||||||
|  | 				.filter(u => u.uid && u.fullname) | ||||||
|  | 				.map(u => ['fullname:sorted', 0, u.fullname.toLowerCase() + ':' + u.uid]); | ||||||
|  | 			await db.sortedSetAddBulk(bulkAdd); | ||||||
|  | 		}, { | ||||||
|  | 			batch: 500, | ||||||
|  | 			progress: this.progress, | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
| @@ -94,6 +94,10 @@ module.exports = function (User) { | |||||||
| 			bulkAdd.push(['user:' + userData.uid + ':emails', timestamp, userData.email + ':' + timestamp]); | 			bulkAdd.push(['user:' + userData.uid + ':emails', timestamp, userData.email + ':' + timestamp]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (userData.fullname) { | ||||||
|  | 			bulkAdd.push(['fullname:sorted', 0, userData.fullname.toLowerCase() + ':' + userData.uid]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		await Promise.all([ | 		await Promise.all([ | ||||||
| 			db.incrObjectField('global', 'userCount'), | 			db.incrObjectField('global', 'userCount'), | ||||||
| 			db.sortedSetAddBulk(bulkAdd), | 			db.sortedSetAddBulk(bulkAdd), | ||||||
|   | |||||||
| @@ -136,6 +136,10 @@ module.exports = function (User) { | |||||||
| 			bulkRemove.push(['email:sorted', userData.email.toLowerCase() + ':' + uid]); | 			bulkRemove.push(['email:sorted', userData.email.toLowerCase() + ':' + uid]); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (userData.fullname) { | ||||||
|  | 			bulkRemove.push(['fullname:sorted', userData.fullname.toLowerCase() + ':' + uid]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		await Promise.all([ | 		await Promise.all([ | ||||||
| 			db.sortedSetRemoveBulk(bulkRemove), | 			db.sortedSetRemoveBulk(bulkRemove), | ||||||
| 			db.decrObjectField('global', 'userCount'), | 			db.decrObjectField('global', 'userCount'), | ||||||
|   | |||||||
| @@ -263,6 +263,14 @@ module.exports = function (User) { | |||||||
| 	async function updateFullname(uid, newFullname) { | 	async function updateFullname(uid, newFullname) { | ||||||
| 		const fullname = await User.getUserField(uid, 'fullname'); | 		const fullname = await User.getUserField(uid, 'fullname'); | ||||||
| 		await updateUidMapping('fullname', uid, newFullname, fullname); | 		await updateUidMapping('fullname', uid, newFullname, fullname); | ||||||
|  | 		if (newFullname !== fullname) { | ||||||
|  | 			if (fullname) { | ||||||
|  | 				await db.sortedSetRemove('fullname:sorted', fullname.toLowerCase() + ':' + uid); | ||||||
|  | 			} | ||||||
|  | 			if (newFullname) { | ||||||
|  | 				await db.sortedSetAdd('fullname:sorted', 0, newFullname.toLowerCase() + ':' + uid); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	User.changePassword = async function (uid, data) { | 	User.changePassword = async function (uid, data) { | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ module.exports = function (User) { | |||||||
| 		hardCap = hardCap || resultsPerPage * 10; | 		hardCap = hardCap || resultsPerPage * 10; | ||||||
|  |  | ||||||
| 		const data = await db.getSortedSetRangeByLex(searchBy + ':sorted', min, max, 0, hardCap); | 		const data = await db.getSortedSetRangeByLex(searchBy + ':sorted', min, max, 0, hardCap); | ||||||
| 		const uids = data.map(data => data.split(':')[1]); | 		const uids = data.map(data => data.split(':').pop()); | ||||||
| 		return uids; | 		return uids; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								test/user.js
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								test/user.js
									
									
									
									
									
								
							| @@ -399,6 +399,22 @@ describe('User', function () { | |||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		it('should search users by fullname', async function () { | ||||||
|  | 			const uid = await User.create({ username: 'fullnamesearch1', fullname: 'Mr. Fullname' }); | ||||||
|  | 			const data = await socketUser.search({ uid: adminUid }, { query: 'mr', searchBy: 'fullname' }); | ||||||
|  | 			assert(Array.isArray(data.users)); | ||||||
|  | 			assert.equal(data.users.length, 1); | ||||||
|  | 			assert.equal(uid, data.users[0].uid); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		it('should search users by fullname', async function () { | ||||||
|  | 			const uid = await User.create({ username: 'fullnamesearch2', fullname: 'Baris:Usakli' }); | ||||||
|  | 			const data = await socketUser.search({ uid: adminUid }, { query: 'baris:', searchBy: 'fullname' }); | ||||||
|  | 			assert(Array.isArray(data.users)); | ||||||
|  | 			assert.equal(data.users.length, 1); | ||||||
|  | 			assert.equal(uid, data.users[0].uid); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		it('should return empty array if query is empty', function (done) { | 		it('should return empty array if query is empty', function (done) { | ||||||
| 			socketUser.search({ uid: testUid }, { query: '' }, function (err, data) { | 			socketUser.search({ uid: testUid }, { query: '' }, function (err, data) { | ||||||
| 				assert.ifError(err); | 				assert.ifError(err); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user