mmday-firebase/src/triggers/onRewardImageUploaded.ts
윤정민 1c60607683 Add reward image optimization pipeline
- reward-products/ 이미지를 리사이즈(메인 1080px, 상세 너비 1440px) + WebP(품질 82)로 변환하는 공용 서비스 추가 (sharp)
- Storage 트리거 onRewardImageUploaded(us-central1)로 이후 업로드에도 자동 적용 — 다운로드 토큰을 보존해 기존 URL이 그대로 유효
- 기존 자산 일괄 최적화 스크립트(npm run optimize:reward-images) 추가, 실행 결과 38개 파일 135MB → 5.7MB
- 재처리 무한루프는 rewardImageOptimized 메타데이터 플래그로 방지
2026-07-20 13:04:29 +09:00

47 lines
1.9 KiB
TypeScript

import { onObjectFinalized } from "firebase-functions/storage";
import { logger } from "firebase-functions";
import { getStorage } from "firebase-admin/storage";
import "../firebase";
import { OPTIMIZED_FLAG, optimizeRewardImage, targetForPath } from "../services/imageOptimizeService";
/**
* reward-products/ 아래에 업로드된 이미지를 리사이즈 + WebP로 변환해 같은 경로에 덮어쓴다.
* 커스텀 메타데이터(다운로드 토큰 포함)를 보존하므로 기존 URL은 그대로 유효하다.
* 버킷(US-CENTRAL1)과 같은 리전이어야 Eventarc 트리거가 생성된다.
*/
export const onRewardImageUploaded = onObjectFinalized(
{
bucket: "mmday-panit.firebasestorage.app",
region: "us-central1",
memory: "1GiB",
timeoutSeconds: 300,
},
async (event) => {
const object = event.data;
const target = targetForPath(object.name);
if (!target) return;
if (!object.contentType?.startsWith("image/")) return;
if (object.metadata?.[OPTIMIZED_FLAG] === "true") return;
const file = getStorage().bucket(object.bucket).file(object.name);
const [input] = await file.download();
const output = await optimizeRewardImage(input, target);
if (output.data.length >= input.length) {
// 이미 충분히 작은 파일은 재업로드 없이 플래그만 남긴다(메타데이터 변경은 finalize를 다시 발생시키지 않음).
await file.setMetadata({ metadata: { [OPTIMIZED_FLAG]: "true" } });
logger.info(`[rewardImage] skip (already small) ${object.name} ${input.length}B`);
return;
}
await file.save(output.data, {
contentType: "image/webp",
metadata: { metadata: { ...object.metadata, [OPTIMIZED_FLAG]: "true" } },
});
logger.info(
`[rewardImage] optimized ${object.name} ` +
`${input.length}B -> ${output.data.length}B (${output.width}x${output.height})`,
);
},
);