-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[backend] Use class serializer interceptor #29
Conversation
cf. Building a REST API with NestJS and Prisma: Handling Relational Data https://www.prisma.io/blog/nestjs-prisma-relational-data-7D056s1kOabc#add-a-user-model-to-the-database
Warning Rate Limit Exceeded@usatie has exceeded the limit for the number of files or commits that can be reviewed per hour. Please wait 0 minutes and 56 seconds before requesting another review. How to resolve this issue?After the wait time has elapsed, a review can be triggered using the @coderabbitai review command. Alternatively, push new commits to this PR. We recommend that you space out your commits to avoid hitting the rate limit. How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per repository. WalkthroughThe changes in this update focus on improving the handling of user data in the backend. The password field is now excluded from serialization using the Changes
Poem
TipsChat with CodeRabbit Bot (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 3
Configuration used: CodeRabbit UI
Files selected for processing (4)
- backend/src/main.ts (2 hunks)
- backend/src/user/entities/user.entity.ts (2 hunks)
- backend/src/user/user.controller.ts (1 hunks)
- backend/src/user/user.service.ts (1 hunks)
Additional comments: 10
backend/src/user/entities/user.entity.ts (2)
1-8: The constructor is a good addition to the
UserEntity
class. It allows for easy instantiation of the class with a partial object. This is a common pattern in TypeScript and is useful for creating instances from database results or request bodies.19-20: The
@Exclude()
decorator is a good way to prevent the password from being serialized when instances ofUserEntity
are transformed to JSON. This is a good security practice as it prevents the password from being accidentally exposed in API responses or logs.backend/src/main.ts (2)
1-7: The
Reflector
class is imported from@nestjs/core
and theClassSerializerInterceptor
is imported from@nestjs/common
. These are necessary for the global application of theClassSerializerInterceptor
.10-13: The
ClassSerializerInterceptor
is globally applied to the application using theapp.useGlobalInterceptors()
method. This interceptor will automatically serialize the response objects according to the decorators used in the entity classes.+ app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); // enable serialization
backend/src/user/user.controller.ts (5)
27-34: The
create
method now returns a newUserEntity
instance created from the result ofuserService.create
. This is a good practice as it ensures that the returned user object is an instance ofUserEntity
and not just a plain JavaScript object. This allows for better type checking and can prevent potential runtime errors.36-41: The
findAll
method now returns an array ofUserEntity
instances created from the result ofuserService.findAll
. This is a good practice as it ensures that the returned user objects are instances ofUserEntity
and not just plain JavaScript objects. This allows for better type checking and can prevent potential runtime errors.43-49: The
findOne
method now returns aUserEntity
instance created from the result ofuserService.findOne
. This is a good practice as it ensures that the returned user object is an instance ofUserEntity
and not just a plain JavaScript object. This allows for better type checking and can prevent potential runtime errors.51-60: The
update
method now returns aUserEntity
instance created from the result ofuserService.update
. This is a good practice as it ensures that the returned user object is an instance ofUserEntity
and not just a plain JavaScript object. This allows for better type checking and can prevent potential runtime errors.62-68: The
remove
method now returns aUserEntity
instance created from the result ofuserService.remove
. This is a good practice as it ensures that the returned user object is an instance ofUserEntity
and not just a plain JavaScript object. This allows for better type checking and can prevent potential runtime errors.backend/src/user/user.service.ts (1)
- 24-27: The
findUniqueOrThrow
method will throw an error if the user is not found. Ensure that this error is being properly handled in the calling code.
backend/src/user/user.service.ts
Outdated
update( | ||
id: number, | ||
updateUserDto: UpdateUserDto, | ||
): Promise<UserWithoutPassword> { | ||
): Promise<User> { | ||
return this.prisma.user | ||
.update({ | ||
where: { id: id }, | ||
data: updateUserDto, | ||
}) | ||
.then(excludePassword); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When updating a user, consider checking if the user exists before attempting the update. This can prevent unnecessary database operations and provide more informative error messages.
+ async userExists(id: number): Promise<boolean> {
+ const user = await this.prisma.user.findUnique({ where: { id: id } });
+ return user !== null;
+ }
- update(
- id: number,
- updateUserDto: UpdateUserDto,
- ): Promise<User> {
- return this.prisma.user
- .update({
- where: { id: id },
- data: updateUserDto,
- });
- }
+ async update(
+ id: number,
+ updateUserDto: UpdateUserDto,
+ ): Promise<User> {
+ if (!await this.userExists(id)) {
+ throw new Error(`User with id ${id} does not exist`);
+ }
+ return this.prisma.user
+ .update({
+ where: { id: id },
+ data: updateUserDto,
+ });
+ }
Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Make sure it correctly replaces the highlighted code, has no missing lines and indentaion issues.
update( | |
id: number, | |
updateUserDto: UpdateUserDto, | |
): Promise<UserWithoutPassword> { | |
): Promise<User> { | |
return this.prisma.user | |
.update({ | |
where: { id: id }, | |
data: updateUserDto, | |
}) | |
.then(excludePassword); | |
}); | |
} | |
async userExists(id: number): Promise<boolean> { | |
const user = await this.prisma.user.findUnique({ where: { id: id } }); | |
return user !== null; | |
} | |
async update( | |
id: number, | |
updateUserDto: UpdateUserDto, | |
): Promise<User> { | |
if (!await this.userExists(id)) { | |
throw new Error(`User with id ${id} does not exist`); | |
} | |
return this.prisma.user | |
.update({ | |
where: { id: id }, | |
data: updateUserDto, | |
}); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking if the user exists before attempting the update is done by ValidationPipe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@usatie, thank you for your comment. You're correct that the ValidationPipe
can be used to validate incoming data in the request. However, it doesn't inherently check if a resource (like a user in this case) exists in the database before performing operations like update or delete.
The suggestion to add a userExists
method is to ensure that the user exists in the database before attempting to update. This can provide more informative error messages and prevent unnecessary database operations. If the ValidationPipe
in your application is already configured to perform this check, then the userExists
method may not be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is already configured to perform that check, so it's unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@usatie, thank you for the clarification. If the ValidationPipe
is already configured to check if the user exists before attempting an update, then the userExists
method is indeed unnecessary. I appreciate your time and input on this matter.
backend/src/user/user.service.ts
Outdated
remove(id: number): Promise<User> { | ||
return this.prisma.user | ||
.delete({ | ||
where: { id: id }, | ||
}) | ||
.then(excludePassword); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the update method, consider checking if the user exists before attempting to delete. This can prevent unnecessary database operations and provide more informative error messages.
- remove(id: number): Promise<User> {
- return this.prisma.user
- .delete({
- where: { id: id },
- });
- }
+ async remove(id: number): Promise<User> {
+ if (!await this.userExists(id)) {
+ throw new Error(`User with id ${id} does not exist`);
+ }
+ return this.prisma.user
+ .delete({
- where: { id: id },
+ });
+ }
Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Make sure it correctly replaces the highlighted code, has no missing lines and indentaion issues.
remove(id: number): Promise<User> { | |
return this.prisma.user | |
.delete({ | |
where: { id: id }, | |
}) | |
.then(excludePassword); | |
}); | |
} | |
async remove(id: number): Promise<User> { | |
if (!await this.userExists(id)) { | |
throw new Error(`User with id ${id} does not exist`); | |
} | |
return this.prisma.user | |
.delete({ | |
where: { id: id }, | |
}); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 1
Configuration used: CodeRabbit UI
Files selected for processing (3)
- backend/src/user/entities/user.entity.ts (2 hunks)
- backend/src/user/user.controller.ts (1 hunks)
- backend/src/user/user.service.ts (1 hunks)
Files skipped from review due to trivial changes (1)
- backend/src/user/entities/user.entity.ts
Additional comments: 6
backend/src/user/user.controller.ts (5)
27-32: The
create
method is now asynchronous and returns a newUserEntity
object created from the result ofuserService.create
. Ensure that thecreate
method inuserService
returns a promise that resolves to a user object that can be used to create aUserEntity
.34-39: The
findAll
method is now asynchronous and returns an array ofUserEntity
objects created from the result ofuserService.findAll
. Ensure that thefindAll
method inuserService
returns a promise that resolves to an array of user objects that can be used to createUserEntity
objects.41-45: The
findOne
method is now asynchronous and returns aUserEntity
object created from the result ofuserService.findOne
. Ensure that thefindOne
method inuserService
returns a promise that resolves to a user object that can be used to create aUserEntity
.47-54: The
update
method is now asynchronous and returns aUserEntity
object created from the result ofuserService.update
. Ensure that theupdate
method inuserService
returns a promise that resolves to a user object that can be used to create aUserEntity
.56-60: The
remove
method is now asynchronous and returns aUserEntity
object created from the result ofuserService.remove
. Ensure that theremove
method inuserService
returns a promise that resolves to a user object that can be used to create aUserEntity
.backend/src/user/user.service.ts (1)
- 34-38: The return types of these methods have been changed to
User
fromUserWithoutPassword
. This means that the password field is now included in the returned user objects. This could potentially expose sensitive user information. Ensure that the password field is properly excluded from the response in the controller or elsewhere in the application.
backend/src/user/user.service.ts
Outdated
async create(createUserDto: CreateUserDto): Promise<User> { | ||
const saltRounds = 10; | ||
const hashedPassword = await hash(createUserDto.password, saltRounds); | ||
const userData = { ...createUserDto, password: hashedPassword }; | ||
return this.prisma.user.create({ data: userData }).then(excludePassword); | ||
return this.prisma.user.create({ data: userData }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The password hashing logic is still present in the create
method. Consider moving it to a separate function for better modularity and reusability. This will also make the code easier to test and maintain.
+ async hashPassword(password: string): Promise<string> {
+ const saltRounds = 10;
+ return await hash(password, saltRounds);
+ }
- async create(createUserDto: CreateUserDto): Promise<User> {
- const saltRounds = 10;
- const hashedPassword = await hash(createUserDto.password, saltRounds);
- const userData = { ...createUserDto, password: hashedPassword };
- return this.prisma.user.create({ data: userData });
- }
+ async create(createUserDto: CreateUserDto): Promise<User> {
+ const hashedPassword = await this.hashPassword(createUserDto.password);
+ const userData = { ...createUserDto, password: hashedPassword };
+ return this.prisma.user.create({ data: userData });
+ }
Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Make sure it correctly replaces the highlighted code, has no missing lines and indentaion issues.
async create(createUserDto: CreateUserDto): Promise<User> { | |
const saltRounds = 10; | |
const hashedPassword = await hash(createUserDto.password, saltRounds); | |
const userData = { ...createUserDto, password: hashedPassword }; | |
return this.prisma.user.create({ data: userData }).then(excludePassword); | |
return this.prisma.user.create({ data: userData }); | |
} | |
async hashPassword(password: string): Promise<string> { | |
const saltRounds = 10; | |
return await hash(password, saltRounds); | |
} | |
async create(createUserDto: CreateUserDto): Promise<User> { | |
const hashedPassword = await this.hashPassword(createUserDto.password); | |
const userData = { ...createUserDto, password: hashedPassword }; | |
return this.prisma.user.create({ data: userData }); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 0
Configuration used: CodeRabbit UI
Files selected for processing (1)
- backend/src/user/user.controller.ts (2 hunks)
Additional comments: 6
backend/src/user/user.controller.ts (6)
6-11: The import statements are well-organized and only necessary modules are imported. Good job on maintaining clean and efficient code.
28-34: The
create
method now returns aUserEntity
object. Ensure that the frontend or any other consumer of this API is expecting this new return type.36-41: The
findAll
method now returns an array ofUserEntity
objects. As with thecreate
method, ensure that the frontend or any other consumer of this API is expecting this new return type.43-47: The
findOne
method now returns aUserEntity
object. As with thecreate
method, ensure that the frontend or any other consumer of this API is expecting this new return type.49-56: The
update
method now returns aUserEntity
object. As with thecreate
method, ensure that the frontend or any other consumer of this API is expecting this new return type.58-63: The
remove
method now returns a promise ofvoid
. This is a good practice as it doesn't reveal any unnecessary information to the client. However, ensure that the frontend or any other consumer of this API is expecting this new return type.
UserEntityをResponseタイプに持つhandlerから自動的に
@Exclude
で指定したpasswordカラムが削除されるようにしました。とはいえ、Prismaで指定されたUserを返り値に指定してしまうことがあったらダメなので、もっとまともなチェックはないのかなーという感じがしました。
cf. Building a REST API with NestJS and Prisma: Handling Relational Data https://www.prisma.io/blog/nestjs-prisma-relational-data-7D056s1kOabc#add-a-user-model-to-the-database
Summary by CodeRabbit
UserEntity
class to include a constructor and exclude password from serialization.UserController
andUserService
classes for improved data return types.excludePassword
function to prevent password exposure in user objects.