[NestJS] Create in CRUD

woogiemon·2023년 3월 8일
0
post-thumbnail
async addUser(request: AddUserRequest): Promise<UserEntity> {
    const newUser = this.userRepository.create({
      email: request.email,
      password: request.password,
      name: request.name,
    });
    await this.userRepository.save(newUser);
    return newUser;
  }

TypeORM 의 create() 메서드를 이용해서 튜플을 생성했다.
참고로 repository.create() 메서드는 단순히 repository entity 의 객체를 생성하는 것이므로 async await 으로 처리할 필요는 없다.

이렇게 만들어진 데이터를 저장하기 위해서 this.userRepository.save(newUser) 을 사용하는 것이다. 여기는 DB에 접근하는 것이므로 async await 으로 비동기를 제어해줘야 한다.


회원가입, 로그인, 로그아웃과 UseGuards(JwtAuthGuard) 를 이용해 accessToken 확인하는 로직까지 완성했고, 이제 User(1) : Board(N) 관계를 가지고 로그인한 유저가 게시글을 등록하는 로직을 만들어보자.

삽질 1.

User Entity

@OneToMany(() => BoardEntity, (board) => board.writer)
boards: BoardEntity[];

Board Entity

@ManyToOne(() => UserEntity, (user) => user.boards)
writer: UserEntity;

로그인한 유저가 글을 등록하게 만들고 싶기 때문에 앞서 만든 @UseGuards(JwtAuthGuard) 를 사용했다.

Board Controller

@HttpCode(HttpStatus.CREATED)
@UseGuards(JwtAuthGuard)
@Post('/insertBoard')
async insertBoard(@Req() req: Request, @Body() request: InsertBoardRequest) {
    const decodedUser = this.jwtService.decode(req.cookies['jwt']);
    const user = await this.userService.fetchOneUser(decodedUser['id']);
    await this.boardService.insertBoard(user, request);
  }

Board Service

async insertBoard(user: UserEntity, request: InsertBoardRequest) {
    const newBoard = this.boardRepository.create({
      title: request.title,
      content: request.content,
      writer: user,
    });
    await this.boardRepository.save(newBoard);
  }

Board Controller 에서 받은 쿠키를 decode 하는 작업을 포함시켰다. 이딴식으로 코드를 짜게 되면 매번 컨트롤러를 호출할 때마다 decode 하는 중복 코드가 발생한다. 이를 해결하기 위해서 Custom Decorator 를 만들었다.

export class Payload {
  @ApiProperty()
  readonly id: number;
  @ApiProperty()
  readonly email: string;
  @ApiProperty()
  readonly name: string;
}
import {
  createParamDecorator,
  ExecutionContext,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Payload } from 'src/modules/auth/dto/payload.dto';

export const UserPayLoad = createParamDecorator(
  async (data: string, ctx: ExecutionContext) => {
    try {
      const jwtService = new JwtService({});
      const request = ctx.switchToHttp().getRequest();
      const cookie = data
        ? await request.cookies?.['jwt']
        : await request.cookies;
      console.log(cookie);
      const jwt: Payload = await jwtService.verifyAsync(cookie.access_token);
      console.log(jwt);
      return jwt;
    } catch (error) {
      throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
    }
  },
);

이랬더니, console.log(cookie) 값은 잘 나온다. 근데 뒤에서 자꾸 jwt를 내놓으라고 했고, console.log(cookie)는 jwt='액세스토큰값' 이 나오는데 왜 그럴까 생각했다.

그러다 내가 만든 cookie의 이름이 jwt 였다는게 기억났고,

@Post('/login')
async login(@Body() request: AuthenticateRequest, @Res() res: Response) {
    const user = await this.userService.getByEmail(request.email);
    this.authService.getAuthenticatedUser(request.email, request.password);
    const accessToken = await this.authService.getAccessToken(user.email);
    this.logger.debug(accessToken);
    res.setHeader('Authorization', 'Bearer ' + accessToken);
  	// 이부분!!!
    res.cookie('jwt', accessToken, {
      httpOnly: true,
      maxAge: 24 * 60 * 60 * 1000,
    });
    return res.send({
      message: 'success',
    });
}
const const jwt: Payload = await jwtService.verifyAsync(cookie.jwt)

이렇게 수정했다.

