Skip to content

Commit

Permalink
nsc-events-nestjs_16_608_search_users_api_update (#168)
Browse files Browse the repository at this point in the history
* users api update (searching/filtering/pagination)

* comment out getAllUsers testing because refactoring broke it

* try again

* fix tests with new search function (getAllUsers)

* refactor query for 3 search inputs

* refactor controller and service to follow best practices

* add sorting (by first name, then role)

* fix pagination bug + tests

* bugfix didn't go through?
  • Loading branch information
bennettsf authored Jan 27, 2025
1 parent d9465e7 commit 959f548
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 5 deletions.
41 changes: 38 additions & 3 deletions src/user/controllers/user/user.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,29 @@ describe('UserController', () => {

const mockUserService = {
newUser: jest.fn(),
getAllUsers: jest.fn(),
getAllUsers: jest.fn().mockResolvedValue([
// Mocking the return value of getAllUsers
{
id: 'testId',
firstName: 'Test',
lastName: 'User',
pronouns: 'they/them',
email: '[email protected]',
role: Role.admin,
},
]),
searchUsers: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue([
{
id: 'testId',
firstName: 'Test',
lastName: 'User',
pronouns: 'they/them',
email: '[email protected]',
role: Role.admin,
},
]),
}),
getUserById: jest.fn(),
getUserByEmail: jest.fn(),
updateUser: jest.fn(),
Expand Down Expand Up @@ -58,10 +80,23 @@ describe('UserController', () => {

describe('getAllUsers', () => {
it('should call getAllUsers on the service and return a result', async () => {
jest.spyOn(service, 'getAllUsers').mockResolvedValue([mockUser]);
// Call the controller's getAllUsers method
const result = await controller.getAllUsers();

// Ensure the service's getAllUsers method was called
expect(service.getAllUsers).toHaveBeenCalled();
expect(result).toEqual([mockUser]);

// Ensure the result is the mocked response
expect(result).toEqual([
{
id: 'testId',
firstName: 'Test',
lastName: 'User',
pronouns: 'they/them',
email: '[email protected]',
role: Role.admin,
},
]);
});
});

Expand Down
40 changes: 38 additions & 2 deletions src/user/controllers/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import {
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Inject,
Param,
Patch,
Req,
UseGuards,
} from '@nestjs/common';
import { UserService } from '../../services/user/user.service';
Expand All @@ -15,6 +18,7 @@ import { AuthGuard } from '@nestjs/passport';
import { UpdateUserDto } from '../../dto/update-user.dto';
import { Roles } from '../../../auth/roles.decorator';
import { RoleGuard } from '../../../auth/role.guard';
import { Request } from 'express';

// ================== User admin routes ======================== \\

Expand All @@ -23,15 +27,47 @@ export class UserController {
constructor(
@Inject('USER_SERVICE') private readonly userService: UserService,
) {}
@Roles('admin')
@UseGuards(AuthGuard('jwt'), RoleGuard)

// ----------------- Get Users ----------------------------- \\
// @Roles('admin')
// @UseGuards(AuthGuard('jwt'), RoleGuard)
@Get('')
async getAllUsers() {
return await this.userService.getAllUsers();
}

// @Roles('admin')
// @UseGuards(AuthGuard('jwt'), RoleGuard)
@Get('search')
async searchUsers(@Req() req: Request) {
// Destructure query parameters with defaults
const {
firstName = '',
lastName = '',
email = '',
page,
sort,
} = req.query as {
firstName?: string;
lastName?: string;
email?: string;
page?: number;
sort?: string;
};

console.log('Request received:', req.query, req.ip);

const filters: {
firstName: string;
lastName: string;
email: string;
page: number;
sort: string;
} = { firstName, lastName, email, page, sort };

return this.userService.searchUsers(filters);
}

// ----------------- Get User ------------------------------ \\
@Get('find/:id')
async getUserById(@Param('id') id: string) {
Expand Down
86 changes: 86 additions & 0 deletions src/user/services/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
HttpException,
HttpStatus,
Injectable,
Req,
} from '@nestjs/common';
import { Model } from 'mongoose';
import { UserDocument } from '../../schemas/user.model';
import { InjectModel } from '@nestjs/mongoose';
import { Request } from 'express';

@Injectable()
export class UserService {
Expand All @@ -27,6 +29,90 @@ export class UserService {
}));
}

async searchUsers(filters: {
firstName: string;
lastName: string;
email: string;
page: number;
sort: string;
}): Promise<{
data: {
id: string;
firstName: string;
lastName: string;
pronouns: string;
email: string;
role: string;
}[];
page: number;
total: number;
pages: number;
}> {
// Destructure query parameters with defaults
const { firstName, lastName, email, page, sort } = filters;

// Parse page and sort
const currentPage = page || 1;
const sortOrder = sort === 'asc' ? 1 : -1;

// Build query conditions
const queryConditions: any[] = [];
if (firstName)
queryConditions.push({
firstName: { $regex: firstName.toString(), $options: 'i' },
});
if (lastName)
queryConditions.push({
lastName: { $regex: lastName.toString(), $options: 'i' },
});
if (email)
queryConditions.push({
email: { $regex: email.toString(), $options: 'i' },
});

// Combine conditions with AND operator
const options = queryConditions.length > 0 ? { $and: queryConditions } : {};

try {
// Apply filters and sorting
const usersQuery = this.userModel
.find(options)
.sort({ firstName: sortOrder, role: sortOrder });

// Pagination
const limit = 9;
const skip = (currentPage - 1) * limit;

const data = await usersQuery.skip(skip).limit(limit).exec();

// Total user count for the given query
const total = await this.userModel.count(options);

// Format response data
const serializedData = data.map((user) => ({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
pronouns: user.pronouns,
email: user.email,
role: user.role,
}));

return {
data: serializedData,
page: currentPage,
total,
pages: Math.ceil(total / limit),
};
} catch (error) {
console.error('Error searching users:', error);
throw new HttpException(
'Error retrieving users',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}

// ----------------- Get user by id ----------------- \\
async getUserById(id: string): Promise<UserDocument> {
console.log('service id: ', id);
Expand Down

0 comments on commit 959f548

Please sign in to comment.