เอกสารนี้อธิบายการทำงานของฟีเจอร์ โปรไฟล์ → ลบบัญชี สำหรับผู้พัฒนาและผู้ตรวจสอบ
- ผู้ใช้ต้องสามารถยกเลิกการใช้งานและ ลบข้อมูลของตนเอง ได้ภายในแอป (ข้อกำหนดของ Apple App Store ข้อ 5.1.1(v) และ Google Play — ควรตรวจฉบับล่าสุดก่อนส่งตรวจ)
- แอปไม่มี backend ของตัวเอง (ไม่มี Cloud Functions) การลบจึงทำจากฝั่ง client ภายใต้ Security Rules เดิม โดยเพิ่มสิทธิ์ที่จำเป็นเท่าที่ต้องใช้เท่านั้น
โค้ดอยู่ที่ src/lib/accountDeletion.ts (ข้อมูล) และ src/contexts/AuthContext.tsx (deleteAccount) ส่วน UI อยู่ที่ src/components/DeleteAccount.tsx
| # | ขั้นตอน | เหตุผล |
|---|---|---|
| 0 | สรุปแผน (planAccountDeletion) อ่านอย่างเดียว |
ให้ผู้ใช้เห็นผลกระทบก่อนยืนยัน โดยเฉพาะสมุดที่เป็นเจ้าของ |
| 1 | ยืนยันตัวตนใหม่ reauthenticateWithPopup |
Firebase อนุญาตให้ลบบัญชีเฉพาะเมื่อเพิ่งล็อกอิน; ต้องเรียกจากการกดปุ่มของผู้ใช้โดยตรง (ไม่งั้น popup ถูกบล็อก) |
| 2 | ลบ users/{uid}/transactions/* เป็นชุดละ 400 รายการ |
ข้อมูลส่วนตัวทั้งหมด |
| 3 | วนทุกสมุดที่เป็นสมาชิก (รวมสมุดที่ถูก soft-delete) | ดูด้านล่าง |
| 4 | ถอนคำขอเข้าร่วมทั้งหมดที่ผู้ใช้ส่งไว้ (อ่านจาก users/{uid}/joinRequestRefs ใน Firestore) แล้วลบรายการนั้น |
ไม่ทิ้งคำขอที่มีชื่อ/รูปค้างในสมุดคนอื่น แม้ส่งจากอุปกรณ์อื่น |
| 5 | deleteUser ลบบัญชี Firebase Auth |
ทำเป็นขั้นสุดท้าย เพราะขั้นก่อนหน้าต้องใช้สิทธิ์ผู้ใช้ที่ยังล็อกอินอยู่ |
| 6 | ล้างข้อมูลในเครื่อง (localStorage คีย์ sood-ngern-*) และแสดงข้อความยืนยันที่หน้า Login |
| บทบาท | สิ่งที่ทำ |
|---|---|
| Owner | ลบรายการในสมุด → ลบคำขอเข้าร่วม → ลบเอกสารสมุด (hard delete) สมาชิกคนอื่นเสียสิทธิ์เข้าถึง — หน้ายืนยันระบุชื่อสมุดและจำนวนสมาชิกชัดเจน |
| Editor / Viewer | เปลี่ยน createdByName เป็น “ผู้ใช้ที่ลบบัญชี” และลบ createdByPhotoURL ในรายการที่ตนบันทึก ไม่ว่าสิทธิ์ปัจจุบันจะเป็นอะไร (อดีต Editor ที่ถูกลดเป็น Viewer ก็ยังมีรายการเก่า) แล้วออกจากสมุด (arrayRemove + ลบรายการใน members) |
ทำไมไม่ลบรายการของ Editor ออกจากสมุดร่วม — สมุดร่วมคือบัญชีของทุกคน การลบรายการย้อนหลังจะทำให้ยอดรวมของสมาชิกที่เหลือผิดเพี้ยน จึงเลือก “ไม่ระบุตัวตน” แทน (ตัดชื่อและรูปที่ระบุตัวบุคคลได้ออก)
ทุกขั้นตอนทำซ้ำได้โดยไม่เกิดผลเสีย: การลบเอกสารที่ไม่มีอยู่ไม่ทำให้พัง และการออกจากสมุดที่ไม่ได้เป็นสมาชิกแล้วจะไม่ถูกเลือกขึ้นมาอีก หากล้มเหลวกลางทาง (เครือข่ายหลุด ปิดแท็บ) ผู้ใช้เปิดแอปแล้วกดลบบัญชีซ้ำได้ ข้อมูลที่เหลือจะถูกลบต่อ ข้อความผิดพลาดที่พบบ่อยถูกแปลเป็นภาษาไทย: ปิด popup ยืนยันตัวตน, popup ถูกบล็อก, เลือกบัญชี Google ไม่ตรงกับที่ล็อกอิน
| กฎ | เหตุผล |
|---|---|
books/{id} อนุญาต delete เฉพาะเจ้าของ (resource.data.ownerUid == request.auth.uid) |
hard-delete สมุดตอนลบบัญชี |
books/{id}/transactions อนุญาต delete ให้เจ้าของเสมอ แม้สมุดถูก soft-delete |
เคลียร์รายการของสมุดที่เคยกดลบไว้ก่อนหน้า |
สมาชิกทุกคนแก้รายการ ของตัวเอง ได้เฉพาะ createdByName → “ผู้ใช้ที่ลบบัญชี” และ createdByPhotoURL → "" (isOwnTxAnonymization) |
ทำให้รายการไม่ระบุตัวตนได้ทุกบทบาท โดยแก้อย่างอื่นไม่ได้ |
| สมาชิกออกจากสมุดได้เฉพาะตัวเอง | ออกจากสมุด |
users/{uid}/joinRequestRefs/* เป็นของผู้ใช้คนเดียว |
รู้ว่าต้องถอนคำขอเข้าร่วมสมุดใดบ้าง |
ทุกกฎมีเทสต์ใน tests/firestore.rules.test.ts (npm run test:rules)
หน้ายืนยันมีปุ่ม ดาวน์โหลดรายการส่วนตัวเป็น CSV (UTF-8 พร้อม BOM เพื่อให้ Excel แสดงภาษาไทยถูกต้อง) คอลัมน์: date, type, category, amount, note
ยังไม่รวมสมุดร่วม (เป็นข้อมูลของสมาชิกทุกคน)
- ไม่ใช่ transaction เดียว — การลบทำเป็นหลายคำสั่ง จึงอาศัยความสามารถทำซ้ำเพื่อกู้จากความล้มเหลวแทน
- ไม่มีการโอนความเป็นเจ้าของสมุด — เจ้าของที่ลบบัญชีจะลบสมุดให้ทุกคน (เตือนชัดเจนก่อนยืนยัน) การเพิ่มฟีเจอร์โอนสิทธิ์เป็นแผนต่อยอดที่เหมาะสม
- Firebase Analytics ไม่ได้เก็บตัวตนผู้ใช้รายบุคคล จึงไม่มีข้อมูลต้องลบเพิ่ม (ดู นโยบายความเป็นส่วนตัว)
- สำรองข้อมูลอัตโนมัติของ Firestore (ถ้าเปิดใช้) อาจเก็บสำเนาไว้ตามรอบของมันก่อนถูกลบทิ้งเอง — ตรวจการตั้งค่าของโปรเจคก่อนระบุระยะเวลาในนโยบาย
- Security Rules —
npm run test:rules(Firestore Emulator) ครอบคลุมสิทธิ์ลบสมุด/รายการ, ทำให้รายการไม่ระบุตัวตน, ออกจากสมุด - ตรวจ flow ทั้งเส้นทาง — ใช้ข้อมูลตัวอย่างบน Firebase จำลอง ผลที่ได้:
- เจ้าของ: รายการส่วนตัว 20 → 0, สมุดและรายการในสมุดถูกลบหมด
- Editor: ถูกนำออกจากสมุด, รายการที่เคยบันทึก 1 รายการถูกทำให้ไม่ระบุตัวตน, ข้อมูลของคนอื่นไม่ถูกแตะ
- ก่อนใช้จริง ควรลองกับบัญชี Google ทดสอบบนโปรเจคจริงหนึ่งรอบ เพราะขั้นตอนยืนยันตัวตนด้วย popup ของ Google จำลองไม่ได้