Skip to content
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

Merged
merged 4 commits into from
Nov 6, 2023

Conversation

usatie
Copy link
Owner

@usatie usatie commented Nov 6, 2023

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

  • New Features
    • Introduced global interceptors for enhanced data handling.
  • Refactor
    • Updated UserEntity class to include a constructor and exclude password from serialization.
    • Modified UserController and UserService classes for improved data return types.
  • Bug Fixes
    • Removed the excludePassword function to prevent password exposure in user objects.

Copy link
Contributor

coderabbitai bot commented Nov 6, 2023

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.
Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
Please see our FAQ for further information.

Commits Files that changed from the base of the PR and between 885d819 and f0f5856.

Walkthrough

The 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 @Exclude() decorator from the class-transformer package. Additionally, the ClassSerializerInterceptor is globally used to automatically apply serialization rules to all responses. The return types of the UserService methods have been changed to User instead of UserWithoutPassword, and the excludePassword function has been removed.

Changes

File Path Change Summary
backend/src/main.ts Imported Reflector and ClassSerializerInterceptor. Added global use of ClassSerializerInterceptor.
backend/src/user/entities/user.entity.ts Added a constructor and excluded the password property from serialization.
backend/src/user/user.controller.ts Updated methods to return UserEntity instances.
backend/src/user/user.service.ts Removed excludePassword function. Updated methods to return User instead of UserWithoutPassword.

Poem

🍂 As the leaves fall this November day, 🍁

Changes in code have come our way. 🖥️

No more passwords in plain sight, 🚫🔑

A rabbit's work in the moonlight. 🐇🌙

Celebrating this day in history, 📆

When the first artificial satellite, Sputnik, ended its journey. 🛰️

So here's to progress, and to all who steer, 🥂

Making our code safer, year after year. 💻🔒


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai help to get help.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 2641e64 and b4ce51b.
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 of UserEntity 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 the ClassSerializerInterceptor is imported from @nestjs/common. These are necessary for the global application of the ClassSerializerInterceptor.

  • 10-13: The ClassSerializerInterceptor is globally applied to the application using the app.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 new UserEntity instance created from the result of userService.create. This is a good practice as it ensures that the returned user object is an instance of UserEntity 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 of UserEntity instances created from the result of userService.findAll. This is a good practice as it ensures that the returned user objects are instances of UserEntity 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 a UserEntity instance created from the result of userService.findOne. This is a good practice as it ensures that the returned user object is an instance of UserEntity 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 a UserEntity instance created from the result of userService.update. This is a good practice as it ensures that the returned user object is an instance of UserEntity 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 a UserEntity instance created from the result of userService.remove. This is a good practice as it ensures that the returned user object is an instance of UserEntity 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.

Comment on lines 29 to 38
update(
id: number,
updateUserDto: UpdateUserDto,
): Promise<UserWithoutPassword> {
): Promise<User> {
return this.prisma.user
.update({
where: { id: id },
data: updateUserDto,
})
.then(excludePassword);
});
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 6, 2023

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.

Suggested change
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,
});
}

Copy link
Owner Author

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.

Copy link
Contributor

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.

Copy link
Owner Author

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.

Copy link
Contributor

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.

Comment on lines 40 to 45
remove(id: number): Promise<User> {
return this.prisma.user
.delete({
where: { id: id },
})
.then(excludePassword);
});
}
Copy link
Contributor

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.

Suggested change
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 },
});
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between b4ce51b and 215de65.
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 new UserEntity object created from the result of userService.create. Ensure that the create method in userService returns a promise that resolves to a user object that can be used to create a UserEntity.

  • 34-39: The findAll method is now asynchronous and returns an array of UserEntity objects created from the result of userService.findAll. Ensure that the findAll method in userService returns a promise that resolves to an array of user objects that can be used to create UserEntity objects.

  • 41-45: The findOne method is now asynchronous and returns a UserEntity object created from the result of userService.findOne. Ensure that the findOne method in userService returns a promise that resolves to a user object that can be used to create a UserEntity.

  • 47-54: The update method is now asynchronous and returns a UserEntity object created from the result of userService.update. Ensure that the update method in userService returns a promise that resolves to a user object that can be used to create a UserEntity.

  • 56-60: The remove method is now asynchronous and returns a UserEntity object created from the result of userService.remove. Ensure that the remove method in userService returns a promise that resolves to a user object that can be used to create a UserEntity.

backend/src/user/user.service.ts (1)
  • 34-38: The return types of these methods have been changed to User from UserWithoutPassword. 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.

Comment on lines 12 to 17
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 });
}
Copy link
Contributor

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.

Suggested change
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 });
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Commits Files that changed from the base of the PR and between 215de65 and 885d819.
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 a UserEntity 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 of UserEntity objects. As with the create 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 a UserEntity object. As with the create 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 a UserEntity object. As with the create 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 of void. 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.

@usatie usatie changed the title [WIP] [backend] Use class serializer interceptor [backend] Use class serializer interceptor Nov 6, 2023
@usatie usatie merged commit c42e152 into main Nov 6, 2023
1 check passed
@usatie usatie deleted the feat/backend/class-serializer-interceptor branch November 6, 2023 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant