From c0ca314d8e9c6599c7fca807d80db84f372b8456 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 12:12:24 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat(#144):=20my-info=EC=9D=98=20owner?= =?UTF-8?q?=EC=9D=98=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/controllers/user.controller.ts | 17 +++++++++++- src/users/services/user.service.ts | 34 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 1007045..08c7371 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -1,4 +1,10 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; +import { + Controller, + Get, + Param, + ParseIntPipe, + UseGuards, +} from '@nestjs/common'; import { UserService } from '../services/user.service'; import { ApiTags } from '@nestjs/swagger'; import { JwtAccessTokenGuard } from 'src/config/guards/jwt-access-token.guard'; @@ -16,4 +22,13 @@ export class UserController { async getMyInfo(@GetUserId() userId: number) { return this.userService.getMyInfo(userId); } + + @UseGuards(JwtAccessTokenGuard) + @Get('my-info/:targetId') + async getMyInfoAndOwner( + @GetUserId() userId: number, + @Param('targetId', ParseIntPipe) targetId: number, + ) { + return this.userService.checkMyInfoOwner(userId, targetId); + } } diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 3b92bb7..3d4b672 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -24,4 +24,38 @@ export class UserService { userImage, }; } + + async checkMyInfoOwner(userId: number, targetId: number) { + if (userId === targetId) { + const { name, email, gender, admin, provider } = + await this.userRepository.getUserInfo(userId); + const userImage = (await this.userImageRepository.checkUserImage(userId)) + .imageUrl; + return { + userId, + name, + email, + gender, + admin, + provider, + userImage, + owner: true, + }; + } + + const { name, email, gender, admin, provider } = + await this.userRepository.getUserInfo(targetId); + const userImage = (await this.userImageRepository.checkUserImage(targetId)) + .imageUrl; + return { + userId, + name, + email, + gender, + admin, + provider, + userImage, + owner: false, + }; + } } From 6ae2fce86cf14da3fc0471e32a2820e9b49e0a45 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 13:11:35 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat(#144):=20swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/controllers/user.controller.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 08c7371..7862354 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -6,7 +6,7 @@ import { UseGuards, } from '@nestjs/common'; import { UserService } from '../services/user.service'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiHeader, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { JwtAccessTokenGuard } from 'src/config/guards/jwt-access-token.guard'; import { GetUserId } from 'src/common/decorators/get-userId.decorator'; import { ApiGetMyInfo } from '../swagger-decorators/get-my-info-decorator'; @@ -24,6 +24,17 @@ export class UserController { } @UseGuards(JwtAccessTokenGuard) + @ApiParam({ name: 'targetId', example: 1, required: true }) + @ApiHeader({ + name: 'access_token', + example: '액세스 토큰값', + required: true, + }) + @ApiOperation({ + summary: '기존 my-info + owner', + description: + '유저의 액세스 토큰의 id와 조회하려는 유저의 id를 비교해서 owner 여부를 포함해서 알려줌', + }) @Get('my-info/:targetId') async getMyInfoAndOwner( @GetUserId() userId: number, From a549200166a503dc6e13e0ca06a469559dfee4c7 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 16:00:19 +0900 Subject: [PATCH 03/11] =?UTF-8?q?modify(#144):=20if=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20owner=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=20?= =?UTF-8?q?=EB=8B=B4=EC=9D=84=20=EB=95=8C=20true/false=20=ED=8C=90?= =?UTF-8?q?=EB=B3=84=ED=95=B4=EC=84=9C=20return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/services/user.service.ts | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 3d4b672..03c98bf 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -26,26 +26,9 @@ export class UserService { } async checkMyInfoOwner(userId: number, targetId: number) { - if (userId === targetId) { - const { name, email, gender, admin, provider } = - await this.userRepository.getUserInfo(userId); - const userImage = (await this.userImageRepository.checkUserImage(userId)) - .imageUrl; - return { - userId, - name, - email, - gender, - admin, - provider, - userImage, - owner: true, - }; - } - const { name, email, gender, admin, provider } = - await this.userRepository.getUserInfo(targetId); - const userImage = (await this.userImageRepository.checkUserImage(targetId)) + await this.userRepository.getUserInfo(userId); + const userImage = (await this.userImageRepository.checkUserImage(userId)) .imageUrl; return { userId, @@ -55,7 +38,7 @@ export class UserService { admin, provider, userImage, - owner: false, + owner: userId === targetId, }; } } From 944d6ca8e56f625ba882548736bf8f9fc6f92773 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 16:01:24 +0900 Subject: [PATCH 04/11] refactor(#144): change method name --- src/users/controllers/user.controller.ts | 4 ++-- src/users/services/user.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 7862354..b901467 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -36,10 +36,10 @@ export class UserController { '유저의 액세스 토큰의 id와 조회하려는 유저의 id를 비교해서 owner 여부를 포함해서 알려줌', }) @Get('my-info/:targetId') - async getMyInfoAndOwner( + async getMyInfoWithOwner( @GetUserId() userId: number, @Param('targetId', ParseIntPipe) targetId: number, ) { - return this.userService.checkMyInfoOwner(userId, targetId); + return this.userService.getMyInfoWithOwner(userId, targetId); } } diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 03c98bf..db1a6a2 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -25,7 +25,7 @@ export class UserService { }; } - async checkMyInfoOwner(userId: number, targetId: number) { + async getMyInfoWithOwner(userId: number, targetId: number) { const { name, email, gender, admin, provider } = await this.userRepository.getUserInfo(userId); const userImage = (await this.userImageRepository.checkUserImage(userId)) From 23e346f355035f9208aceb3e3677a7c8a8b38466 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 16:04:39 +0900 Subject: [PATCH 05/11] refactor(#144): swagger custom decorator --- src/users/controllers/user.controller.ts | 13 +---- .../get-my-info-with-owner-decorator.ts | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/users/swagger-decorators/get-my-info-with-owner-decorator.ts diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index b901467..27613ff 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -10,6 +10,7 @@ import { ApiHeader, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { JwtAccessTokenGuard } from 'src/config/guards/jwt-access-token.guard'; import { GetUserId } from 'src/common/decorators/get-userId.decorator'; import { ApiGetMyInfo } from '../swagger-decorators/get-my-info-decorator'; +import { ApiGetMyInfoWithOwner } from '../swagger-decorators/get-my-info-with-owner-decorator'; @Controller('user') @ApiTags('user API') @@ -24,17 +25,7 @@ export class UserController { } @UseGuards(JwtAccessTokenGuard) - @ApiParam({ name: 'targetId', example: 1, required: true }) - @ApiHeader({ - name: 'access_token', - example: '액세스 토큰값', - required: true, - }) - @ApiOperation({ - summary: '기존 my-info + owner', - description: - '유저의 액세스 토큰의 id와 조회하려는 유저의 id를 비교해서 owner 여부를 포함해서 알려줌', - }) + @ApiGetMyInfoWithOwner() @Get('my-info/:targetId') async getMyInfoWithOwner( @GetUserId() userId: number, diff --git a/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts b/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts new file mode 100644 index 0000000..7369ea8 --- /dev/null +++ b/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts @@ -0,0 +1,57 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiHeaders, + ApiOperation, + ApiParam, + ApiResponse, +} from '@nestjs/swagger'; + +export function ApiGetMyInfoWithOwner() { + return applyDecorators( + ApiOperation({ + summary: '내 정보 조회 API(owner 여부와 함께)', + description: '내 정보 조회 API(owner 여부와 함께)', + }), + ApiResponse({ + status: 200, + description: '성공적으로 내 정보를 조회한 경우', + content: { + JSON: { + example: { + userId: 62, + name: '박준혁', + email: 'pjh_2004@naver.com', + gender: 'M', + admin: false, + provider: 'kakao', + userImage: + 'http://k.kakaocdn.net/dn/bgfjbT/btrNZpdv3sK/AMb1oWdaF6WxMEXkuKRkR0/img_640x640.jpg', + owner: false, + }, + }, + }, + }), + ApiResponse({ + status: 404, + description: '내 정보를 찾을 수 없는 경우', + content: { + JSON: { + example: { + statusCode: 404, + message: '사용자를 찾을 수 없습니다.', + error: 'Not Found', + }, + }, + }, + }), + ApiHeaders([ + { + name: 'access_token', + description: '액세스 토큰', + required: true, + example: '여기에 액세스 토큰', + }, + ]), + ApiParam({ name: 'targetId', example: 1, required: true }), + ); +} From def16ed163e99670d1de0ffc277fc0be7e778425 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Wed, 22 Nov 2023 16:12:27 +0900 Subject: [PATCH 06/11] refactor(#144): modify swagger response data --- src/users/swagger-decorators/get-my-info-decorator.ts | 9 ++++----- .../get-my-info-with-owner-decorator.ts | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/users/swagger-decorators/get-my-info-decorator.ts b/src/users/swagger-decorators/get-my-info-decorator.ts index b9af4ac..66d800f 100644 --- a/src/users/swagger-decorators/get-my-info-decorator.ts +++ b/src/users/swagger-decorators/get-my-info-decorator.ts @@ -13,14 +13,13 @@ export function ApiGetMyInfo() { content: { JSON: { example: { - userId: 62, - name: '박준혁', - email: 'pjh_2004@naver.com', + userId: 1, + name: '홍길동', + email: 'abcd@naver.com', gender: 'M', admin: false, provider: 'kakao', - userImage: - 'http://k.kakaocdn.net/dn/bgfjbT/btrNZpdv3sK/AMb1oWdaF6WxMEXkuKRkR0/img_640x640.jpg', + userImage: 'http://img.jpg', }, }, }, diff --git a/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts b/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts index 7369ea8..0b8e065 100644 --- a/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts +++ b/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts @@ -18,14 +18,13 @@ export function ApiGetMyInfoWithOwner() { content: { JSON: { example: { - userId: 62, - name: '박준혁', - email: 'pjh_2004@naver.com', + userId: 1, + name: '홍길동', + email: 'abcd@naver.com', gender: 'M', admin: false, provider: 'kakao', - userImage: - 'http://k.kakaocdn.net/dn/bgfjbT/btrNZpdv3sK/AMb1oWdaF6WxMEXkuKRkR0/img_640x640.jpg', + userImage: 'http://img.jpg', owner: false, }, }, From c37d55087f81f2f57270306b4134972c2d3d419c Mon Sep 17 00:00:00 2001 From: NicoDora Date: Thu, 23 Nov 2023 14:11:15 +0900 Subject: [PATCH 07/11] =?UTF-8?q?test(#10)=20:=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=B4=20log=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/services/auth.service.ts | 122 +++++++++++++----- src/common/decorators/get-userId.decorator.ts | 6 +- src/config/guards/jwt-access-token.guard.ts | 2 +- src/users/services/user.service.ts | 8 +- 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 1e3e441..ea21e2b 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -13,7 +13,7 @@ export class AuthService { private readonly userRepository: UserRepository, private readonly userImageRepository: UserImageRepository, private readonly tokenService: TokenService, - ) {} + ) {} async naverLogin(authorizeCode: string) { try { @@ -32,11 +32,13 @@ export class AuthService { redirect_uri: process.env.NAVER_CALLBACK_URL, }; - const naverToken = (await axios.post(naverTokenUrl, naverTokenBody, naverTokenHeader)).data; + const naverToken = ( + await axios.post(naverTokenUrl, naverTokenBody, naverTokenHeader) + ).data; const naverAccessToken = naverToken.access_token; const naverRefreshToken = naverToken.refresh_token; - + const naverUserInfoUrl = 'https://openapi.naver.com/v1/nid/me'; const naverUserInfoHeader = { headers: { @@ -44,7 +46,9 @@ export class AuthService { }, }; - const naverUserInfo = (await axios.get(naverUserInfoUrl, naverUserInfoHeader)).data; + const naverUserInfo = ( + await axios.get(naverUserInfoUrl, naverUserInfoHeader) + ).data; const nickname = naverUserInfo.response.nickname; const email = naverUserInfo.response.email; const profileImage = naverUserInfo.response.profile_image; @@ -58,25 +62,33 @@ export class AuthService { }; const checkUser = await this.userRepository.findUser(email, provider); - if (checkUser) { // 이미 존재하는 사용자인 경우 + if (checkUser) { + // 이미 존재하는 사용자인 경우 const userId = checkUser.id; await this.userRepository.updateUserName(userId, nickname); // 이름 업데이트 - const userImage = (await this.userImageRepository.checkUserImage(userId)).imageUrl; // DB 이미지 + const userImage = ( + await this.userImageRepository.checkUserImage(userId) + ).imageUrl; // DB 이미지 const imageUrlParts = userImage.split('/'); const dbImageProvider = imageUrlParts[imageUrlParts.length - 2]; // 이미지 제공자 이름 - if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { // S3에 업로드된 이미지가 아닌 경우 + if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { + // S3에 업로드된 이미지가 아닌 경우 await this.userImageRepository.updateUserImage(userId, profileImage); // DB에 이미지 URL 업데이트 } return { userId, naverAccessToken, naverRefreshToken }; - } else { // 존재하지 않는 사용자인 경우 + } else { + // 존재하지 않는 사용자인 경우 const newUser = await this.userRepository.createUser(userInfo); const userId = newUser.id; if (!profileImage) { - await this.userImageRepository.uploadUserImage(userId, process.env.DEFAULT_USER_IMAGE); + await this.userImageRepository.uploadUserImage( + userId, + process.env.DEFAULT_USER_IMAGE, + ); } else { await this.userImageRepository.uploadUserImage(userId, profileImage); } @@ -84,7 +96,10 @@ export class AuthService { } } catch (error) { if (error.response.status == 401) { - throw new HttpException('유효하지 않은 인가코드입니다.', HttpStatus.UNAUTHORIZED); + throw new HttpException( + '유효하지 않은 인가코드입니다.', + HttpStatus.UNAUTHORIZED, + ); } } } @@ -104,7 +119,9 @@ export class AuthService { code: authorizeCode, }; - const kakaoToken = (await axios.post(kakaoTokenUrl, kakaoTokenBody, kakaoTokenHeader)).data; + const kakaoToken = ( + await axios.post(kakaoTokenUrl, kakaoTokenBody, kakaoTokenHeader) + ).data; const kakaoAccessToken = kakaoToken.access_token; const kakaoRefreshToken = kakaoToken.refresh_token; @@ -116,7 +133,9 @@ export class AuthService { }, }; - const kakaoUserInfo = (await axios.get(kakaoUserInfoUrl, kakaoUserInfoHeader)).data; + const kakaoUserInfo = ( + await axios.get(kakaoUserInfoUrl, kakaoUserInfoHeader) + ).data; const nickname = kakaoUserInfo.properties.nickname; const email = kakaoUserInfo.kakao_account.email; const profileImage = kakaoUserInfo.properties.profile_image; @@ -127,28 +146,36 @@ export class AuthService { nickname, email, gender, - } + }; const checkUser = await this.userRepository.findUser(email, provider); - if (checkUser) { // 이미 존재하는 사용자인 경우 + if (checkUser) { + // 이미 존재하는 사용자인 경우 const userId = checkUser.id; await this.userRepository.updateUserName(userId, nickname); // 이름 업데이트 - - const userImage = (await this.userImageRepository.checkUserImage(userId)).imageUrl; // DB 이미지 + + const userImage = ( + await this.userImageRepository.checkUserImage(userId) + ).imageUrl; // DB 이미지 const imageUrlParts = userImage.split('/'); const dbImageProvider = imageUrlParts[imageUrlParts.length - 2]; // 이미지 제공자 이름 - if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { // S3에 업로드된 이미지가 아닌 경우 + if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { + // S3에 업로드된 이미지가 아닌 경우 await this.userImageRepository.updateUserImage(userId, profileImage); // DB에 이미지 URL 업데이트 } return { userId, kakaoAccessToken, kakaoRefreshToken }; - } else { // 존재하지 않는 사용자인 경우 + } else { + // 존재하지 않는 사용자인 경우 const newUser = await this.userRepository.createUser(userInfo); const userId = newUser.id; if (!profileImage) { - await this.userImageRepository.uploadUserImage(userId, process.env.DEFAULT_USER_IMAGE); + await this.userImageRepository.uploadUserImage( + userId, + process.env.DEFAULT_USER_IMAGE, + ); } else { await this.userImageRepository.uploadUserImage(userId, profileImage); } @@ -156,16 +183,27 @@ export class AuthService { } } catch (error) { if (error.response.status == 400) { - throw new HttpException('유효하지 않은 인가코드입니다.', HttpStatus.UNAUTHORIZED); + throw new HttpException( + '유효하지 않은 인가코드입니다.', + HttpStatus.UNAUTHORIZED, + ); + } else { + console.log(error); + throw new HttpException( + '카카오 로그인 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } async kakaoLogout(accessToken: string, refreshToken: string) { try { - const checkValidKakaoToken = await this.tokenService.checkValidKakaoToken(accessToken); + const checkValidKakaoToken = + await this.tokenService.checkValidKakaoToken(accessToken); if (checkValidKakaoToken === 401) { - const newKakaoToken = await this.tokenService.getNewKakaoToken(refreshToken); + const newKakaoToken = + await this.tokenService.getNewKakaoToken(refreshToken); accessToken = newKakaoToken.access_token; } @@ -177,18 +215,23 @@ export class AuthService { }; axios.post(kakaoLogoutUrl, {}, kakaoLogoutHeader); - return { message: "카카오 로그아웃이 완료되었습니다." }; + return { message: '카카오 로그아웃이 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('카카오 로그아웃 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '카카오 로그아웃 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async kakaoUnlink(accessToken: string, refreshToken: string) { try { - const checkValidKakaoToken = await this.tokenService.checkValidKakaoToken(accessToken); + const checkValidKakaoToken = + await this.tokenService.checkValidKakaoToken(accessToken); if (checkValidKakaoToken === 401) { - const newKakaoToken = await this.tokenService.getNewKakaoToken(refreshToken); + const newKakaoToken = + await this.tokenService.getNewKakaoToken(refreshToken); accessToken = newKakaoToken.access_token; } @@ -200,18 +243,23 @@ export class AuthService { }; axios.post(kakaoUnlinkUrl, {}, kakaoUnlinkHeader); - return { message: "카카오 연결 끊기가 완료되었습니다." }; + return { message: '카카오 연결 끊기가 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('카카오 연결 끊기 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '카카오 연결 끊기 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async naverUnlink(accessToken: string, refreshToken: string) { try { - const checkValidNaverToken = await this.tokenService.checkValidNaverToken(accessToken); + const checkValidNaverToken = + await this.tokenService.checkValidNaverToken(accessToken); if (checkValidNaverToken === 401) { - const newNaverToken = await this.tokenService.getNewNaverToken(refreshToken); + const newNaverToken = + await this.tokenService.getNewNaverToken(refreshToken); accessToken = newNaverToken.access_token; } @@ -229,18 +277,24 @@ export class AuthService { }; axios.post(naverUnlinkUrl, naverUnlinkBody, naverUnlinkHeader); - return { message: "네이버 연동 해제가 완료되었습니다." }; + return { message: '네이버 연동 해제가 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('네이버 연결 끊기 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '네이버 연결 끊기 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async accountDelete(userId: number) { const deleteUser = await this.userRepository.deleteUser(userId); if (!deleteUser) { - throw new HttpException('사용자를 찾을 수 없습니다.', HttpStatus.NOT_FOUND); + throw new HttpException( + '사용자를 찾을 수 없습니다.', + HttpStatus.NOT_FOUND, + ); } - return { message: "사용자 계정 삭제가 완료되었습니다." }; + return { message: '사용자 계정 삭제가 완료되었습니다.' }; } } diff --git a/src/common/decorators/get-userId.decorator.ts b/src/common/decorators/get-userId.decorator.ts index c8aec4a..3160022 100644 --- a/src/common/decorators/get-userId.decorator.ts +++ b/src/common/decorators/get-userId.decorator.ts @@ -3,7 +3,9 @@ import { ExecutionContext, createParamDecorator } from '@nestjs/common'; export const GetUserId = createParamDecorator( (data, ctx: ExecutionContext): number => { const req = ctx.switchToHttp().getRequest(); - + if (!req.user) { + return null; + } return req.user.userId; }, -); \ No newline at end of file +); diff --git a/src/config/guards/jwt-access-token.guard.ts b/src/config/guards/jwt-access-token.guard.ts index 9c7451d..d7b507f 100644 --- a/src/config/guards/jwt-access-token.guard.ts +++ b/src/config/guards/jwt-access-token.guard.ts @@ -10,7 +10,7 @@ export class JwtAccessTokenGuard { const accessToken = request.headers['access_token']; if (!accessToken) { - return false; + return true; } const userId = await this.tokenService.decodeToken(accessToken); diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 3b92bb7..ddcd6c7 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UserRepository } from '../repositories/user.repository'; import { UserImageRepository } from '../repositories/user-image.repository'; @@ -10,6 +10,12 @@ export class UserService { ) {} async getMyInfo(userId: number) { + if (!userId) { + throw new HttpException( + '토큰이 제공되지 않았습니다.', + HttpStatus.LENGTH_REQUIRED, + ); + } const { name, email, gender, admin, provider } = await this.userRepository.getUserInfo(userId); const userImage = (await this.userImageRepository.checkUserImage(userId)) From 8e14e282bce1904772468ce36a60b7fed4273005 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Thu, 23 Nov 2023 15:44:22 +0900 Subject: [PATCH 08/11] =?UTF-8?q?modify(#144):=20user-info=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=A0=20=EB=95=8C=20targetId=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EB=B3=B8=EC=9D=B8=EC=9D=98=20Id=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/users/controllers/user.controller.ts | 2 +- src/users/services/user.service.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 27613ff..4e10f63 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -6,7 +6,7 @@ import { UseGuards, } from '@nestjs/common'; import { UserService } from '../services/user.service'; -import { ApiHeader, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { JwtAccessTokenGuard } from 'src/config/guards/jwt-access-token.guard'; import { GetUserId } from 'src/common/decorators/get-userId.decorator'; import { ApiGetMyInfo } from '../swagger-decorators/get-my-info-decorator'; diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index db1a6a2..3a22519 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -27,11 +27,11 @@ export class UserService { async getMyInfoWithOwner(userId: number, targetId: number) { const { name, email, gender, admin, provider } = - await this.userRepository.getUserInfo(userId); - const userImage = (await this.userImageRepository.checkUserImage(userId)) + await this.userRepository.getUserInfo(targetId); + const userImage = (await this.userImageRepository.checkUserImage(targetId)) .imageUrl; return { - userId, + userId: targetId, name, email, gender, From f759de42d9a01eae92cfe069808ee9c3ef0b09c3 Mon Sep 17 00:00:00 2001 From: 2swo Date: Thu, 23 Nov 2023 18:08:32 +0900 Subject: [PATCH 09/11] =?UTF-8?q?add(#151)=20=EA=B0=84=EB=8B=A8=ED=95=9C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/services/auth.service.ts | 117 +++++++++++++----- src/boards/repository/boards.repository.ts | 7 ++ src/users/controllers/user.controller.ts | 10 +- src/users/services/user.service.ts | 10 ++ .../swagger-decorators/get-info-decorator.ts | 41 ++++++ src/users/user.module.ts | 9 +- 6 files changed, 158 insertions(+), 36 deletions(-) create mode 100644 src/users/swagger-decorators/get-info-decorator.ts diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 1e3e441..960d15e 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -13,7 +13,7 @@ export class AuthService { private readonly userRepository: UserRepository, private readonly userImageRepository: UserImageRepository, private readonly tokenService: TokenService, - ) {} + ) {} async naverLogin(authorizeCode: string) { try { @@ -32,11 +32,13 @@ export class AuthService { redirect_uri: process.env.NAVER_CALLBACK_URL, }; - const naverToken = (await axios.post(naverTokenUrl, naverTokenBody, naverTokenHeader)).data; + const naverToken = ( + await axios.post(naverTokenUrl, naverTokenBody, naverTokenHeader) + ).data; const naverAccessToken = naverToken.access_token; const naverRefreshToken = naverToken.refresh_token; - + const naverUserInfoUrl = 'https://openapi.naver.com/v1/nid/me'; const naverUserInfoHeader = { headers: { @@ -44,7 +46,9 @@ export class AuthService { }, }; - const naverUserInfo = (await axios.get(naverUserInfoUrl, naverUserInfoHeader)).data; + const naverUserInfo = ( + await axios.get(naverUserInfoUrl, naverUserInfoHeader) + ).data; const nickname = naverUserInfo.response.nickname; const email = naverUserInfo.response.email; const profileImage = naverUserInfo.response.profile_image; @@ -58,25 +62,33 @@ export class AuthService { }; const checkUser = await this.userRepository.findUser(email, provider); - if (checkUser) { // 이미 존재하는 사용자인 경우 + if (checkUser) { + // 이미 존재하는 사용자인 경우 const userId = checkUser.id; await this.userRepository.updateUserName(userId, nickname); // 이름 업데이트 - const userImage = (await this.userImageRepository.checkUserImage(userId)).imageUrl; // DB 이미지 + const userImage = ( + await this.userImageRepository.checkUserImage(userId) + ).imageUrl; // DB 이미지 const imageUrlParts = userImage.split('/'); const dbImageProvider = imageUrlParts[imageUrlParts.length - 2]; // 이미지 제공자 이름 - if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { // S3에 업로드된 이미지가 아닌 경우 + if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { + // S3에 업로드된 이미지가 아닌 경우 await this.userImageRepository.updateUserImage(userId, profileImage); // DB에 이미지 URL 업데이트 } return { userId, naverAccessToken, naverRefreshToken }; - } else { // 존재하지 않는 사용자인 경우 + } else { + // 존재하지 않는 사용자인 경우 const newUser = await this.userRepository.createUser(userInfo); const userId = newUser.id; if (!profileImage) { - await this.userImageRepository.uploadUserImage(userId, process.env.DEFAULT_USER_IMAGE); + await this.userImageRepository.uploadUserImage( + userId, + process.env.DEFAULT_USER_IMAGE, + ); } else { await this.userImageRepository.uploadUserImage(userId, profileImage); } @@ -84,7 +96,10 @@ export class AuthService { } } catch (error) { if (error.response.status == 401) { - throw new HttpException('유효하지 않은 인가코드입니다.', HttpStatus.UNAUTHORIZED); + throw new HttpException( + '유효하지 않은 인가코드입니다.', + HttpStatus.UNAUTHORIZED, + ); } } } @@ -104,7 +119,10 @@ export class AuthService { code: authorizeCode, }; - const kakaoToken = (await axios.post(kakaoTokenUrl, kakaoTokenBody, kakaoTokenHeader)).data; + const kakaoToken = ( + await axios.post(kakaoTokenUrl, kakaoTokenBody, kakaoTokenHeader) + ).data; + const kakaoAccessToken = kakaoToken.access_token; const kakaoRefreshToken = kakaoToken.refresh_token; @@ -116,7 +134,9 @@ export class AuthService { }, }; - const kakaoUserInfo = (await axios.get(kakaoUserInfoUrl, kakaoUserInfoHeader)).data; + const kakaoUserInfo = ( + await axios.get(kakaoUserInfoUrl, kakaoUserInfoHeader) + ).data; const nickname = kakaoUserInfo.properties.nickname; const email = kakaoUserInfo.kakao_account.email; const profileImage = kakaoUserInfo.properties.profile_image; @@ -127,28 +147,36 @@ export class AuthService { nickname, email, gender, - } + }; const checkUser = await this.userRepository.findUser(email, provider); - if (checkUser) { // 이미 존재하는 사용자인 경우 + if (checkUser) { + // 이미 존재하는 사용자인 경우 const userId = checkUser.id; await this.userRepository.updateUserName(userId, nickname); // 이름 업데이트 - - const userImage = (await this.userImageRepository.checkUserImage(userId)).imageUrl; // DB 이미지 + + const userImage = ( + await this.userImageRepository.checkUserImage(userId) + ).imageUrl; // DB 이미지 const imageUrlParts = userImage.split('/'); const dbImageProvider = imageUrlParts[imageUrlParts.length - 2]; // 이미지 제공자 이름 - if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { // S3에 업로드된 이미지가 아닌 경우 + if (dbImageProvider != 'ma6-main.s3.ap-northeast-2.amazonaws.com') { + // S3에 업로드된 이미지가 아닌 경우 await this.userImageRepository.updateUserImage(userId, profileImage); // DB에 이미지 URL 업데이트 } return { userId, kakaoAccessToken, kakaoRefreshToken }; - } else { // 존재하지 않는 사용자인 경우 + } else { + // 존재하지 않는 사용자인 경우 const newUser = await this.userRepository.createUser(userInfo); const userId = newUser.id; if (!profileImage) { - await this.userImageRepository.uploadUserImage(userId, process.env.DEFAULT_USER_IMAGE); + await this.userImageRepository.uploadUserImage( + userId, + process.env.DEFAULT_USER_IMAGE, + ); } else { await this.userImageRepository.uploadUserImage(userId, profileImage); } @@ -156,16 +184,21 @@ export class AuthService { } } catch (error) { if (error.response.status == 400) { - throw new HttpException('유효하지 않은 인가코드입니다.', HttpStatus.UNAUTHORIZED); + throw new HttpException( + '유효하지 않은 인가코드입니다.', + HttpStatus.UNAUTHORIZED, + ); } } } async kakaoLogout(accessToken: string, refreshToken: string) { try { - const checkValidKakaoToken = await this.tokenService.checkValidKakaoToken(accessToken); + const checkValidKakaoToken = + await this.tokenService.checkValidKakaoToken(accessToken); if (checkValidKakaoToken === 401) { - const newKakaoToken = await this.tokenService.getNewKakaoToken(refreshToken); + const newKakaoToken = + await this.tokenService.getNewKakaoToken(refreshToken); accessToken = newKakaoToken.access_token; } @@ -177,18 +210,23 @@ export class AuthService { }; axios.post(kakaoLogoutUrl, {}, kakaoLogoutHeader); - return { message: "카카오 로그아웃이 완료되었습니다." }; + return { message: '카카오 로그아웃이 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('카카오 로그아웃 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '카카오 로그아웃 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async kakaoUnlink(accessToken: string, refreshToken: string) { try { - const checkValidKakaoToken = await this.tokenService.checkValidKakaoToken(accessToken); + const checkValidKakaoToken = + await this.tokenService.checkValidKakaoToken(accessToken); if (checkValidKakaoToken === 401) { - const newKakaoToken = await this.tokenService.getNewKakaoToken(refreshToken); + const newKakaoToken = + await this.tokenService.getNewKakaoToken(refreshToken); accessToken = newKakaoToken.access_token; } @@ -200,18 +238,23 @@ export class AuthService { }; axios.post(kakaoUnlinkUrl, {}, kakaoUnlinkHeader); - return { message: "카카오 연결 끊기가 완료되었습니다." }; + return { message: '카카오 연결 끊기가 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('카카오 연결 끊기 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '카카오 연결 끊기 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async naverUnlink(accessToken: string, refreshToken: string) { try { - const checkValidNaverToken = await this.tokenService.checkValidNaverToken(accessToken); + const checkValidNaverToken = + await this.tokenService.checkValidNaverToken(accessToken); if (checkValidNaverToken === 401) { - const newNaverToken = await this.tokenService.getNewNaverToken(refreshToken); + const newNaverToken = + await this.tokenService.getNewNaverToken(refreshToken); accessToken = newNaverToken.access_token; } @@ -229,18 +272,24 @@ export class AuthService { }; axios.post(naverUnlinkUrl, naverUnlinkBody, naverUnlinkHeader); - return { message: "네이버 연동 해제가 완료되었습니다." }; + return { message: '네이버 연동 해제가 완료되었습니다.' }; } catch (error) { console.log(error); - throw new HttpException('네이버 연결 끊기 중 오류가 발생했습니다.', HttpStatus.INTERNAL_SERVER_ERROR) + throw new HttpException( + '네이버 연결 끊기 중 오류가 발생했습니다.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } async accountDelete(userId: number) { const deleteUser = await this.userRepository.deleteUser(userId); if (!deleteUser) { - throw new HttpException('사용자를 찾을 수 없습니다.', HttpStatus.NOT_FOUND); + throw new HttpException( + '사용자를 찾을 수 없습니다.', + HttpStatus.NOT_FOUND, + ); } - return { message: "사용자 계정 삭제가 완료되었습니다." }; + return { message: '사용자 계정 삭제가 완료되었습니다.' }; } } diff --git a/src/boards/repository/boards.repository.ts b/src/boards/repository/boards.repository.ts index dcbd67e..f122602 100644 --- a/src/boards/repository/boards.repository.ts +++ b/src/boards/repository/boards.repository.ts @@ -36,6 +36,13 @@ export class BoardRepository { }); } + async findBoardByuserId(userId: number): Promise { + return await this.entityManager.find(Board, { + relations: ['user', 'user.userImage', 'boardImages'], + where: { userId }, + }); + } + async updateBoard( id: number, boardData: Partial, diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 27613ff..ae0abbb 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -3,14 +3,16 @@ import { Get, Param, ParseIntPipe, + Query, UseGuards, } from '@nestjs/common'; import { UserService } from '../services/user.service'; -import { ApiHeader, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { JwtAccessTokenGuard } from 'src/config/guards/jwt-access-token.guard'; import { GetUserId } from 'src/common/decorators/get-userId.decorator'; import { ApiGetMyInfo } from '../swagger-decorators/get-my-info-decorator'; import { ApiGetMyInfoWithOwner } from '../swagger-decorators/get-my-info-with-owner-decorator'; +import { ApiGetInfo } from '../swagger-decorators/get-info-decorator'; @Controller('user') @ApiTags('user API') @@ -24,6 +26,12 @@ export class UserController { return this.userService.getMyInfo(userId); } + @ApiGetInfo() + @Get('info') + async getUserInfo(@Query('userId') userId: number) { + return this.userService.getUserInfo(userId); + } + @UseGuards(JwtAccessTokenGuard) @ApiGetMyInfoWithOwner() @Get('my-info/:targetId') diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index db1a6a2..4fdac65 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -1,12 +1,14 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from '../repositories/user.repository'; import { UserImageRepository } from '../repositories/user-image.repository'; +import { BoardRepository } from 'src/boards/repository/boards.repository'; @Injectable() export class UserService { constructor( private readonly userRepository: UserRepository, private readonly userImageRepository: UserImageRepository, + private readonly boardRepository: BoardRepository, ) {} async getMyInfo(userId: number) { @@ -25,6 +27,14 @@ export class UserService { }; } + async getUserInfo(userId: number) { + const board = await this.boardRepository.findBoardByuserId(userId); + if (!board) { + return true; + } + return board; + } + async getMyInfoWithOwner(userId: number, targetId: number) { const { name, email, gender, admin, provider } = await this.userRepository.getUserInfo(userId); diff --git a/src/users/swagger-decorators/get-info-decorator.ts b/src/users/swagger-decorators/get-info-decorator.ts new file mode 100644 index 0000000..4f7702c --- /dev/null +++ b/src/users/swagger-decorators/get-info-decorator.ts @@ -0,0 +1,41 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOperation, ApiResponse } from '@nestjs/swagger'; + +export function ApiGetInfo() { + return applyDecorators( + ApiOperation({ + summary: '유저 id로 보드를 조회 API', + description: '유저 id로 보드를 조회 API', + }), + ApiResponse({ + status: 200, + description: '성공적으로 유저 id로 보드를 조회한 경우', + content: { + JSON: { + example: { + userId: 1, + name: '홍길동', + email: 'abcd@naver.com', + gender: 'M', + admin: false, + provider: 'kakao', + userImage: 'http://img.jpg', + }, + }, + }, + }), + ApiResponse({ + status: 404, + description: '사용자를 찾을 수 없는 경우', + content: { + JSON: { + example: { + statusCode: 404, + message: '사용자를 찾을 수 없습니다.', + error: 'Not Found', + }, + }, + }, + }), + ); +} diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 61e04ed..81c00a9 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -9,11 +9,18 @@ import { UserRepository } from './repositories/user.repository'; import { UserImageRepository } from './repositories/user-image.repository'; import { UserImageService } from './services/user-image.service'; import { AuthModule } from 'src/auth/auth.module'; +import { BoardRepository } from 'src/boards/repository/boards.repository'; @Module({ imports: [TypeOrmModule.forFeature([User]), AuthModule], controllers: [UserController, UserImageController], providers: [ - S3Service, UserRepository, UserImageRepository, UserService, UserImageService], + S3Service, + UserRepository, + UserImageRepository, + UserService, + UserImageService, + BoardRepository, + ], }) export class UserModule {} From b8356c499efc5646a55b8a82d3af13db26650701 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Mon, 27 Nov 2023 13:02:40 +0900 Subject: [PATCH 10/11] feat(#144): modify api-path --- src/users/controllers/user.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 4e10f63..3a75711 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -26,7 +26,7 @@ export class UserController { @UseGuards(JwtAccessTokenGuard) @ApiGetMyInfoWithOwner() - @Get('my-info/:targetId') + @Get('info/:targetId') async getMyInfoWithOwner( @GetUserId() userId: number, @Param('targetId', ParseIntPipe) targetId: number, From b431b4bc0e91414120bba6d921e17b64485142e0 Mon Sep 17 00:00:00 2001 From: hobiJeong Date: Mon, 27 Nov 2023 13:10:46 +0900 Subject: [PATCH 11/11] modify(#144): modify api path --- src/users/controllers/user.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 7e83365..694e150 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -27,7 +27,7 @@ export class UserController { } @ApiGetInfo() - @Get('info') + @Get('info-board') async getUserInfo(@Query('userId') userId: number) { return this.userService.getUserInfo(userId); }