Critical Architectural Pivot
This is a very common enterprise requirement. Here is the Revised Day 4 specification, focusing on RESTful API definitions and the backend handlers.
Revised Architecture: The 3-Tier Serverless Stack
We are abandoning direct client access to Firestore.
Authenticates via Firebase Auth to get a temporary ID Token (Bearer Token). It makes HTTP requests to the API Gateway, putting that token in the Authorization header.
Sits in front of our Cloud Functions. It is configured with a security definition to validate the Firebase ID Token. If valid, it passes the request to the function; if not, it rejects with 401 Unauthorized.
Now act as RESTful API handlers. They trust the user ID passed down from the Gateway, perform business logic, and are the only things allowed to talk to Firestore.
A. API Gateway Security Configuration
Before defining the endpoints, we define the security scheme that applies to all of them.
Authorization: Bearer <firebase_id_token_here>
X-Apigateway-Api-UserInfo) that the backend Cloud Function can decode and trust.
B. API Endpoint Definitions & Function Specs
We need to build 5 specific Cloud Functions to act as handlers for our API routes.
1. The "Get Upload URL" Handler (Replacing direct upload)
Since the client cannot write to storage directly, it must ask the backend for a temporary, pre-authorized "Signed URL" to perform the upload.
/v1/reports/upload-url
{
"fileName": "bloodwork_oct26.pdf",
"contentType": "application/pdf"
}
- Auth Check: Decode gateway header. Ensure requestor has role: 'patient'. Get their UID.
- Validation: Ensure contentType is 'application/pdf' or 'image/jpeg'.
- Path Construction: Determine the target storage path: uploads/{patient'sAssignedDoctorId}/{patientUid}/{timestamp}_{fileName}. (Need to read patient profile first to get the doctor ID).
- GCS Operation: Use Cloud Storage Admin SDK to generate a V4 Signed PUT URL, valid for 15 minutes, restricted to that specific path and content type.
{
"uploadUrl": "https://storage.googleapis.com/...", // The pre-signed long URL
"expiresIn": 900
}
2. Patient: Get My Reports
/v1/patient/reports
- Auth Check: Decode header. Get Patient UID. Ensure role: 'patient'.
- DB Operation: Query Firestore: collection('users').doc(patientUid).collection('lab_reports').orderBy('uploadTimestamp', 'desc').limit(X).
- Success Output (HTTP 200): A JSON array of the report documents.
- Error Output: 403 Forbidden if role is wrong.
3. Doctor: Get My Patient List
/v1/doctor/patients
- Auth Check: Decode header. Get Doctor UID. Ensure role: 'doctor'.
- DB Operation: Query Firestore: collection('users').where('assignedDoctorId', '==', doctorUid).
- Success Output (HTTP 200): JSON array of patient profile documents (excluding sensitive internal fields).
4. Doctor: Get Specific Patient Report Details
/v1/doctor/patients/{patientId}/reports/{reportId}
- Auth Check: Decode header. Get Doctor UID. Ensure role: 'doctor'.
-
Security Verification (Crucial):
Read the target patient's profile: db.doc('users/' + patientId).
Verify that fetchedPatient.assignedDoctorId === requestingDoctorUid.
If not match: return 403 Forbidden (You are not assigned to this patient). - DB Operation: If verified, read db.doc('users/' + patientId + '/lab_reports/' + reportId).
- Success Output (HTTP 200): JSON object of the specific lab report.
5. Doctor: Schedule Appointment
/v1/appointments
{
"patientId": "uid_of_patient",
"startTime": "2023-10-28T10:00:00Z", // ISO 8601 string
"endTime": "2023-10-28T10:30:00Z",
"notes": "Initial consult"
}
- Auth Check: Doctor UID from token.
- Validation: Ensure startTime is in the future. Ensure patientId belongs to this doctor (similar check to #4 above).
- Data Prep: Read Doctor Name and Patient Name to prepare for denormalization.
- DB Operation: db.collection('appointments').add({ doctorId: requestingUid, patientId: req.body.patientId, ... }).
- Success Output (HTTP 201 Created): { "appointmentId": "new_auto_id" }
- Error Output: 400 Bad Request (invalid dates), 403 Forbidden (wrong patient).
C. The Background Event Functions
These components automate business logic, invisible to the user but driving application state.
Function 1: User Profile Seeder
Trigger: auth-onCreateUser (Firebase Authentication User Created Event)
Logic: Check if doc exists at /users/{uid}. If not, create with default data (Role: 'patient', AssignedDoctor: null).
{
"email": event.email,
"displayName": event.displayName || "New User",
"createdAt": admin.firestore.FieldValue.serverTimestamp(),
"role": "patient",
"assignedDoctorId": null
}Function 2: The Intelligent PDF Processor
Trigger: storage-onFinalize (Cloud Storage Object Finalized)
Filter: Path must match uploads/{doctorId}/{patientId}/{filename}.
| Step | Action | On Failure |
|---|---|---|
| Pre-validation | Check content type & path. | Log error "Invalid File". |
| Placeholder | Write { "status": "processing" } to Firestore. |
Log error. |
| AI Processing | Send to Document AI Form Parser. | Update status to "failed". |
| Parsing | Extract key-value pairs & test date. | Update status to "failed". |
| Finalize | Update Firestore with { "status": "processed", "parsedData": {...} }. |
Log write error. |
D. Architecture Diagrams
Visual representations of the system flow for Client, Doctor, and Background operations.
Figure 1: Client Side & API Security Flow
Flow: Mobile App -> API Gateway -> Cloud Functions -> Firestore/GCS
Workflow Narrative
- Auth: App authenticates user, receives Firebase ID Token.
- View Request: App requests reports via Gateway, attaching token to header.
- Gateway Security: API Gateway validates signature of the token with Firebase.
- Backend Query: The authorized Cloud Function queries Firestore for this specific user's data.
- Upload Request: Patient wants to upload. App asks API for permission.
- Path Logic: Backend function reads patient profile to determine correct folder path (e.g., uploads/dr_smith_id/patient_jones_id/).
- Signed URL: Function asks Cloud Storage for a temporary (e.g., 15-min) URL that allows a single PUT operation to that specific path.
- Direct Upload: The mobile app uses that temporary URL to upload the heavy PDF file directly to Google Cloud Storage, bypassing the API Gateway and Functions to save bandwidth/cost.
GCP Resource Inventory (View 1)
| Resource Name | Type | Purpose in this Flow |
|---|---|---|
| Firebase Authentication | Identity | Issues and validates the secure JWT (ID Tokens) used for API calls. |
| API Gateway | Networking/Security | The single entry point. Handles TLS termination, DDoS protection, and token validation before reaching compute. |
| Cloud Functions (2nd Gen) | Compute (HTTP) | Hosts the Node.js/Python API handlers (api-getPatientReports, api-getSignedUrl). |
| Cloud Firestore | Database (NoSQL) | Stores user profiles and metadata pointers to the lab reports. |
| Cloud Storage | Object Storage | Stores the actual raw PDF files. Generates temporary Signed URLs for secure direct access. |
Figure 2: Doctor View & Routing Logic
Flow: Doctor Actions -> Gateway Validation -> Role Check -> Compute Logic
Workflow Narrative
- API Request: Doctor app makes REST calls (e.g., to view patient list or schedule).
- List Query: The getDoctorPatients function performs a filtered query on Firestore to find users assigned to the requesting doctor's UID.
- Security Check (Crucial): When accessing specific patient data (viewing a report or making an appointment), the Cloud Function first reads the target patient's user document to verify the assignedDoctorId matches the requestor. If not, it throws a 403 Forbidden error.
- Write Operation: Once verified, the appointment is written to Firestore.
GCP Resource Inventory (View 2)
| Resource Name | Type | Purpose in this Flow |
|---|---|---|
| Firebase Authentication | Identity | Issues tokens asserting the user has the 'doctor' role. |
| API Gateway | Networking/Security | Protects doctor API routes. |
| Cloud Functions (2nd Gen) | Compute (HTTP) | Hosts API handlers and enforces business logic security checks. |
| Cloud Firestore | Database (NoSQL) | Stores patient relationships, lab report data, and appointment schedules. |
Figure 3: Event-Driven Background Compute
Flow: Upload Event -> Eventarc -> Processor Function -> DocAI -> Firestore Update
Workflow Narrative
- Event Emission: A file successfully finishes uploading to Cloud Storage (from Patient View, step 8). Storage emits an google.storage.object.finalize event.
- Trigger: Eventarc routes this event to the specific Cloud Function designed to handle it.
- Initial Status: The function immediately writes to Firestore to let the UI know processing has begun.
- AI Processing: The function streams the file from Storage to the Document AI API.
- Data Extraction: Document AI analyzes the PDF and returns a JSON object of key/value pairs.
- Finalize: The function sanitizes the JSON and updates the Firestore document, changing status from "processing" to "processed". The mobile app listens to this document and updates the UI automatically.
GCP Resource Inventory (View 3)
| Resource Name | Type | Purpose in this Flow |
|---|---|---|
| Cloud Storage | Object Storage | The trigger source. Holds the raw file. |
| Eventarc | Eventing | Captures the storage event and reliably delivers it to the Cloud Function. |
| Cloud Functions (2nd Gen) | Compute (Event) | The background worker that coordinates the AI and Database process. |
| Document AI | AI/ML API | The intelligence engine that converts unstructured PDF pixels into structured JSON data. |
| Cloud Firestore | Database (NoSQL) | Stores the final processed results for the frontend to consume. |
Summary of Revised Day 4
We have successfully redefined the application flow to adhere to a strict 3-Tier API Gateway architecture.
- The mobile app never touches Firestore or Cloud Storage directly.
- All user-initiated actions are mapped to secure REST API endpoints.
- We have detailed specifications for the 5 Cloud Functions that will handle these API requests.