diff --git a/src/users/dto/update-email.dto.ts b/src/users/dto/update-email.dto.ts new file mode 100644 index 0000000..6b2e2c1 --- /dev/null +++ b/src/users/dto/update-email.dto.ts @@ -0,0 +1,14 @@ +import { createZodDto } from "nestjs-zod"; +import { z } from "nestjs-zod/z"; + +export const UpdateEmailSchema = z + .object({ + email: z + .string({ + required_error: "Email is required", + }) + .email("Invalid email"), + }) + .required(); + +export class UpdateEmailDTO extends createZodDto(UpdateEmailSchema) {} diff --git a/src/users/dto/update-password.dto.ts b/src/users/dto/update-password.dto.ts new file mode 100644 index 0000000..09709be --- /dev/null +++ b/src/users/dto/update-password.dto.ts @@ -0,0 +1,33 @@ +import { createZodDto } from "nestjs-zod"; +import { z } from "nestjs-zod/z"; + +// TODO: see if it can be refactored + +export const UpdatePasswordSchema = z + .object({ + old_password: z + .password({ + required_error: "Password is required", + }) + .min(8) + .max(32) + .atLeastOne("digit") + .atLeastOne("uppercase") + .atLeastOne("lowercase") + .atLeastOne("special") + .transform((value) => value.replace(/\s+/g, "")), + new_password: z + .password({ + required_error: "Password is required", + }) + .min(8) + .max(32) + .atLeastOne("digit") + .atLeastOne("uppercase") + .atLeastOne("lowercase") + .atLeastOne("special") + .transform((value) => value.replace(/\s+/g, "")), + }) + .required(); + +export class UpdatePasswordDTO extends createZodDto(UpdatePasswordSchema) {} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index ac4e3e6..3f2cd7e 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -23,6 +23,8 @@ import { CreateUserDTO } from "./dto/create-user.dto"; import { Public } from "src/public.decorator"; import { UpdateNameDTO } from "./dto/update-name.dto"; import { User } from "./types/user.type"; +import { UpdateEmailDTO } from "./dto/update-email.dto"; +import { UpdatePasswordDTO } from "./dto/update-password.dto"; @ApiTags("Users") @Controller("users") @@ -49,7 +51,7 @@ export class UserController { description: "Not authenticated / Invalid JWT Token", }) me(@Request() req) { - return req.user; // TODO: Add typing to req.user + return req.user; } @Public() @@ -74,12 +76,23 @@ export class UserController { @Patch("/email") @ApiOperation({ summary: "Updates the email of a logged user" }) @ApiBearerAuth("JWT") - updateEmail() {} + updateEmail(@Body() body: UpdateEmailDTO, @Request() req) { + return this.userService.updateEmail(req.user as User, body.email); + } @Patch("/password") @ApiOperation({ summary: "Updates the password of a logged user" }) @ApiBearerAuth("JWT") - updatePassword() {} + updatePassword( + @Body() { old_password, new_password }: UpdatePasswordDTO, + @Request() req, + ) { + return this.userService.updatePassword( + req.user as User, + old_password, + new_password, + ); + } @Patch("/image") @ApiOperation({ summary: "Add a profile image" }) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 9e87f68..6f8be9a 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -101,6 +101,35 @@ export class UserService { return user; } + async updateEmail( + loggedUser: User, + email: string, + ): Promise<{ message: string }> { + const user = await this.prisma.user.findFirst({ + where: { id: loggedUser.id }, + }); + + if (email !== undefined && email.trim() !== user.email) { + const isAlreadyInUse = await this.prisma.user.findFirst({ + where: { email }, + }); + if (isAlreadyInUse != null && isAlreadyInUse.email !== user.email) { + throw new BadRequestException("Email already in use"); + } + + await this.prisma.user.update({ + where: { + id: loggedUser.id, + }, + data: { + email: email ?? user.email, + }, + }); + + return { message: "Email updated successfully" }; + } + } + async updateName( loggedUser: User, username: string | undefined, @@ -133,4 +162,36 @@ export class UserService { }, }); } + + async updatePassword( + loggedUser: User, + old_password: string, + new_password: string, + ): Promise<{ message: string }> { + const id = loggedUser.id; + + const user = await this.prisma.user.findFirst({ + where: { id }, + }); + + const validatePassword = await bcrypt.compare(old_password, user.password); + + if (!validatePassword) { + throw new BadRequestException("Wrong password"); + } + + const salt = await bcrypt.genSalt(15); + const hash = await bcrypt.hash(new_password, salt); + + await this.prisma.user.update({ + where: { + id, + }, + data: { + password: hash, + }, + }); + + return { message: "Password updated successfully" }; + } }