From 53bf6563f0b591d66c404f48025d5d86318adc48 Mon Sep 17 00:00:00 2001 From: 2swo Date: Mon, 6 Nov 2023 15:35:17 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor(#58)=20boardImage(PATCH)=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boards/controllers/Boards.controller.ts | 2 +- src/boards/services/BoardImage.service.ts | 64 ++++++++++++--------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/boards/controllers/Boards.controller.ts b/src/boards/controllers/Boards.controller.ts index e56fe9f..96f965a 100644 --- a/src/boards/controllers/Boards.controller.ts +++ b/src/boards/controllers/Boards.controller.ts @@ -97,7 +97,7 @@ export class BoardsController { async editBoardImages( @Headers('access_token') accessToken: string, @Query('boardId') boardId: number, - @Query('deleteImageUrl') deleteImageUrl: string, + @Query('deleteImageUrl') deleteImageUrl: string[], @UploadedFiles() files: Express.Multer.File[], ) { const userId = await this.tokenService.decodeToken(accessToken); diff --git a/src/boards/services/BoardImage.service.ts b/src/boards/services/BoardImage.service.ts index a700351..ebbd01d 100644 --- a/src/boards/services/BoardImage.service.ts +++ b/src/boards/services/BoardImage.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BoardImageRepository } from '../repository/boardImage.repository'; import { S3Service } from '../../common/s3/s3.service'; import { CreateBoardImageDto } from '../dto/create.board-image.dto'; +import { BoardImage } from '../entities/board-image.entity'; @Injectable() export class BoardImagesService { @@ -20,7 +21,7 @@ export class BoardImagesService { const uploadedImage = await this.s3Service.uploadImage( file, userId, - 'BoadImages/', + 'BoadImages', ); const boardImage = new CreateBoardImageDto(); boardImage.boardId = boardId; @@ -34,42 +35,53 @@ export class BoardImagesService { async updateBoardImages( boardId: number, - files: Express.Multer.File[], + files: Express.Multer.File[] | undefined, userId: number, - deleteImageUrl: string, + deleteImageUrl: string[], ): Promise { - const existingImages = + const existingImages = // boardId에 해당하는 이미지url을 DB에서 불러옵니다. await this.boardImageRepository.getBoardImages(boardId); - const imagesToDelete = existingImages.filter( - (image) => image.imageUrl === deleteImageUrl, + (image) => deleteImageUrl.includes(image.imageUrl), // 불러온 이미지들과 param값 비교 ); - const s3ToDelete = imagesToDelete.map((image) => { - const parts = image.imageUrl.split('/'); - const fileName = parts[parts.length - 1]; - return 'BoardImages/' + fileName; - }); - - await this.boardImageRepository.deleteImages(imagesToDelete); - await this.s3Service.deleteImage(s3ToDelete.join(',')); + await this.deleteImages(imagesToDelete); //이미지 삭제처리 + // 여기서부터 새로운 이미지 추가 const newImagesArray: CreateBoardImageDto[] = []; - for (const file of files) { - const uploadedImage = await this.s3Service.uploadImage( - file, - userId, - 'BoardImages/', - ); - const boardImage = new CreateBoardImageDto(); - boardImage.boardId = boardId; - boardImage.imageUrl = uploadedImage.url; - const savedImage = - await this.boardImageRepository.saveBoardImage(boardImage); - newImagesArray.push(savedImage); + // 예외처리 files이 없으면 실행되지 않음. + if (files && files.length > 0) { + for (const file of files) { + const uploadedImage = await this.s3Service.uploadImage( + file, + userId, + 'BoardImages', + ); + const boardImage = new CreateBoardImageDto(); + boardImage.boardId = boardId; + boardImage.imageUrl = uploadedImage.url; + const savedImage = + await this.boardImageRepository.saveBoardImage(boardImage); + newImagesArray.push(savedImage); + } } return { message: '이미지 업데이트 및 삭제가 성공적으로 처리되었습니다.', newImagesArray, }; } + // 이미지 삭제 수행 + private async deleteImages(imagesToDelete: BoardImage[]) { + const s3ToDelete = imagesToDelete.map((image) => { + const parts = image.imageUrl.split('/'); + const fileName = parts[parts.length - 1]; // S3에서 삭제할 이미지 파일명을 추출합니다. + console.log(fileName); + return 'BoardImages/' + fileName; + }); + + // 예외처리 (빈 배열이 아닐때만 삭제요청 하기) + if (s3ToDelete.length > 0) { + await this.boardImageRepository.deleteImages(imagesToDelete); // 이미지 삭제 (db) + await this.s3Service.deleteImage(s3ToDelete.join(',')); // 이미지 삭제 (s3) + } + } } From 0cfe0972327cd24a54b3b7bed7624129a1e3e367 Mon Sep 17 00:00:00 2001 From: 2swo Date: Mon, 6 Nov 2023 15:51:05 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor(#58)=20boardImage(PATCH)=20?= =?UTF-8?q?=EB=8D=B0=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boards/controllers/Boards.controller.ts | 2 + .../patch-board-decorators.ts | 4 +- .../patch-board-images-decorators.ts | 85 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/boards/swagger-decorators/patch-board-images-decorators.ts diff --git a/src/boards/controllers/Boards.controller.ts b/src/boards/controllers/Boards.controller.ts index 96f965a..5952a3e 100644 --- a/src/boards/controllers/Boards.controller.ts +++ b/src/boards/controllers/Boards.controller.ts @@ -25,6 +25,7 @@ import { ApiGetOneBoard } from '../swagger-decorators/get-one-board-decorators'; import { ApiUpdateBoard } from '../swagger-decorators/patch-board-decorators'; import { ApiTags } from '@nestjs/swagger'; import { ApiDeleteBoard } from '../swagger-decorators/delete-board-decorators'; +import { ApiUpdateBoardImage } from '../swagger-decorators/patch-board-images-decorators'; @Controller('boards') @ApiTags('board API') @@ -93,6 +94,7 @@ export class BoardsController { } @Patch('/images') + @ApiUpdateBoardImage() @UseInterceptors(FilesInterceptor('files', 3)) async editBoardImages( @Headers('access_token') accessToken: string, diff --git a/src/boards/swagger-decorators/patch-board-decorators.ts b/src/boards/swagger-decorators/patch-board-decorators.ts index 53ebdcc..8061b58 100644 --- a/src/boards/swagger-decorators/patch-board-decorators.ts +++ b/src/boards/swagger-decorators/patch-board-decorators.ts @@ -4,8 +4,8 @@ import { ApiHeaders, ApiOperation, ApiResponse } from '@nestjs/swagger'; export function ApiUpdateBoard() { return applyDecorators( ApiOperation({ - summary: '보드를 수정 API', - description: '보드 수정 API', + summary: '보드를 수정하는 API', + description: '보드 수정하는 API', }), ApiResponse({ status: 200, diff --git a/src/boards/swagger-decorators/patch-board-images-decorators.ts b/src/boards/swagger-decorators/patch-board-images-decorators.ts new file mode 100644 index 0000000..bab6c1a --- /dev/null +++ b/src/boards/swagger-decorators/patch-board-images-decorators.ts @@ -0,0 +1,85 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiHeaders, ApiOperation, ApiResponse } from '@nestjs/swagger'; + +export function ApiUpdateBoardImage() { + return applyDecorators( + ApiOperation({ + summary: '보드의 이미지를 수정하는 API', + description: '보드의 이미지를 수정하는 API', + }), + ApiResponse({ + status: 200, + description: '보드의 내용을 성공적으로 수정한 경우', + content: { + JSON: { + example: { + message: '이미지 업데이트 및 삭제가 성공적으로 처리되었습니다.', + newImagesArray: [ + { + boardId: '업데이트된 보드 아이디', + imageUrl: '새롭게 넣은 이미지 url', + id: '새롭게 넣은 이미지 id', + }, + ], + }, + }, + }, + }), + ApiResponse({ + status: 401, + description: '우리 서비스의 액세스 토큰이 아닌 경우', + content: { + JSON: { + example: { statusCode: 401, message: '유효하지 않은 토큰입니다.' }, + }, + }, + }), + ApiResponse({ + status: 403, + description: '만료된 액세스 토큰인 경우', + content: { + JSON: { + example: { statusCode: 403, message: '만료된 토큰입니다.' }, + }, + }, + }), + ApiResponse({ + status: 404, + description: 'DB에서 사용자를 찾을 수 없는 경우', + content: { + JSON: { + example: { statusCode: 404, message: '사용자를 찾을 수 없습니다.' }, + }, + }, + }), + ApiResponse({ + status: 411, + description: '액세스 토큰이 제공되지 않은 경우', + content: { + JSON: { + example: { statusCode: 411, message: '토큰이 제공되지 않았습니다.' }, + }, + }, + }), + ApiResponse({ + status: 500, + description: '보드 수정중 오류가 발생했습니다', + content: { + JSON: { + example: { + statusCode: 500, + message: '보드 수정 중 오류가 발생했습니다.', + }, + }, + }, + }), + ApiHeaders([ + { + name: 'access_token', + description: '액세스 토큰', + required: true, + example: '여기에 액세스 토큰', + }, + ]), + ); +}