Skip to content

Latest commit

 

History

History
560 lines (485 loc) · 16.1 KB

File metadata and controls

560 lines (485 loc) · 16.1 KB

📊 ARCHITECTURE & DATA FLOW - COMPLETE SYSTEM DESIGN

System Overview

Your app now has a unified Firebase-first architecture with TinyDB as offline fallback:

┌─────────────────────────────────────────────────────────────┐
│                    GreenTime Mobile App                      │
│  (Flutter - iOS/Android/Windows)                             │
└──────────────────────┬──────────────────────────────────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
    ┌───▼────┐   ┌────▼────┐   ┌───▼────┐
    │Firebase │   │Firebase │   │TinyDB  │
    │  Auth   │   │Firestore│   │(Local) │
    │         │   │         │   │        │
    └─────────┘   └─────────┘   └────────┘
        │ User       │ Data       │ Backup
        │ Auth       │ Sync       │ Offline
        └────────────┼────────────┘
                     │
          ┌──────────▼──────────┐
          │ Single Source Truth  │
          │ (Firestore = Primary)│
          └─────────────────────┘

1️⃣ AUTHENTICATION FLOW

Sign Up Flow

User enters: email, password, username, role (parent/kid)
        │
        ├─► AuthService.signUp()
        │
        ├─► Try: FirebaseService.createUserWithEmailPassword()
        │
        ├─► Step 1: Create Firebase Auth user
        │   └─ Result: uid = "firebase_uid_123"
        │
        ├─► Step 2: Generate IDs
        │   ├─ personalId = "personal_uuid_456" (eco points tracking)
        │   └─ parentId = "parent_uuid_789" (only if role='parent')
        │
        ├─► Step 3: Create Firestore user document
        │   └─ Path: /users/{uid}
        │   └─ Fields: uid, email, username, role, personalId, parentId,
        │              linkedChildren=[], parentUids=[], createdAt, updatedAt
        │
        ├─► Step 4: Store backup in TinyDB
        │   └─ For offline access
        │
        ├─► Step 5: Verify in both places
        │   ├─ Check Firebase Auth user exists
        │   └─ Check Firestore document exists
        │
        └─► ✅ Return uid if verified, ❌ throw error if not

Sign In Flow

User enters: email, password
        │
        ├─► AuthService.signIn()
        │
        ├─► Try: FirebaseService.signInWithEmailPassword()
        │
        ├─► Step 1: Authenticate with Firebase Auth
        │   └─ Result: uid
        │
        ├─► Step 2: Fetch user role from Firestore
        │   └─ Query: /users/{uid}.role
        │
        ├─► Step 3: Store in TinyDB for quick access
        │   └─ Keys: current_user_uid, current_user_email, current_role
        │
        └─► ✅ Return uid

2️⃣ PARENT-CHILD LINKING FLOW

Initial Linking

Parent wants to link child
        │
        ├─► Parent navigates to AddChildScreen
        ├─► Enters child's username
        │
        ├─► FirebaseService.linkChildToParent(parentUid, childUid, ...)
        │
        ├─► VALIDATION:
        │   ├─ Check: Child exists in /users/{childUid}
        │   ├─ Check: childUid not already linked to another parent
        │   └─ Check: Parent exists in /users/{parentUid}
        │
        ├─► UPDATE PARENT DOCUMENT:
        │   └─ /users/{parentUid}
        │   └─ linkedChildren: FieldValue.arrayUnion([childUid])
        │
        ├─► UPDATE CHILD DOCUMENT:
        │   └─ /users/{childUid}
        │   └─ parentId: parentUid
        │   └─ parentEmail: parent_email
        │   └─ linkedAt: serverTimestamp()
        │
        ├─► BACKUP IN TINYDB:
        │   └─ linked_children: 'username1,username2,...'
        │
        └─► ✅ Both parent and child documents synced

After Linking Verification

✅ Parent dashboard now shows:
   └─ Badge with count of linkedChildren

✅ Parent can:
   ├─ See child name in "My Children"
   ├─ Create tasks for child
   └─ View child's completed tasks

✅ Child can:
   ├─ See linked parent name
   ├─ View tasks from parent
   └─ Complete and submit tasks

Persistence Across App Restarts

User logs in again
        │
        ├─► AuthService.signIn() → get uid
        │
        ├─► Read /users/{uid}.linkedChildren array
        │   (Firestore is source of truth)
        │
        └─► ✅ Linking remains intact
            (Parent sees same children)

3️⃣ TASK CREATION FLOW

Creating a Task

