diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 1e3e441..9ade336 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,28 @@ 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 +217,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 +245,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 +279,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/common/decorators/get-userId.decorator.ts b/src/common/decorators/get-userId.decorator.ts index 59ca9ad..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; }, ); 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/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 1007045..694e150 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -1,9 +1,18 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; +import { + Controller, + Get, + Param, + ParseIntPipe, + Query, + 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'; 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') @@ -16,4 +25,20 @@ export class UserController { async getMyInfo(@GetUserId() userId: number) { return this.userService.getMyInfo(userId); } + + @ApiGetInfo() + @Get('info-board') + async getUserInfo(@Query('userId') userId: number) { + return this.userService.getUserInfo(userId); + } + + @UseGuards(JwtAccessTokenGuard) + @ApiGetMyInfoWithOwner() + @Get('info/:targetId') + async getMyInfoWithOwner( + @GetUserId() userId: number, + @Param('targetId', ParseIntPipe) targetId: number, + ) { + return this.userService.getMyInfoWithOwner(userId, targetId); + } } diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 3b92bb7..3604f10 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -1,15 +1,23 @@ -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'; +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) { + 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)) @@ -24,4 +32,29 @@ export class UserService { userImage, }; } + + 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(targetId); + const userImage = (await this.userImageRepository.checkUserImage(targetId)) + .imageUrl; + return { + userId: targetId, + name, + email, + gender, + admin, + provider, + userImage, + owner: userId === targetId, + }; + } } 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/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 new file mode 100644 index 0000000..0b8e065 --- /dev/null +++ b/src/users/swagger-decorators/get-my-info-with-owner-decorator.ts @@ -0,0 +1,56 @@ +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: 1, + name: '홍길동', + email: 'abcd@naver.com', + gender: 'M', + admin: false, + provider: 'kakao', + userImage: 'http://img.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 }), + ); +} 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 {}