@@ -27,78 +27,48 @@ router.post('/', async function(req, res) {
2727 return res . status ( OK ) . send ( { items : [ ] } ) ;
2828 }
2929
30- const query = req . body . query . replace ( / [ * \s ] / g, '' ) ;
30+ // function for calculating how similar strings are to each other via levenshtein distance
31+ function levenshteinDistance ( a , b ) {
32+ const m = a . length ;
33+ const n = b . length ;
3134
32- // Create a fuzzy regex pattern to match characters in order, e.g., "pone" -> /p.*o.*n.*e/i
33- const fuzzyPattern = query . split ( '' ) . join ( '.*' ) ;
34- const pattern = new RegExp ( fuzzyPattern , 'i' ) ;
35+ if ( m === 0 ) return n ;
36+ if ( n === 0 ) return m ;
3537
36- const maybeOr = {
37- $or : [
38- {
39- $expr : {
40- $regexMatch : {
41- input : { $concat : [ '$firstName' , '$lastName' ] } ,
42- regex : pattern ,
43- }
44- }
45- } ,
46- { email : { $regex : new RegExp ( query , 'i' ) } }
47- ]
48- } ;
38+ const dp = Array . from ( { length : m + 1 } , ( ) => Array ( n + 1 ) ) ;
39+ for ( let i = 0 ; i <= m ; i ++ ) dp [ i ] [ 0 ] = i ;
40+ for ( let j = 0 ; j <= n ; j ++ ) dp [ 0 ] [ j ] = j ;
4941
50- /**
51- * Function to calculate scores based on token matches for sorting
52- * @param {string } str - The string to score against
53- * @param {Array } tokens - The tokens to match against the string
54- * @return {number } - The score based on matches
55- */
56- const tokenScores = ( str , tokens ) => {
57- return tokens . reduce ( ( score , token ) => {
58- if ( str . startsWith ( token ) ) return score ; // highest score for exact match
59- if ( str . includes ( token ) ) return score + 1 ; // lower score for partial match
60- return score + 2 ; // lowest score for no match
61- } , 0 ) ;
62- } ;
63-
64- /**
65- * Sorts the user items based on the query match
66- * @param {string } query input string to match against
67- * @returns {function } - A comparison function for sorting
68- */
69- const sortByMatch = ( query ) => {
70- const input = query . toLowerCase ( ) . split ( / [ \s @ . _ - ] + / ) . filter ( Boolean ) ;
71-
72- return ( a , b ) => {
73- const aName = ( a . firstName + ' ' + a . lastName ) . toLowerCase ( ) ;
74- const bName = ( b . firstName + ' ' + b . lastName ) . toLowerCase ( ) ;
75- const aEmail = a . email . toLowerCase ( ) ;
76- const bEmail = b . email . toLowerCase ( ) ;
77-
78- // First Priority: sort by name match
79- const nameScoreA = tokenScores ( aName , input ) ;
80- const nameScoreB = tokenScores ( bName , input ) ;
81- if ( nameScoreA !== nameScoreB ) {
82- return nameScoreA - nameScoreB ;
83- }
84-
85- // Second Priority: sort by email match
86- const emailScoreA = tokenScores ( aEmail , input ) ;
87- const emailScoreB = tokenScores ( bEmail , input ) ;
88- if ( emailScoreA !== emailScoreB ) {
89- return emailScoreA - emailScoreB ;
42+ for ( let i = 1 ; i <= m ; i ++ ) {
43+ for ( let j = 1 ; j <= n ; j ++ ) {
44+ const cost = a [ i - 1 ] === b [ j - 1 ] ? 0 : 1 ;
45+ dp [ i ] [ j ] = Math . min (
46+ dp [ i - 1 ] [ j ] + 1 ,
47+ dp [ i ] [ j - 1 ] + 1 ,
48+ dp [ i - 1 ] [ j - 1 ] + cost
49+ ) ;
9050 }
51+ }
52+ return dp [ m ] [ n ] ;
53+ }
9154
92- // Tie-breaker: alphabetical email sort
93- return a . email . localeCompare ( b . email ) ;
94- } ;
95- } ;
55+ // Find top 5 matching users and sort results based on best match of name or email
56+ User . find ( { } , { password : 0 } )
57+ . then ( users => {
58+ const matchingUsers = users . map ( user => {
59+ const firstNameScore = levenshteinDistance ( req . body . query , user . firstName ) ;
60+ const lastNameScore = levenshteinDistance ( req . body . query , user . lastName ) ;
61+ const emailScore = levenshteinDistance ( req . body . query , user . email ) ;
62+ return {
63+ user,
64+ score : Math . min ( firstNameScore , lastNameScore , emailScore )
65+ } ;
66+ } ) ;
9667
97- // Find user and sort results based on best match of full name or email
98- User . find ( maybeOr , { password : 0 } )
99- . limit ( 5 )
100- . then ( items => {
101- items . sort ( sortByMatch ( req . body . query ) ) ;
68+ const items = matchingUsers
69+ . sort ( ( a , b ) => a . score - b . score )
70+ . slice ( 0 , 5 )
71+ . map ( item => item . user ) ;
10272 res . status ( OK ) . send ( { items } ) ;
10373 } )
10474 . catch ( ( error ) => {
0 commit comments