diff --git a/package-lock.json b/package-lock.json index dbb950d..1b2f7f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,12 +326,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -366,22 +366,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -473,9 +473,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -590,9 +590,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.14", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz", - "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -779,33 +779,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", - "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.3", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.11", - "@babel/types": "^7.22.11", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -823,13 +823,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", - "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { diff --git a/src/user/controllers/user/user.controller.spec.ts b/src/user/controllers/user/user.controller.spec.ts deleted file mode 100644 index 42d95da..0000000 --- a/src/user/controllers/user/user.controller.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserController } from './user.controller'; -import { UserService } from '../../services/user/user.service'; -import { Role } from '../../../auth/schemas/userAuth.model'; - -type MockCreateUserDto = { - id: string; - email: string; - name: string; - password: string; - role: Role; -}; - -const mockUserService = { - newUser: jest.fn().mockResolvedValue('someId'), - updateUser: jest.fn().mockResolvedValue(null), - removeUser: jest.fn().mockResolvedValue(null), -}; - -describe('UserController', () => { - let controller: UserController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UserController], - providers: [ - { - provide: UserService, - useValue: mockUserService, - }, - ], - }).compile(); - - controller = module.get(UserController); - jest.clearAllMocks(); - }); - - describe('Admin CRUD operations', () => { - const mockRequest = { user: { role: 'admin' } }; - const mockUser: MockCreateUserDto = { - id: 'testId', - name: 'test', - email: 'jeremy@gmail.com', - password: '1234567890', - role: Role.admin, - }; - - it('should add a user', async () => { - const result = await controller.adminAddUser(mockUser, mockRequest); - expect(result.id).toEqual('someId'); - expect(mockUserService.newUser).toHaveBeenCalledWith(mockUser); - }); - - it('should update a user', async () => { - const updatedName = 'updatedName'; - mockUser.name = updatedName; - - await controller.updateUser( - mockUser.id, - mockUser.name, - mockUser.email, - mockUser.role, - ); - }); - - it('should delete a user', async () => { - await controller.adminDeleteUser(mockUser.id, mockRequest); - expect(mockUserService.removeUser).toHaveBeenCalledWith(mockUser.id); - }); - }); -}); diff --git a/src/user/controllers/user/user.controller.ts b/src/user/controllers/user/user.controller.ts index cecdd15..351383f 100644 --- a/src/user/controllers/user/user.controller.ts +++ b/src/user/controllers/user/user.controller.ts @@ -6,14 +6,12 @@ import { Get, Param, Patch, - Post, - Req, UseGuards, } from '@nestjs/common'; import { UserService } from '../../services/user/user.service'; -import { Role } from '../../schemas/user.model'; -import { CreateUserDto } from '../../dto/create-user.dto'; +import { UserDocument } from '../../schemas/user.model'; import { AuthGuard } from '@nestjs/passport'; +import { UpdateUserDto } from '../../dto/update-user.dto'; // ================== User admin routes ======================== \\ @Controller('users') @@ -21,13 +19,6 @@ import { AuthGuard } from '@nestjs/passport'; export class UserController { constructor(private readonly userService: UserService) {} - // ----------------- Add User ------------------------------- \\ - @Post('new') - async adminAddUser(@Body() createUserDto: CreateUserDto, @Req() req: any) { - const generatedId = await this.userService.newUser(createUserDto); - return { id: generatedId }; - } - // ----------------- Get Users ----------------------------- \\ @Get('') async getAllUsers() { @@ -48,18 +39,13 @@ export class UserController { // ----------------- Update User --------------------------- \\ @Patch('update/:id') - async updateUser( - @Param('id') id: string, - @Body('name') name: string, - @Body('email') email: string, - @Body('role') role: Role, - ) { - await this.userService.updateUser(id, name, email, role); + async updateUser(@Param('id') id: string, @Body() userDto: UpdateUserDto) { + return await this.userService.updateUser(id, userDto as UserDocument); } // ----------------- Delete User --------------------------- // @Delete('remove/:id') - async adminDeleteUser(@Param('id') id: string, @Req() req: any) { + async adminDeleteUser(@Param('id') id: string) { await this.userService.removeUser(id); } diff --git a/src/user/dto/update-user.dto.ts b/src/user/dto/update-user.dto.ts new file mode 100644 index 0000000..7bdf627 --- /dev/null +++ b/src/user/dto/update-user.dto.ts @@ -0,0 +1,20 @@ +import { IsEmpty, IsEnum, IsOptional, IsString } from 'class-validator'; +import { Role } from '../schemas/user.model'; + +export class UpdateUserDto { + @IsOptional() + @IsString() + readonly name: string; + + @IsOptional() + @IsEmpty({ message: 'Cannot update email here' }) + readonly email: string; + + @IsOptional() + @IsEmpty({ message: 'Cannot update password here' }) + readonly password: string; + + @IsOptional() + @IsEnum(Role) + readonly role: Role; +} diff --git a/src/user/services/user/user.service.ts b/src/user/services/user/user.service.ts index 00ade27..d04c020 100644 --- a/src/user/services/user/user.service.ts +++ b/src/user/services/user/user.service.ts @@ -1,19 +1,13 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { BadRequestException, - ForbiddenException, HttpException, HttpStatus, Injectable, - NotFoundException, - UnauthorizedException, } from '@nestjs/common'; -import * as bcrypt from 'bcryptjs'; import { Model } from 'mongoose'; -import { Role, UserDocument } from '../../schemas/user.model'; +import { UserDocument } from '../../schemas/user.model'; import { InjectModel } from '@nestjs/mongoose'; -import { CreateUserDto } from 'src/user/dto/create-user.dto'; -import { log } from 'console'; @Injectable() export class UserService { @@ -21,25 +15,6 @@ export class UserService { // User defined in user.module.ts } - // ----------------- Add user ----------------- \\ - async newUser(createUserDto: CreateUserDto): Promise { - const { name, email, password, role } = createUserDto; - const hashedPassword = await bcrypt.hash(password, 10); - const newUser = new this.userModel({ - name, - email, - password: hashedPassword, - role, - }); - try { - const result = await newUser.save(); - return result._id; - } catch (error) { - log(error); - return error; - } - } - // ----------------- Get all users ----------------- \\ async getAllUsers(): Promise { //fix: Use serialization to mask password, so we don't have to transform the data @@ -84,30 +59,38 @@ export class UserService { if (!user) { throw new HttpException('User not found!', 404); } - - return user; + return { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + } as UserDocument; } // ----------------- Update user ----------------- \\ - async updateUser(id: string, name: string, email: string, role: Role) { - const updatedUser = await this.userModel.findById(id).exec(); - if (!updatedUser) { - throw new NotFoundException('User not found'); - } - - if (name) { - updatedUser.name = name; + async updateUser(id: string, user: UserDocument) { + // we may want to check if id is a valid id, if you remove/add a character, it returns a 500 error + if (user === null) { + throw new BadRequestException(`Updated User not supplied`); } - if (email) { - updatedUser.email = email; - } - if (role) { - updatedUser.role = role; + const updatedUser = await this.userModel + .findByIdAndUpdate(id, user, { + new: true, + runValidators: true, + }) + .exec(); + if (!updatedUser) { + throw new HttpException( + `User with id ${id} not found`, + HttpStatus.NOT_FOUND, + ); } - - const updated = await updatedUser.save(); - console.log(updated); - return updated; + return { + id: updatedUser.id, + name: updatedUser.name, + email: updatedUser.email, + role: updatedUser.role, + } as UserDocument; } // ----------------- Delete user ----------------- \\