import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; import sharp from 'sharp'; import config from '../config/index.js'; // S3 클라이언트 생성 const s3Client = new S3Client({ endpoint: config.s3.endpoint, region: 'us-east-1', credentials: { accessKeyId: config.s3.accessKey, secretAccessKey: config.s3.secretKey, }, forcePathStyle: true, }); const BUCKET = config.s3.bucket; const PUBLIC_URL = config.s3.publicUrl; /** * 이미지를 3가지 해상도로 변환 */ async function processImage(buffer) { const [originalBuffer, mediumBuffer, thumbBuffer] = await Promise.all([ sharp(buffer).webp({ lossless: true }).toBuffer(), sharp(buffer) .resize(800, null, { withoutEnlargement: true }) .webp({ quality: 85 }) .toBuffer(), sharp(buffer) .resize(400, null, { withoutEnlargement: true }) .webp({ quality: 80 }) .toBuffer(), ]); return { originalBuffer, mediumBuffer, thumbBuffer }; } /** * S3에 이미지 업로드 */ async function uploadToS3(key, buffer, contentType = 'image/webp') { await s3Client.send(new PutObjectCommand({ Bucket: BUCKET, Key: key, Body: buffer, ContentType: contentType, })); return `${PUBLIC_URL}/${BUCKET}/${key}`; } /** * S3에서 이미지 삭제 */ async function deleteFromS3(key) { try { await s3Client.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: key, })); } catch (err) { console.error(`S3 삭제 오류 (${key}):`, err.message); } } /** * 멤버 프로필 이미지 업로드 * @param {string} name - 멤버 이름 * @param {Buffer} buffer - 이미지 버퍼 * @returns {Promise<{originalUrl: string, mediumUrl: string, thumbUrl: string}>} */ export async function uploadMemberImage(name, buffer) { const { originalBuffer, mediumBuffer, thumbBuffer } = await processImage(buffer); const basePath = `member/${name}`; const filename = `${name}.webp`; // 병렬 업로드 const [originalUrl, mediumUrl, thumbUrl] = await Promise.all([ uploadToS3(`${basePath}/original/${filename}`, originalBuffer), uploadToS3(`${basePath}/medium_800/${filename}`, mediumBuffer), uploadToS3(`${basePath}/thumb_400/${filename}`, thumbBuffer), ]); return { originalUrl, mediumUrl, thumbUrl }; } /** * 멤버 프로필 이미지 삭제 * @param {string} name - 멤버 이름 */ export async function deleteMemberImage(name) { const basePath = `member/${name}`; const filename = `${name}.webp`; const sizes = ['original', 'medium_800', 'thumb_400']; await Promise.all( sizes.map(size => deleteFromS3(`${basePath}/${size}/${filename}`)) ); }