Parent wants to create task
        │
        ├─► ParentDashboard._addCustomTask()
        │
        ├─► User selects:
        │   ├─ Child (from linkedChildren dropdown)
        │   ├─ Title ("Water a Plant")
        │   ├─ Description
        │   ├─ Points (50)
        │   └─ Requires Proof? (yes/no)
        │
        ├─► TaskService.createTask(
        │    parentUid: "firebase_uid_parent",
        │    childUid: "firebase_uid_child",
        │    ...
        │   )
        │
        ├─► VALIDATION:
        │   ├─ Check: parentUid not empty
        │   ├─ Check: childUid not empty
        │   ├─ Check: Child exists in /users/{childUid}
        │   ├─ Check: childUid.parentId == parentUid ✨ KEY CHECK
        │   └─ If not linked: ❌ throw Exception
        │
        ├─► CREATE TASK DOCUMENT:
        │   └─ Path: /parents/{parentUid}/tasks/{auto_id}
        │   └─ Fields:
        │       ├─ parentId: parentUid
        │       ├─ childId: childUid
        │       ├─ title, description, points, requiresProof
        │       ├─ status: 'pending'
        │       ├─ createdAt: serverTimestamp()
        │       ├─ approvedByParent: false
        │       ├─ isSubmitted: false
        │       └─ proofPhotoURL: null
        │
        └─► ✅ taskId returned

Task Lifecycle

STATE 1: pending
├─ Parent created task
├─ Child sees task on dashboard
└─ Child has not started

STATE 2: submitted
├─ Child marked task complete
├─ If requiresProof=true: proof photo uploaded
└─ Waiting for parent approval

STATE 3: approved
├─ Parent reviewed and approved
├─ Points awarded to child
└─ Task complete

STATE 3: rejected
├─ Parent reviewed and rejected
├─ Child notified of reason
└─ Task can be resubmitted

4️⃣ REAL-TIME UPDATES FLOW

Badge Count Update

Parent opens dashboard
        │
        ├─► ParentDashboard.build()
        │
        ├─► StreamBuilder<DocumentSnapshot>(
        │    stream: /users/{parentUid}.snapshots()
        │   )
        │
        ├─► Listen to parent document changes
        │   ├─ Initial load: get linkedChildren.length
        │   └─ On change: linkedChildren array modified
        │
        ├─► SCENARIO: Child links during parent viewing dashboard
        │   ├─ Firestore: /users/{parentUid}.linkedChildren updated
        │   ├─ Stream: notifies UI immediately
        │   ├─ UI: Badge updates from 0 → 1
        │   └─ User: sees update instantly (NO restart needed)
        │
        └─► StreamBuilder rebuilds widget with new count

Task List Real-Time Update

Parent viewing task list
        │
        ├─► StreamBuilder<QuerySnapshot>(
        │    stream: /parents/{parentUid}/tasks/
        │   )
        │
        └─► Listen to tasks subcollection
            ├─ Child submits proof photo
            ├─ Firestore: task.status changes to 'submitted'
            ├─ Stream: notifies parent immediately
            └─ Parent: sees new submitted task in list

5️⃣ OFFLINE MODE FALLBACK

When Firebase Unavailable

┌─ Check: Is Firebase initialized?
│   │
│   ├─ YES: Use Firestore (primary)
│   │
│   └─ NO: Use TinyDB (fallback)
│
└─ TinyDB Storage Structure:
   ├─ users: {email: {uid, username, role, ...}}
   ├─ linked_children: 'child1,child2,...'
   ├─ current_user_uid: 'firebase_uid'
   ├─ current_user_email: 'user@example.com'
   ├─ current_role: 'parent'
   └─ tasks: {taskId: {title, status, ...}}

Sync When Online

When connection restored
        │
        ├─► FirebaseService.syncOfflineData(uid)
        │
        ├─► Read TinyDB:
        │   ├─ users data
        │   ├─ tasks data
        │   └─ linked_children
        │
        ├─► Compare with Firestore
        │   ├─ New tasks: upload to Firestore
        │   ├─ Modified data: sync updates
        │   └─ Conflicts: Firestore version wins
        │
        └─► TinyDB remains as backup cache

6️⃣ SECURITY & VALIDATION

Firestore Security Rules

match /users/{userId} {
  // User can read/write own profile
  allow read, write: if request.auth.uid == userId;
  
  // Create new user must have required fields
  allow create: if request.auth.uid != null && 
                   request.resource.data.uid == request.auth.uid &&
                   request.resource.data.email != null &&
                   request.resource.data.username != null &&
                   request.resource.data.role != null;
}

match /parents/{parentId}/tasks/{taskId} {
  // Parent can read/write own tasks
  allow read, write: if request.auth.uid == parentId;
  
  // Child can read tasks assigned to them
  allow read: if request.auth.uid == resource.data.childId;
}

Validation Hierarchy

1. Frontend Validation (Flutter)
   ├─ Email format
   ├─ Password strength
   ├─ Required fields filled
   └─ Quick user feedback

2. Backend Authentication (Firebase Auth)
   ├─ Email/password correct
   ├─ User exists
   └─ Session valid

3. Application Logic (Services)
   ├─ Linking: child not already linked
   ├─ Task: parent actually linked to child
   ├─ Permissions: user owns data
   └─ State: consistent with Firestore

4. Database Rules (Firestore)
   ├─ uid matches auth user
   ├─ email not null
   ├─ username not null
   └─ Only owner can write

7️⃣ DATA CONSISTENCY CHECKS

Invariants (Always True)