그랬더니

[Nest] 28265  - 08/03/2023, 16:18:27   ERROR [ExceptionsHandler] secret or public key must be provided
JsonWebTokenError: secret or public key must be provided
    at /Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/jsonwebtoken/verify.js:113:19
    at getSecret (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/jsonwebtoken/verify.js:97:14)
    at Object.module.exports [as verify] (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/jsonwebtoken/verify.js:101:10)
    at /Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/@nestjs/jwt/dist/jwt.service.js:43:53
    at new Promise (<anonymous>)
    at JwtService.verifyAsync (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/@nestjs/jwt/dist/jwt.service.js:43:16)
    at /Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/decorators/userPayload.decorator.ts:15:43
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

앞서 설정해놓았던 secret 키를 추가함으로써 해결했다.

완성 로직

const jwt: Payload = await jwtService.verifyAsync(cookie.jwt, {
  secret: process.env.JWT_SECRET,
});

이제 목표였던 로그인한 사용자가 게시글 올리기 를 위한 Board controller 를 수정했다.

@HttpCode(HttpStatus.CREATED)
@UseGuards(JwtAuthGuard)
@Post('/insertBoard')
async insertBoard(
  	@UserPayLoad() payload: Payload,
    @Body() request: InsertBoardRequest,
  ) {
    const user = await this.userService.fetchOneUser(payload.id);
    await this.boardService.insertBoard(user, request);
  }
}	

삽질 2.

Entity의 Column 개수보다 부족한 수의 인자를 save 할 때,

[Nest] 31357  - 15/03/2023, 03:53:40   ERROR [ExceptionsHandler] null value in column "STATUS" of relation "PRODUCT_REQ_LIST" violates not-null constraint
QueryFailedError: null value in column "STATUS" of relation "PRODUCT_REQ_LIST" violates not-null constraint
    at PostgresQueryRunner.query (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/driver/postgres/PostgresQueryRunner.ts:299:19)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at InsertQueryBuilder.execute (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/query-builder/InsertQueryBuilder.ts:163:33)
    at SubjectExecutor.executeInsertOperations (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/persistence/SubjectExecutor.ts:428:42)
    at SubjectExecutor.execute (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/persistence/SubjectExecutor.ts:137:9)
    at EntityPersistExecutor.execute (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/persistence/EntityPersistExecutor.ts:197:21)
    at ProductReqListService.insertProductReqList (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/modules/productReqList/services/productReqList.service.ts:30:12)
    at UserService.requestProduct (/Users/jin/Desktop/JIN/STUDY/solo-study/board-project/src/modules/user/services/user.service.ts:250:12)
    at /Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/@nestjs/core/router/router-execution-context.js:46:28
    at /Users/jin/Desktop/JIN/STUDY/solo-study/board-project/node_modules/@nestjs/core/router/router-proxy.js:9:17

이런 에러메세지를 만났다. Status Column 이 null 값이라 PRODUCT_REQ_LIST 테이블이 not-null을 위반한다는 말이다.

productReqList.entity.ts

@Entity('PRODUCT_REQ_LIST')
export class ProductReqListEntity {
  @PrimaryGeneratedColumn({ name: 'PRODUCT_REQ_LIST_ID' })
  @Generated('increment')
  id: number;

  @Column({ name: 'PRODUCT_NAME' })
  productName: string;

  @Column({ name: 'PRICE' })
  price: number;

  @Column({ name: 'STATUS' })
  status: string;

  @Column({ name: 'REASON' })
  reason: string;

  @ManyToOne(() => UserEntity, (user) => user.productReqList)
  @JoinColumn({ name: 'USER_ID' })
  user: UserEntity;

  @ManyToOne(() => EmployeeEntity, (employee) => employee.productReqList)
  @JoinColumn({ name: 'EMPLOYEE_ID' })
  employee: EmployeeEntity;

  @ManyToOne(() => BrandEntity, (brand) => brand.productReqList)
  @JoinColumn({ name: 'BRAND_ID' })
  brand: BrandEntity;
}

찾아보니 default 값을 넣어줌으로써 null이 아니게 만들면 되더라.

@Column({ name: 'STATUS', default: '' })
status: string;

@Column({ name: 'REASON', default: '' })
reason: string;

해결!!

profile
삽질 기록..

0개의 댓글