일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- C++
- thymeleaf
- Nodejs
- 시스템호출
- 파이썬
- python
- git
- C언어
- 코딩테스트
- Spring
- 코테
- TypeORM
- nestjs typeorm
- @Autowired
- 카카오 알고리즘
- OpenCV
- 스프링
- 카카오
- 가상면접사례로배우는대규모시스템설계기초
- nestjs auth
- 구조체배열
- AWS
- 카카오 코테
- 알고리즘
- spring boot
- 컴포넌트스캔
- 해시
- nestJS
- 프로그래머스
- @Component
- Today
- Total
공부 기록장 💻
[NestJS/오세유] Recruitment Entity 생성 및 구인글 등록 기능 및 Auth 인증 절차 추가와 User 정보 포함 본문
[NestJS/오세유] Recruitment Entity 생성 및 구인글 등록 기능 및 Auth 인증 절차 추가와 User 정보 포함
dream_for 2022. 8. 31. 20:32NestJS 프레임워크에서 구인글 등록하는 Recruitment Post 기능을 만들어보자.
이전에 작성했던 회원가입 기능 - 새로운 유저 등록 (https://dream-and-develop.tistory.com/197) 부분과 동일한 방식으로 구현을 하였다.
또한 지난 시간에 Recruitment 모델을 만든 것을 바탕으로 서비스와 컨트롤러를 작성해보자. (https://dream-and-develop.tistory.com/208)
우선 recruit의 전체 디렉터리의 구조는 다음과 같다.
Recruit Repository
우선 recruit.repository.ts 는 다음과 같이 작성해주자.
이전에 User Repository 작성했던 것과 동일하게, typeorm 최신 버전에서 사라진 EntityRepository 대신
새로운 typeorm-ex.decorator과 typeorm-ex.module에서 만든 CustomRepository를 데코레이터로 사용해주자.
import { CustomRepository } from "src/db/typeorm-ex.module";
import { Repository } from "typeorm";
import { Recruitment } from "./entity/recruit.entity";
@CustomRepository(Recruitment)
export class RecruitRepository extends Repository<Recruitment>{}
Recruit Service
recruit.service.ts 에서는 다음과 같은 함수들을 작성해주었다.
createPost() : 신규 구인글 등록
getAllPosts() : 전체 구인글 목록 가져오기
getPostById() : 구인글 객체 ID로 가져오기
deletePost() : 구인글 객체 삭제하기
서비스 부분에는 Recruit Repository를 사용하기 위해 생성자 부분에 주입시키고,
다음과 같이 구현을 해주었다.
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { RecruitDTO } from './dto/recruit.dto';
import { Recruitment } from '../domain/recruit.entity';
import { RecruitRepository } from './recruit.repository';
import { User } from 'src/domain/user.entity';
@Injectable()
export class RecruitService {
constructor(
@InjectRepository(RecruitRepository)
private recruitRepository:RecruitRepository,
){}
// 신규 구인글 등록
async createPost(recruitDTO: RecruitDTO): Promise<RecruitDTO | undefined>{
return await this.recruitRepository.save(recruitDTO);
}
// 전체 구인글 목록 가져오기
async getAllPosts():Promise<Recruitment[]>{
return this.recruitRepository.find();
}
// ID로 구인글 객체 가져오기
async getPostById(post_id): Promise<Recruitment>{
const found = await this.recruitRepository.findOne({where: {post_id:post_id}});
if(!found){
throw new NotFoundException(`해당 게시글을 찾을 수 없습니다.`)
}
return found;
}
// 해당 ID 구인글 삭제
async deletePost(post_id, user) : Promise<void> {
const result = await this.recruitRepository.delete({post_id, user});
if(result.affected === 0){
throw new NotFoundException(`ID(${post_id})의 게시글을 찾을 수 없습니다.`);
}
}
}
Recruit Controller
API endpoints 를 바탕으로 controller 부분을 작성해보자.
우선 수정 부분은 제외하고,
구인글 전체 목록, 구인글 등록, 구인글 상세보기, 구인글 삭제 부분을 구현해보자.
각각 위의 Recruit Service 에서 작성한 createPost(), getAllPosts(), getPostById(), deletePost() 함수를 호출하여
Post, Get, Delete 요청을 하게 된다.
import { Controller, Param, Body, Post, Res, Req, Get, Delete, UseGuards } from '@nestjs/common';
import { RecruitDTO } from './dto/recruit.dto';
import { RecruitService } from './recruit.service';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport';
import { Recruitment } from '../domain/recruit.entity';
import { GetUser } from 'src/auth/get-user.decorator';
import { Logger } from '@nestjs/common/services';
import { User } from 'src/domain/user.entity';
import { Response } from 'express';
@Controller('recruit')
@UseGuards(AuthGuard())
export class RecruitController {
private logger = new Logger('Recruitment');
constructor(private recruitService:RecruitService){}
// 새로운 구인글 등록
@Post('/new')
async newPost(
@Req() req:Request, @Body() recruitDTO: RecruitDTO): Promise<RecruitDTO>{
return await this.recruitService.createPost(recruitDTO);
}
// 모든 구인글 목록 가져오기
@Get('/recruitment-lists')
async getAllPosts(): Promise<Recruitment[]>{
return this.recruitService.getAllPosts();
}
// ID로 구인글 가져오기
@Get('/:post_id')
getPostById(@Param('post_id') post_id:number) : Promise<Recruitment>{
return this.recruitService.getPostById(post_id);
}
// ID로 구인글 삭제하기
@Delete('/:post_id')
deleteBoard(@Param('post_id') post_id:number,
@GetUser() user: User
): Promise<void>{
this.logger.verbose(`유저 ${user.name}이 ID(${post_id})를 삭제합니다.`);
return this.recruitService.deletePost(post_id, user);
}
}
인증 권한 부여
이제는 인증된 유저만 게시물을 등록하고 조회할 수 있도록 만들어보자.
Recruit Module
우선 Recruit Module 에서 Auth Module의 Auth Guard를 사용할 수 있도록
imports 부분에 AuthModule을 추가해주자.
// recruit/recruit.module.ts
@Module({
imports: [
/* */
AuthModule
],
/* */
})
Recruit Controller
다음은 recruit.controller.ts에 AuthGuard()를 controller-level로 추가해주자.
이전에 만들어둔 AuthGuard() 를 UseGuards 데코레이터에 주입함으로써
게시물 생성 및 조회, 삭제 시 인증된 유저만 가능하도록 설정하도록 한다.
import { UseGuards } from '@nestjs/common';
@Controller('recruit')
@UseGuards(AuthGuard())
export class RecruitController {
/* */
}
게시물 생성 시 유저 정보 포함하기
이전에 User과 Recruit 의 One-To-Many Relations 관계를 엔티티 내부에 포함시켰으므로,
이제 게시물 생성 시 유저 정보를 게시물에 포함시키는 기능을 만들어보자.
게시물 생성 요청을 하게 되면, 헤더 안에 있는 토큰으로 유저 정보를 얻고,
유저 정보와 게시물 관계를 형성하여 게시물을 생성하게 되는 구조이다.
Get-User Decorator
우선 auth/get-user.decorator.ts 파일을 생성하고 다음과 같이 작성하자.
User의 정보를 Parameter로 전달할 수 있도록 새로운 Decorator을 만드는 과정이다.
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
import { User } from "./entity/user.entity";
export const GetUser = createParamDecorator((data, ctx: ExecutionContext): User => {
const req = ctx.switchToHttp().getRequest();
return req.user;
})
Recruit Repository
이제 새로운 구인글 등록을 할 때, User 의 정보를 추가하기 위해
RecruitRepository에 createPost 함수를 새로 만들어 구조를 이전과 다르게 변경해보자.
createPost 함수의 인수에는 RecruitDTO와 더불어, User 객체를 포함시킨다.
기존 recruitDTO에는 Recruitment 에 필요한 칼럼들을 모두 포함시키고,
post 객체를 만들어, 마지막에 user을 포함하여 이를 저장하고 리턴하도록 하자.
import { User } from "src/auth/entity/user.entity";
import { CustomRepository } from "src/db/typeorm-ex.module";
import { Repository } from "typeorm";
import { RecruitDTO } from "./dto/recruit.dto";
import { Recruitment } from "./entity/recruit.entity";
@CustomRepository(Recruitment)
export class RecruitRepository extends Repository<Recruitment>{
async createPost(recruitDTO:RecruitDTO, user:User) : Promise<Recruitment>{
const {
work_name,
image,
address,
detailed_address,
district,
start_date,
end_date,
start_time,
end_time,
days_of_work,
num_of_people,
daily_wage,
recommended_lodging,
meals_offered,
trans_offered,
contents,
tags,
} = recruitDTO;
const post = this.create({
work_name,
image,
address,
detailed_address,
district,
start_date,
end_date,
start_time,
end_time,
days_of_work,
num_of_people,
daily_wage,
recommended_lodging,
meals_offered,
trans_offered,
contents,
tags,
user
})
await this.save(post);
return post;
}
}
Recruit Service
Recruit Service에서는 생성자 constructor 부분에 RecruitRepository를 주입시켜준 후,
createPost 함수에 인수와 리턴 값에 user을 추가해주자.
import { User } from 'src/auth/entity/user.entity';
import { UserRepository } from 'src/auth/user.repository';
/* */
export class RecruitService {
constructor(
@InjectRepository(RecruitRepository)
private recruitRepository:RecruitRepository
){}export class RecruitService {
/* */
// 신규 구인글 등록
async createPost(recruitDTO: RecruitDTO, user:User): Promise<RecruitDTO | undefined>{
return await this.recruitRepository.createPost(recruitDTO, user);
}
/* */
}
Recruit Controller
recruit.controller.ts 파일의 새로운 구인글을 등록하는 newPost 함수의 인수에
위에서 만든 @GetUser() 데코레이터를 포함시켜 유저 객체를 가져오도록 하고,
객체를 리턴할 때, recruitDTO와 함께 user을 함께 반환하도록 하자.
/* */
import { User } from 'src/auth/entity/user.entity';
import { GetUser } from 'src/auth/get-user.decorator';
export class RecruitController {
/* */
// 새로운 구인글 등록
@Post('/new')
async newPost(
@Req() req:Request, @Body() recruitDTO: RecruitDTO, @GetUser() user:User): Promise<RecruitDTO>{
return await this.recruitService.createPost(recruitDTO, user);
}
/* */
}
Postman을 이용한 API 검증
postman을 이용해 각각의 컨트롤러에서 작성한 API 검증을 해보도록 하자.
signup
Body 에 user_id, password, name, phone, image 칼럼에 각각의 데이터를 추가하여 json 형태로 POST 요청 전송을 한다.
pk인 id 값이 새로 부여되었고, user_created 에 날짜 데이터가 추가되어 Response 값으로 유저 객체가 전달됨을 확인할 수 있다.
login
user_id와 password를 이용해 로그인을 해보자.
알맞지 않은 password인 경우 로그인 실패를 하게 되고,
올바르게 로그인한 경우 accessToken이 발급된다.
mypage
발급받은 accessToken으로 마이페이지에 GET 요청을 보내보자.
recruits 배열을 칼럼에 포함한, 현재 로그인된 user 객체가 Response로 반환됨을 확인할 수 있다.
logout
accessToken으로 로그아웃 POST 요청을 하면, OK 메세지와 함께 로그아웃 완료가 된다.
Decoding issued JWT Token
발급받은 accessToken을 https://jwt.io/ 을 이용해 디코딩해보자.
header, payload, signature 부분에 토큰을 디코딩한 결과가 각각 나타나는 것을 확인할 수 있다.
Recruitment Test
이제 Recruit 모듈에 대한 API를 검증해보도록 하자.
구인글 등록 new
먼저 새로운 구인글을 등록하는 new API를 검증해보자.
첫번째 케이스는, district가 enum에 포함되지 않은 상수를 사용하는 경우이다.
"충청남도" , "충청북도"는 포함되어 있지만, 다음과 같이 "충청도"를 입력하는 경우 문제가 발생한다.
Data truncated for column 'district' at row 1 이라는 error message가 뜬다.
mysql db에 올바르지 못한 데이터 타입으로 인해 query가 failed 한 것이다.
두번째 케이스는, boolean type 을 잘못 지정하는 경우이다.
이번에는 district value로 "충청남도"를 사용하여 올바르게 타입을 매칭시키고,
trans_offered에 0, 1 이 아닌 string 값 "False" value를 지정했을때 발생하는 문제를 확인해보자.
다음과 같이 Incorrect integer value: 'False' for column 'trans_offrered' at row 1 라는 메세지가 전달되며
친절하게 데이터 타입이 잘못되었음을 알려주는 QueryFailedError 가 발생한다.
typeORM에서 boolean 타입의 값이 nestJS에서 mysql로 전달될 때, tiny integer value로 매핑되므로 0, 1의 값이 지정되어야 한다.
이번에는 trans_offered에 0이라는 올바른 integer value를 지정하여 전송해보자.
별다른 에러 발생 없이 올바르게 POST 요청이 전송되어, response로 recruitment 객체를 전달한 것을 확인할 수 있다.
mysql 서버에서도 객체가 잘 저장되었는지 확인해보자.
마지막 칼럼에 userId 가 추가되어, 유저 정보가 같이 저장되었음을 확인할 수 있다.
recruitment-lists
GET 요청을 보내 등록된 전체 구인글을 확인해보자.
위에서 저장한 post_id 1번의 객체가 나타남을 확인할 수 있다.
(recruitment-lists부분에서 user 정보까지 추가되어야 하므로, 수정이 필요하다.)
Get by ID - ID값으로 recruitment 객체 조회
recruit/1 에 GET 요청을 보내보자.
post_id 값이 1인 구인글 객체가 다음과 같이 나타남을 확인할 수 있다.
구인글 삭제 - Delete
recruit/1 로 DELETE 요청을 보내보자.
local 서버 터미널 창에 다음과 같이 로그가 나타난다.
어떤 user가 어떤 post_id 값의 구인글을 삭제했는지 확인할 수 있다.
MyPage, Profile
마이페이지와 프로필에 GET 요청을 보내보자.
mypage는 user 객체 전체를 전달하고, 새로 추가한 profile에서는 user의 user_id, name, image, phone 칼럼 부분만 전달한다.
mypage에서 해당 user가 생성한 recruit 객체들을 담은 recruits 배열을 포함하여 DTO에 포함된 전체 데이터를 반환함을 확인할 수 있다.
[참고자료]
https://www.youtube.com/watch?v=3JminDpCJNE
'# Develop > Project' 카테고리의 다른 글
[NestJS/오세유] 오세유 프로젝트 진행 상황과 팀 회의 회고 1 (0) | 2022.09.09 |
---|---|
[Git/Github] Github Actions를 이용한 Workflow 자동화와 CI/CD 파이프라인 구축하기 / eslint 적용하기 , "module is not defined" 에러 해결 과정 (0) | 2022.09.09 |
[NestJS/오세유] User, Recruitment Entity / Relations 생성 (0) | 2022.08.30 |
[NestJS/오세유] NestJS 오세유 백엔드 프로젝트 시작 (0) | 2022.08.30 |
[Python API] API 실습 프로젝트 (0) | 2021.05.22 |