From 8e5c85a9ec48a1f281b56d1b88d3d9f8b42b0e51 Mon Sep 17 00:00:00 2001 From: weejee12 Date: Mon, 11 Aug 2025 18:59:47 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=BB=A4=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=ED=8F=BC=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20S3=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/commission.controller.js | 3 +- src/commission/service/commission.service.js | 80 ++++++------------- src/common/swagger/commission.json | 6 +- 3 files changed, 29 insertions(+), 60 deletions(-) diff --git a/src/commission/controller/commission.controller.js b/src/commission/controller/commission.controller.js index ea88fc3..4d3fccc 100644 --- a/src/commission/controller/commission.controller.js +++ b/src/commission/controller/commission.controller.js @@ -113,4 +113,5 @@ export const getCommissionReport = async (req, res, next) => { } catch (err) { next(err); } -}; \ No newline at end of file +}; + diff --git a/src/commission/service/commission.service.js b/src/commission/service/commission.service.js index 6cfca1a..89d48fe 100644 --- a/src/commission/service/commission.service.js +++ b/src/commission/service/commission.service.js @@ -1,6 +1,6 @@ import multer from 'multer'; import path from 'path'; -import fs from 'fs'; +import { uploadToS3, deleteFromS3 } from '../../s3.upload.js'; import { CommissionRepository } from "../repository/commission.repository.js"; import { RequestRepository } from "../../request/repository/request.repository.js"; import { @@ -17,38 +17,14 @@ import { } from "../../common/errors/commission.errors.js"; export const CommissionService = { - // 업로드 디렉토리 설정 - uploadDir: path.join(process.cwd(), 'uploads', 'request-images'), /** - * 업로드 디렉토리 생성 + * S3 업로드를 위한 multer 설정 */ - ensureUploadDir() { - if (!fs.existsSync(this.uploadDir)) { - fs.mkdirSync(this.uploadDir, { recursive: true }); - console.log(`업로드 디렉토리 생성: ${this.uploadDir}`); - } - }, - - /** - * multer 저장소 설정 - */ - storage: multer.diskStorage({ - destination: function(req, file, cb) { - const uploadDir = path.join(process.cwd(), 'uploads', 'request-images'); - cb(null, uploadDir); - }, - filename: function(req, file, cb) { - // 파일명: request_현재시간_랜덤값.확장자 - const timestamp = Date.now(); - const random = Math.round(Math.random() * 1E9); - const ext = path.extname(file.originalname); - cb(null, `request_${timestamp}_${random}${ext}`); - } - }), + storage: multer.memoryStorage(), /** - * 파일 필터 (이미지만 허용) + * 파일 필터 */ fileFilter(req, file, cb) { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png']; @@ -67,42 +43,39 @@ export const CommissionService = { * multer 인스턴스 생성 */ getUploadMiddleware() { - // 업로드 디렉토리 확인 - this.ensureUploadDir(); - return multer({ storage: this.storage, fileFilter: this.fileFilter, limits: { - fileSize: 5 * 1024 * 1024 // 5MB 제한 + fileSize: 10 * 1024 * 1024 } }).single('image'); }, /** - * 이미지 업로드 처리 + * S3 이미지 업로드 처리 */ async uploadRequestImage(file) { try { // 1. 파일 존재 여부 확인 if (!file) { - throw new ImageUploadFailedError('파일이 업로드되지 않았습니다'); + throw new ImageUploadFailedError({ reason: '파일이 업로드되지 않았습니다' }); } - // 2. 파일 크기 추가 검증 - if (file.size > 5 * 1024 * 1024) { - this.deleteFile(file.path); - throw new FileSizeExceededError({ - maxSize: '5MB', - receivedSize: `${Math.round(file.size / 1024 / 1024 * 100) / 100}MB` - }); + // 2. 파일 크기 검증 + if (file.size > 10 * 1024 * 1024) { + throw new FileSizeExceededError({ fileSize: file.size }); + } + + // 3. 파일 확장자 검증 + const ext = path.extname(file.originalname).toLowerCase().replace('.', ''); + if (!['jpeg', 'jpg', 'png'].includes(ext)) { + throw new UnsupportedImageFormatError({ fileType: file.mimetype }); } - // 3. 파일 URL 생성 - const baseUrl = process.env.BASE_URL || 'http://localhost:3000'; - const imageUrl = `${baseUrl}/uploads/request-images/${file.filename}`; + // 4. S3 업로드 (requests 폴더에 저장) + const imageUrl = await uploadToS3(file.buffer, 'requests', ext); - // 4. 성공 응답 반환 return { image_url: imageUrl, file_size: file.size, @@ -110,29 +83,24 @@ export const CommissionService = { }; } catch (error) { - // 오류 발생 시 업로드된 파일 삭제 - if (file && file.path) { - this.deleteFile(file.path); - } throw error; } }, /** - * 파일 삭제 헬퍼 메서드 + * S3 이미지 삭제 */ - deleteFile(filePath) { + async deleteS3Image(imageUrl) { try { - if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath); - console.log(`파일 삭제: ${filePath}`); + if (imageUrl && imageUrl.includes('amazonaws.com')) { + await deleteFromS3(imageUrl); + console.log(`S3 이미지 삭제 완료: ${imageUrl}`); } } catch (error) { - console.error('파일 삭제 실패:', error); + console.error('S3 이미지 삭제 실패:', error); } }, - /** * 커미션 게시글 상세글 조회 */ diff --git a/src/common/swagger/commission.json b/src/common/swagger/commission.json index c6b4eb2..e207848 100644 --- a/src/common/swagger/commission.json +++ b/src/common/swagger/commission.json @@ -410,7 +410,7 @@ "image": { "type": "string", "format": "binary", - "description": "업로드할 이미지 파일 (JPG, PNG, 최대 5MB)" + "description": "업로드할 이미지 파일 (JPG, PNG, 최대 10MB)" } }, "required": ["image"] @@ -460,8 +460,8 @@ "data": { "type": "object", "properties": { - "maxSize": { "type": "string", "example": "5MB" }, - "receivedSize": { "type": "string", "example": "7.2MB" } + "maxSize": { "type": "string", "example": "10MB" }, + "receivedSize": { "type": "string", "example": "15MB" } } } } From 0c6d29f13490703c7dda67c3b5d45889b999d0ad Mon Sep 17 00:00:00 2001 From: weejee12 Date: Mon, 11 Aug 2025 19:45:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EC=BA=90=EB=A6=AD=ED=84=B0=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20URL?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commission/service/commission.service.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commission/service/commission.service.js b/src/commission/service/commission.service.js index 89d48fe..48acdb0 100644 --- a/src/commission/service/commission.service.js +++ b/src/commission/service/commission.service.js @@ -595,7 +595,7 @@ export const CommissionService = { // 캐릭터 데이터 CHARACTER_DATA: [ { - image: "https://example.com/character1.png", + image: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.dualstack.${process.env.AWS_REGION}.amazonaws.com/reports/reportType1.png`, quote: { title: "커미션계의 VIP", description: "\"커미션계의 큰 손 등장!\" 덕분에 작가님들의 창작 활동이 풍요로워졌어요." @@ -603,7 +603,7 @@ export const CommissionService = { condition: "월 사용 포인트 15만포인트 이상" }, { - image: "https://example.com/character2.png", + image: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.dualstack.${process.env.AWS_REGION}.amazonaws.com/reports/reportType2.png`, quote: { title: "작가 덕후 신청자", description: "\"이 작가님만큼은 믿고 맡긴다!\" 단골의 미덕을 지닌 당신, 작가님도 감동했을 거예요." @@ -611,7 +611,7 @@ export const CommissionService = { condition: "같은 작가에게 3회 이상 신청" }, { - image: "https://example.com/character3.png", + image: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.dualstack.${process.env.AWS_REGION}.amazonaws.com/reports/reportType3.png`, quote: { title: "호기심 대장 신청자", description: "호기심이 가득해서, 언제나 새로운 작가를 탐색해요." @@ -619,7 +619,7 @@ export const CommissionService = { condition: "서로 다른 작가 5명 이상에게 커미션을 신청" }, { - image: "https://example.com/character4.png", + image: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.dualstack.${process.env.AWS_REGION}.amazonaws.com/reports/reportType4.png`, quote: { title: "숨겨진 보석 발굴가", description: "\"빛나는 원석을 내가 발견했다!\" 성장하는 작가님들의 첫걸음을 함께한 당신, 멋져요." @@ -627,7 +627,7 @@ export const CommissionService = { condition: "팔로워 수가 0명인 작가에게 신청 2회 이상" }, { - image: "https://example.com/character5.png", + image: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.dualstack.${process.env.AWS_REGION}.amazonaws.com/reports/reportType5.png`, quote: { title: "빠른 피드백러", description: "\"작가님, 이번 커미션 최고였어요!\" 정성 가득한 피드백으로 건강한 커미션 문화를 만들어가요."