Bangun laporan publik yang jujur: terkumpul hanya dari donasi verified, jumlah donatur non-anonim, progress terhadap target, dan agregat tanpa PII.
35m bacaanKuis WajibMicro Badge Lesson
Halaman Laporan Transparansi
Setelah admin memverifikasi donasi, publik perlu melihat dampaknya. Tapi angka publik harus jujur.
Kalimat seperti “terkumpul Rp10.000.000” terdengar sederhana. Pertanyaannya: terkumpul dari mana?
Kalau dihitung dari semua donasi, angka akan memasukkan pending, rejected, dan cancelled. Itu tidak jujur.
Kalau dihitung dari payment status paid, kamu harus memastikan payment flow memang menjadi sumber kebenaran. Di schema saat ini, status donasi yang menandakan uang terverifikasi adalah:
text
donations.status = verified
Jadi definisi laporan harus ditulis sebelum implementasi.
Definisi angka publik
Gunakan definisi ini:
text
Terkumpul = sum(amount) dari donations yang status='verified'Jumlah donatur = count donation verified yang is_anonymous=falseProgress = terkumpul / target_amount campaign
Donasi anonim tetap menambah nominal terkumpul, karena uangnya nyata. Tetapi ia tidak dihitung sebagai identitas donatur terbuka.
Donasi pending, rejected, cancelled, dan refunded tidak dihitung sebagai terkumpul.
Ini bukan sekadar teknis. Ini amanah angka.
Agregat publik tidak boleh bocor PII
Laporan publik cukup menampilkan angka agregat:
total terkumpul;
jumlah donatur non-anonim;
progress.
Ia tidak perlu mengembalikan:
donor_name_snapshot;
donor_email_snapshot;
donor_phone_snapshot;
daftar donation_number;
idempotency key;
lookup token hash.
Kalau halaman publik membaca row donasi satu per satu lalu menyembunyikan kolom di UI, itu masih buruk. Data sudah telanjur keluar ke server/public route. Lebih baik buat function agregat yang hanya mengembalikan angka.
Function agregat yang sempit
Rancang function seperti:
sql
create or replace function public.get_campaign_summary(p_campaign_id uuid)returns table ( campaign_id uuid, collected_amount numeric, public_donor_count bigint)language sqlsecurity definerset search_path = publicas $$ select c.id as campaign_id, coalesce(sum(d.amount) filter (where d.status = 'verified'), 0) as collected_amount, count(d.id) filter (where d.status = 'verified' and d.is_anonymous = false) as public_donor_count from public.campaigns c left join public.donations d on d.campaign_id = c.id where c.id = p_campaign_id and c.status = 'published' group by c.id;$$;
Function ini punya pagar:
hanya campaign published;
hanya angka agregat;
tidak mengembalikan PII;
memakai verified sebagai sumber “terkumpul”.
Grant-nya bisa dibuka untuk anon/authenticated karena return-nya aman:
sql
revoke all on function public.get_campaign_summary(uuid) from public;grant execute on function public.get_campaign_summary(uuid) to anon, authenticated;
Prompt untuk Antigravity
text
Mulai dari AGENTS.md, DESIGN.md, dan docs/README.md.Tugas: tambahkan laporan transparansi publik untuk campaign.Baca:- docs/data-contract.md- docs/project-rules.md- supabase/migrations/20260829_vertical_slice.sql- src/app/campaigns/[slug]/page.tsxDefinisi angka resmi:1. Terkumpul = SUM donations.amount hanya untuk donations.status = 'verified'.2. Donasi pending/rejected/cancelled/refunded tidak dihitung.3. Jumlah donatur = COUNT donasi verified yang is_anonymous=false.4. Donasi anonim tetap menambah nominal, tetapi tidak dihitung sebagai identitas donatur publik.5. Progress = terkumpul / target_amount.Constraint privasi:1. Halaman publik tidak boleh mengembalikan nama, email, telepon, donation_number, lookup_token_hash, atau idempotency_key.2. Gunakan function agregat security definer yang hanya mengembalikan angka agregat.3. Function hanya bekerja untuk campaign published.Implementasi:- migration function get_campaign_summary(campaign_id).- halaman detail campaign memanggil function ini dan menampilkan Terkumpul, Jumlah donatur, Progress.- Format nominal sebagai Rupiah.- Campaign tanpa verified donation menampilkan Rp0 dan progress 0%.Acceptance criteria:- Dengan data contoh verified 100000 + 250000 + 150000, terkumpul tampil Rp500.000.- Pending/rejected tidak menambah angka.- Donasi anonim menambah nominal tetapi tidak menambah public donor count.- Function tidak mengembalikan PII.- npm run lint dan npm run build berhasil.Laporkan file yang diubah dan cara verifikasinya.
Verifikasi dengan data yang bisa dihitung manual
Buat data contoh kecil di database staging/local, bukan production.
Skenario:
Status
Nominal
Anonim
Masuk terkumpul?
Masuk jumlah donatur?
verified
100000
true
ya
tidak
verified
250000
false
ya
ya
verified
150000
false
ya
ya
pending
1000000
false
tidak
tidak
rejected
2000000
false
tidak
tidak
Expected:
text
Terkumpul = Rp500.000Jumlah donatur = 2
Cek langsung:
sql
select coalesce(sum(amount) filter (where status = 'verified'), 0) as collected_amount, count(*) filter (where status = 'verified' and is_anonymous = false) as public_donor_countfrom public.donationswhere campaign_id = '<campaign-id>';
Lalu cocokkan dengan halaman campaign.
Cek function tidak membocorkan data
Panggil function:
sql
select * from public.get_campaign_summary('<campaign-id>');
Kolom yang boleh keluar hanya:
text
campaign_idcollected_amountpublic_donor_count
Kalau ada nama, email, phone, donation_number, token hash, atau idempotency key, function terlalu lebar.
Prinsip yang perlu dibawa
Transparansi bukan berarti membuka semua data.
Transparansi yang aman berarti:
text
angka publik jelas definisinyadata privat tetap tertutuphasil bisa dihitung ulang dari contoh data
Angka tanpa definisi bisa menipu. Data tanpa privasi bisa merusak amanah. Laporan publik harus menjaga keduanya.
Checkpoint
Terkumpul hanya menghitung verified.
Pending/rejected/cancelled/refunded tidak dihitung.
Donasi anonim menambah nominal tapi tidak menambah public donor count.
Function agregat tidak mengembalikan PII.
Campaign tanpa donasi verified tampil Rp0.
Angka halaman cocok dengan perhitungan SQL manual.
npm run lint dan npm run build berhasil.
Sekarang kamu punya laporan publik yang tidak sekadar “kelihatan transparan”. Angkanya punya definisi, bisa diuji, dan tidak membuka data pribadi. Itu bedanya laporan amanah dengan dashboard angka asal jadi.
Checkpoint KuisTarget: 70%
Kuis 05.3 — Laporan Transparansi Publik
Kuis ini wajib untuk sertifikat. Kamu tetap bisa lanjut ke pelajaran berikutnya dan mengerjakannya nanti.