✅ If parent.linkedChildren contains childUid:
   └─ Then child.parentId == parentUid

✅ If task in /parents/{parentUid}/tasks/:
   └─ Then task.parentId == parentUid && task.childId exists

✅ If user.role == 'parent':
   └─ Then user.parentId is not null

✅ If user.role == 'kid':
   └─ Then user.parentId might be null (not yet linked)

✅ Every user must have:
   ├─ uid (Firebase UID)
   ├─ email (unique)
   ├─ username (display name)
   ├─ role ('parent' or 'kid')
   └─ createdAt (timestamp)

Breaking These Invariants = Bug

Example Bad State:
{
  parent.linkedChildren: ['child_uid_1'],  // Says child linked
  child.parentId: 'different_uid'          // But points to different parent
}
❌ INCONSISTENT - These should match!

Our code prevents this:
✅ Both updated atomically
✅ Validation checks before linking
✅ Error thrown if mismatch detected

8️⃣ PERFORMANCE OPTIMIZATIONS

Query Efficiency

✅ Get parent's children:
   └─ Read /users/{parentUid}
   └─ Get linkedChildren array length O(1)
   └─ Single document read

❌ Alternative (slow):
   └─ Query all tasks to count unique children
   └─ Multiple document reads O(n)

✅ Get child's tasks:
   └─ Query /parents/{parentUid}/tasks where childId==childUid
   └─ Uses index on (parentId, childId)
   └─ Fast range query

❌ Alternative (slow):
   └─ Get ALL tasks then filter in app
   └─ Wasteful network usage

Indexing

Recommended Composite Indexes:

1. /parents/{parentUid}/tasks
   ├─ parentId (equality)
   ├─ childId (equality)
   ├─ createdAt (descending)

2. /users
   ├─ parentId (equality)
   ├─ linkedChildren (array-contains)

3. /parents/{parentUid}/tasks
   ├─ status (equality)
   ├─ createdAt (descending)

9️⃣ ERROR HANDLING FLOW

User Not Found

Task: Find user by username
        │
        ├─► Query /users where username == "john"
        │
        ├─► No document found
        │
        ├─► AuthService.findUserByUsername() returns null
        │
        ├─► Parent sees: "Child username not found"
        │
        └─► User: Try different username or check spelling

Linking Validation Fails

Task: Parent tries to link child who's already linked
        │
        ├─► FirebaseService.linkChildToParent()
        │
        ├─► Read /users/{childUid}
        │
        ├─► Check child.parentId
        │
        ├─► Found: child.parentId = 'other_parent_uid'
        │
        ├─► throw Exception('Child already linked to another parent')
        │
        ├─► Parent sees: "Child already linked to another parent"
        │
        └─► User: Child must unlink first

Task Creation Fails

Task: Parent tries to create task for unlinked child
        │
        ├─► TaskService.createTask()
        │
        ├─► Validate: child.parentId == parentUid
        │
        ├─► Found: child.parentId != parentUid
        │
        ├─► throw Exception('Child not linked to parent')
        │
        ├─► Parent sees: "Child not linked to parent"
        │
        └─► User: Link child first via AddChildScreen

🔟 TESTING VERIFICATION SCRIPT

Test Case 1: Complete Flow

// 1. Create parent account
var parentUid = await AuthService.signUp(
  "parent@test.com", "password123", "parent_name", "parent"
);
assert(parentUid != null);
// ✅ Parent appears in Firebase Auth
// ✅ Document in /users/{parentUid} created

// 2. Create child account  
var childUid = await AuthService.signUp(
  "child@test.com", "password456", "child_name", "kid"
);
assert(childUid != null);
// ✅ Child appears in Firebase Auth
// ✅ Document in /users/{childUid} created

// 3. Link child to parent
await FirebaseService.linkChildToParent(
  parentUid: parentUid,
  childUid: childUid,
  childUsername: "child_name",
  childEmail: "child@test.com"
);
// ✅ /users/{parentUid}.linkedChildren contains childUid
// ✅ /users/{childUid}.parentId == parentUid

// 4. Create task
var taskId = await TaskService.createTask(
  parentUid: parentUid,
  childUid: childUid,
  title: "Water Plants",
  description: "Water the garden",
  points: 50,
  requiresProof: true
);
assert(taskId != null);
// ✅ Task exists in /parents/{parentUid}/tasks/{taskId}
// ✅ task.childId == childUid
// ✅ task.parentId == parentUid

// 5. Get tasks as child
List<Task> tasks = await TaskService.getChildTasks(parentUid, childUid);
assert(tasks.isNotEmpty);
assert(tasks[0].title == "Water Plants");
// ✅ Child can retrieve their tasks
// ✅ No cross-contamination with other children's tasks

DEPLOYMENT READINESS CHECKLIST

  • Firebase user creation verified
  • Linking system unified
  • Task validation implemented
  • Real-time listeners ready
  • Offline fallback configured
  • Security rules updated
  • Error messages improved
  • Theme system colors replaced
  • QA tested with multiple accounts
  • Performance load tested
  • Ready for production