Skip to content

Commit

Permalink
Still improve flexibility of user search, refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksTeresh committed Oct 17, 2024
1 parent bea71af commit 9bea50a
Showing 1 changed file with 122 additions and 60 deletions.
182 changes: 122 additions & 60 deletions src/server/routes/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,112 @@ import express from 'express'
import { User } from '../db/models'
import { userFields } from './config'

const USER_FETCH_LIMIT = 100

const usersRouter = express.Router()

const getWhereClauseForOneWordSearch = (search: string) => ({
[Op.or]: [
{
username: {
[Op.iLike]: `${search}%`,
},
},
{
firstName: {
[Op.iLike]: `${search}%`,
},
},
{
lastName: {
[Op.iLike]: `${search}%`,
},
},
{
email: {
[Op.iLike]: `${search}%`,
},
},
{
studentNumber: {
[Op.iLike]: `${search}%`,
},
},
],
})

const getWhereClauseForTwoWordSearch = (search: string) => {
const [firstName, lastName] = search.split(' ')
return {
[Op.or]: [
// assume that the first word is the first name and the second word is the last name
{
firstName: {
[Op.iLike]: `${firstName}%`,
},
lastName: {
[Op.iLike]: `${lastName}%`,
},
},
// assume that both words are the "first name", this is because
// first name includes middle names as well. Thus this allows
// for searching with first and middle names
{
firstName: {
[Op.iLike]: `${search}%`,
},
},
// sometimes, users might first type in last name and then first name
// so we need to account for that as well
{
firstName: {
[Op.iLike]: `${lastName}%`,
},
lastName: {
[Op.iLike]: `${firstName}%`,
},
},
],
}
}

const getWhereClauseForManyWordSearch = (search: string) => {
const searchedWords = search.split(' ')
const [lastName1, ...firstNames1] = searchedWords
// treat the last word in searchedWords as lastName2 and
// the rest as firstNames2 i.e. equivalent to
// const [...firstNames2, lastName2] = searchedWords
// if it'd be possible in JS :)
const lastName2 = searchedWords[searchedWords.length - 1]
const firstNames2 = searchedWords.slice(0, searchedWords.length - 1)

return {
[Op.or]: [
{
firstName: {
[Op.iLike]: `${searchedWords}%`,
},
},
{
firstName: {
[Op.iLike]: `${firstNames1.join(' ')}%`,
},
lastName: {
[Op.iLike]: `${lastName1}%`,
},
},
{
firstName: {
[Op.iLike]: `${firstNames2.join(' ')}%`,
},
lastName: {
[Op.iLike]: `${lastName2}%`,
},
},
],
}
}

interface UserSearchQuery {
search?: string
onlyWithStudyRight?: boolean
Expand Down Expand Up @@ -47,79 +151,37 @@ usersRouter.get('/', async (req, res) => {
}
}

if (trimmedSearch.split(' ').length === 2) {
const [firstName, lastName] = trimmedSearch.split(' ')
const searchedWords = trimmedSearch.split(' ')

if (searchedWords.length === 2) {
const users = await User.findAll({
attributes: userFields,
where: {
...getWhereClauseForTwoWordSearch(trimmedSearch),
...whereClauses,
},
limit: USER_FETCH_LIMIT,
})
res.send(users)
} else if (searchedWords.length > 2) {
const users = await User.findAll({
attributes: userFields,
where: {
[Op.or]: [
// assume that the first word is the first name and the second word is the last name
{
firstName: {
[Op.iLike]: `${firstName}%`,
},
lastName: {
[Op.iLike]: `${lastName}%`,
},
},
// assume that both words are the "first name", this is because
// first name includes middle names as well. Thus this allows
// for searching with first and middle names
{
firstName: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
// sometimes, users might first type in last name and then first name
// so we need to account for that as well
{
firstName: {
[Op.iLike]: `${lastName}%`,
},
lastName: {
[Op.iLike]: `${firstName}%`,
},
},
],
...getWhereClauseForManyWordSearch(trimmedSearch),
...whereClauses,
},
limit: 100,
limit: USER_FETCH_LIMIT,
})
res.send(users)
} else {
// the search consists of only one word
const users = await User.findAll({
attributes: userFields,
where: {
[Op.or]: [
{
username: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
{
firstName: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
{
lastName: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
{
email: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
{
studentNumber: {
[Op.iLike]: `${trimmedSearch}%`,
},
},
],
...getWhereClauseForOneWordSearch(trimmedSearch),
...whereClauses,
},
limit: 100,
limit: USER_FETCH_LIMIT,
})

res.send(users)
Expand Down

0 comments on commit 9bea50a

Please sign in to comment.