From b40249fdfbd398376c2af0cf57ad3a40698bdfda Mon Sep 17 00:00:00 2001 From: M1000fr Date: Thu, 28 Nov 2024 15:53:38 +0100 Subject: [PATCH] feat: Add class-transformer and class-validator packages - Added "@nestjs/mapped-types" package to package.json - Added "class-transformer" and "class-validator" packages to package.json - Imported ValidationPipe from "@nestjs/common" in main.ts and added it as a global pipe - Added validation decorators to CreateUserInput, SetUserPasswordInput, and UpdateUserInput classes - Created UserController with create, findAll, and removeUser methods - Updated UserEntity class with @Exclude decorator for password field - Updated UserModule to include UserController - Updated UserService to use class-transformer for mapping Prisma entities to UserEntity class - Updated UserService to return UserEntity instances instead of Prisma entities - Updated UserService to handle NotFoundException for setPassword and remove methods --- package.json | 3 ++ src/main.ts | 3 ++ src/user/dto/create-user.input.ts | 5 ++++ src/user/dto/setpassword-user.input.ts | 4 +++ src/user/dto/update-user.input.ts | 7 ++++- src/user/user.controller.ts | 26 ++++++++++++++++ src/user/user.entity.ts | 8 +++-- src/user/user.module.ts | 4 ++- src/user/user.service.ts | 41 ++++++++++++++++++++++---- yarn.lock | 34 +++++++++++++++++++++ 10 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/user/user.controller.ts diff --git a/package.json b/package.json index 31b3f5b..7b6cfa5 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,13 @@ "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", + "@nestjs/mapped-types": "^2.0.6", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.4.11", "@prisma/client": "5.22.0", "axios": "^1.7.7", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "passport": "^0.7.0", "passport-discord": "^0.1.4", "passport-jwt": "^4.0.1", diff --git a/src/main.ts b/src/main.ts index ab3999d..bb1c9e3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import { NestFactory } from "@nestjs/core"; import { AppModule } from "./app.module"; +import { ValidationPipe } from "@nestjs/common"; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -8,6 +9,8 @@ async function bootstrap() { origin: "*", }); + app.useGlobalPipes(new ValidationPipe()); + await app.listen(process.env.PORT ?? 3000, "0.0.0.0"); } bootstrap(); diff --git a/src/user/dto/create-user.input.ts b/src/user/dto/create-user.input.ts index 7d46830..234c7f3 100644 --- a/src/user/dto/create-user.input.ts +++ b/src/user/dto/create-user.input.ts @@ -1,4 +1,9 @@ +import { IsBoolean, IsString } from "class-validator"; + export class CreateUserInput { + @IsString() username: string; + + @IsBoolean() isAdmin: boolean; } diff --git a/src/user/dto/setpassword-user.input.ts b/src/user/dto/setpassword-user.input.ts index 49e8177..7da0345 100644 --- a/src/user/dto/setpassword-user.input.ts +++ b/src/user/dto/setpassword-user.input.ts @@ -1,5 +1,9 @@ +import { IsString } from "class-validator"; export class SetUserPasswordInput { + @IsString() id: string; + + @IsString() password: string; } diff --git a/src/user/dto/update-user.input.ts b/src/user/dto/update-user.input.ts index 551b779..2b2df1c 100644 --- a/src/user/dto/update-user.input.ts +++ b/src/user/dto/update-user.input.ts @@ -1,7 +1,12 @@ +import { PartialType } from "@nestjs/mapped-types"; +import { IsString } from "class-validator"; + import { CreateUserInput } from "./create-user.input"; -export class UpdateUserInput extends CreateUserInput { +export class UpdateUserInput extends PartialType(CreateUserInput) { + @IsString() id: string; + @IsString() username: string; } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts new file mode 100644 index 0000000..0f763f2 --- /dev/null +++ b/src/user/user.controller.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Delete, Get, Param, Post, UseGuards } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; + +import { CreateUserInput } from "./dto/create-user.input"; +import { UserService } from "./user.service"; + +@Controller("user") +@UseGuards(AuthGuard("jwt")) +export class UserController { + constructor(private readonly userService: UserService) {} + + @Post() + create(@Body() createUserInput: CreateUserInput) { + return this.userService.create(createUserInput); + } + + @Get() + findAll() { + return this.userService.findAll(); + } + + @Delete(":id") + removeUser(@Param("id") id: string) { + return this.userService.remove(id); + } +} diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 92b7202..f9ae7d4 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,8 +1,12 @@ +import { Exclude } from "class-transformer"; -export class User { +export class UserEntity { id: string; - + username: string; isAdmin: boolean; + + @Exclude() + password: string; } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 7eb088e..ed60dad 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -3,8 +3,10 @@ import { Module } from "@nestjs/common"; import { UserService } from "./user.service"; import { PrismaService } from "src/prisma/prisma.service"; +import { UserController } from './user.controller'; @Module({ - providers: [UserService, PrismaService] + providers: [UserService, PrismaService], + controllers: [UserController] }) export class UserModule {} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 6cce326..1120c8a 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,48 +1,77 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable, NotFoundException } from "@nestjs/common"; +import { plainToClass } from "class-transformer"; import { PrismaService } from "src/prisma/prisma.service"; + import { CreateUserInput } from "./dto/create-user.input"; import { UpdateUserInput } from "./dto/update-user.input"; import { SetUserPasswordInput } from "./dto/setpassword-user.input"; +import { UserEntity } from "./user.entity"; @Injectable() export class UserService { constructor(private readonly prisma: PrismaService) {} async create(createUserInput: CreateUserInput) { - return await this.prisma.user.create({ + const user = await this.prisma.user.create({ data: { username: createUserInput.username, isAdmin: createUserInput.isAdmin, }, }); + + return plainToClass(UserEntity, user); } async update(updateUserInput: UpdateUserInput) { - return await this.prisma.user.update({ + const user = await this.prisma.user.update({ where: { id: updateUserInput.id }, data: { username: updateUserInput.username, isAdmin: updateUserInput.isAdmin, }, }); + + return plainToClass(UserEntity, user); } async setPassword(setUserPasswordInput: SetUserPasswordInput) { - return await this.prisma.user.update({ + const exist = await this.prisma.user.findUnique({ + where: { id: setUserPasswordInput.id }, + }); + if (!exist) throw new NotFoundException("User not found"); + + const user = await this.prisma.user.update({ where: { id: setUserPasswordInput.id }, data: { password: setUserPasswordInput.password, }, }); + + return plainToClass(UserEntity, user); } async findAll() { - return await this.prisma.user.findMany(); + const users = await this.prisma.user.findMany(); + + return users.map((user) => plainToClass(UserEntity, user)); } async findOne(id: string) { - return await this.prisma.user.findUnique({ + const user = await this.prisma.user.findUnique({ where: { id }, }); + + return plainToClass(UserEntity, user); + } + + async remove(id: string) { + const exist = await this.prisma.user.findUnique({ where: { id } }); + if (!exist) throw new NotFoundException("User not found"); + + const user = await this.prisma.user.delete({ + where: { id }, + }); + + return plainToClass(UserEntity, user); } } diff --git a/yarn.lock b/yarn.lock index a690b74..593103b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -284,6 +284,11 @@ "@types/jsonwebtoken" "9.0.5" jsonwebtoken "9.0.2" +"@nestjs/mapped-types@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.6.tgz#d2d8523709fd5d872a9b9e0c38162746e2a7f44e" + integrity sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ== + "@nestjs/passport@^10.0.3": version "10.0.3" resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-10.0.3.tgz#26ec5b2167d364e04962c115fcef80d10e185367" @@ -563,6 +568,11 @@ "@types/node" "*" "@types/send" "*" +"@types/validator@^13.11.8": + version "13.12.2" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.2.tgz#760329e756e18a4aab82fc502b51ebdfebbe49f5" + integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== + "@typescript-eslint/eslint-plugin@^8.0.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz#ac56825bcdf3b392fc76a94b1315d4a162f201a6" @@ -1091,6 +1101,20 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== +class-transformer@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" + integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.10.53" + validator "^13.9.0" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2142,6 +2166,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libphonenumber-js@^1.10.53: + version "1.11.15" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz#0947ba02208cf6c44fdf3b07e097a98b3ec945f4" + integrity sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -3214,6 +3243,11 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validator@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"