캠페인 실시간 랭킹 후원자 이름 · 위치 핀 도로명 주소

This commit is contained in:
Sn-Kinos
2026-10-06 01:24:53 +09:00
parent 83d9c1e8d4
commit d441170dd7
6 changed files with 90 additions and 9 deletions
+3 -1
View File
@@ -1150,7 +1150,7 @@ export async function fundCampaign(input: {
// 귀속 팬덤은 바뀌지 않는 값이라 트랜잭션 밖에서 한 번 읽는다. 상태 · 목표 · 모금액은
// 아래 precheck 가 **차감과 같은 트랜잭션에서** 다시 읽는다 — 밖에서 판정하면 동시
// 후원 둘이 같은 잔여분을 보고 함께 통과해 목표를 넘긴다(B-34).
const campaign = await campaignRef.get();
const [campaign, donor] = await Promise.all([campaignRef.get(), db.doc(`users/${uid}`).get()]);
if (!campaign.exists) fail(ERR.campaignNotApproved, '캠페인을 찾을 수 없습니다.');
const fandomId = campaign.get('fandomId');
let goal = 0;
@@ -1189,6 +1189,8 @@ export async function fundCampaign(input: {
uid,
// 「기여중」 목록의 collectionGroup 인덱스 키(firestore.indexes.json)와 같은 이름.
donorUid: uid,
// 실시간 랭킹의 이름 — 공개 닉네임(M-7). 다른 후원자의 사용자 문서는 규칙상 읽을 수 없다.
donorName: publicNameOf(donor),
amount,
popType,
txId: txIdOf(`fund:${uid}:${campaignId}:${clientSeq}`),
@@ -290,6 +290,15 @@ describe('회사귀책 취소 — fn-campaign-admin-cancel (B-58)', () => {
});
});
it('후원 문서에 공개 닉네임(donorName)을 싣는다 — 실시간 랭킹 이름(M-7), 닉네임 없으면 「팬」', async () => {
await db().doc(`users/${A}`).set({ nickname: '후원왕', displayName: '김실명' }, { merge: true });
await fundedCampaign('cmp-names', 1_000, [[A, 10], [B, 10]]);
const rows = (await db().collection('campaigns/cmp-names/donations').get()).docs.map((d) => d.data());
expect(rows.find((r) => r.uid === A)?.donorName).toBe('후원왕');
expect(rows.find((r) => r.uid === B)?.donorName).toBe('팬');
await db().recursiveDelete(db().doc('campaigns/cmp-names'));
});
describe('정산 분기', () => {
it('목표(targetPop) 달성은 success, 미달은 fail 이다', async () => {
await fundedCampaign('cmp-ok', 500, [[A, 300], [B, 200]]);