Skip to main content

Architecture

LifeLink KH is two clients and one Firebase project, running at $0.

Firebase (lifelinkkh, asia-southeast1, Spark plan — no card)
┌──────────────────────────────────────────────┐
│ Firestore + Security Rules Firebase Auth │
│ Cloud Messaging (FCM) │
└──────────────────────────────────────────────┘
▲ Firebase SDK (reads) ▲ REST (admin) + Admin SDK (functions)
│ │
Flutter app ──── POST /api/functions/{name} ────> Next.js portal, Vercel sin1
(donors, requesters) createRequest · respondToMatch (public pages + admin +
→ APK deleteAccount reviewRequest · confirmDonation)
LayerTechnologySource
MobileFlutter (Android; iOS build-only), Riverpod, go_router, cloud_firestoremobile/
WebNext.js App Router, TypeScript, Tailwind, shadcn/ui, next-intlfrontend/
Server logicThe portal's own Node server on Vercel Hobby (frontend/src/server/)ADR 0010
DataCloud Firestore + Security Rules, one emulator test per rulefirebase/
IdentityFirebase Auth: Google/Facebook in the app, Google or password for adminsADR 0002, DEC-017
PushFirebase Cloud Messaging, in the donor's own language—
Locationgeolocator, no map widgetDEC-004, ADR 0003

The rules that hold it together​

  • Clients read Firebase directly; the Security Rules are the only gate. Anything a client must not write goes through a function on the portal's server, and the rules refuse that write to every client.
  • Five functions (POST /api/functions/{name} with the caller's Firebase ID token):
    • createRequest (app): writes a request as PENDING;
    • reviewRequest (admin): approve, which runs matching and push, or reject;
    • respondToMatch (app): a donor's accept or decline;
    • confirmDonation (admin): records the donation and starts the cooldown;
    • deleteAccount (self): removes personal data and keeps anonymous counts.
  • Matching selects compatible, available, eligible donors within 10 km of the hospital, nearest first. ABO/Rh compatibility is a fixed lookup table, never user input (ADR 0004). Donors with no GPS still match by district.
  • Offline first: the app reads through Firestore's offline cache, and a donor's answer is queued on the phone until it can be sent.

Read further (in the repo)​