캠페인 성공 알림 · 운영 퀘스트 참여 인원 · Music Shake 하루 1회 · 함수 배포 전 빌드
This commit is contained in:
@@ -113,10 +113,39 @@ export async function settleDueCampaigns(now: Timestamp = Timestamp.now()): Prom
|
||||
if (success === null) continue;
|
||||
settled += 1;
|
||||
logger.info('[campaign] 정산', { id: doc.id, success });
|
||||
if (success) await notifySuccess(doc.id);
|
||||
}
|
||||
return settled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 성공 알림 — B-3 8-10 「캠페인 성공 Push」. 개설자 1건 · 후원자(고유 uid) 1건씩. 알림함에 쌓이면
|
||||
* `fnPushInbox` 가 같은 문구로 Push 를 보낸다(`kind = campaign` → 앱 C-78 「결과」 단계).
|
||||
* 문서 ID 가 캠페인마다 고정이라 다시 불러도 한 사람에게 한 건이다.
|
||||
* ponytail: 판정 트랜잭션과 알림 사이에 함수가 죽으면 그 회차 알림은 빠진다(상태가 이미 success).
|
||||
* 빠짐이 문제가 되면 `successNotifiedAt` 표식 + 재시도 패스로.
|
||||
*/
|
||||
export async function notifySuccess(campaignId: string): Promise<number> {
|
||||
const ref = getFirestore().doc(`campaigns/${campaignId}`);
|
||||
const [campaign, donations] = await Promise.all([ref.get(), ref.collection('donations').get()]);
|
||||
const title = (campaign.get('title') as string | undefined) ?? '캠페인';
|
||||
const creator = campaign.get('creatorUid') as string | undefined;
|
||||
const donors = new Set(donations.docs
|
||||
.map((d) => d.get('uid') as string | undefined)
|
||||
.filter((u): u is string => !!u && u !== creator));
|
||||
if (creator) {
|
||||
await notifyOnce(creator, `campaignSuccess_${campaignId}`, {
|
||||
title: `개설하신 「${title}」 캠페인이 목표를 달성했습니다`, campaignId,
|
||||
});
|
||||
}
|
||||
for (const donor of donors) {
|
||||
await notifyOnce(donor, `campaignSuccess_${campaignId}`, {
|
||||
title: `후원하신 「${title}」 캠페인이 목표를 달성했습니다`, campaignId,
|
||||
});
|
||||
}
|
||||
return donors.size + (creator ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 환불이 끝나지 않은 실패 캠페인을 돌린다 — 방금 실패로 판정된 건도 여기서 첫 환불을 받는다.
|
||||
* 자동 시도 상한을 넘긴 건은 Admin 큐에 있으므로 건드리지 않는다. 실제로 돈 건수를 돌려준다.
|
||||
|
||||
Reference in New Issue
Block a user