Implement FCM push notification dispatch for pause events
epic-peer-mentor-pause-management-core-workflows-task-012 — Integrate FCM push notification dispatch into PauseNotificationService for coordinator-targeted push notifications on pause and reactivation events. Implement coordinator FCM token resolution from the user profiles table, build notification payloads with deep link data (route to coordinator pause roster filtered to the specific mentor), and call the FCM HTTP v1 API via Supabase Edge Function or directly. Handle token-not-found and rate-limit errors gracefully.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
Create a Supabase Edge Function named 'dispatch-pause-notification' that accepts a JSON body with { event_type, mentor_id, mentor_name, coordinator_id, organisation_id }. The function should: (1) validate the caller's JWT org claim matches organisation_id, (2) query device_tokens for active tokens for coordinator_id, (3) build FCM messages using the HTTP v1 API format with the service account JWT (obtained via Google's OAuth2 token endpoint using the service account key), (4) dispatch all tokens in parallel using Promise.all, (5) process results and deactivate invalid tokens. In the Flutter PauseNotificationService, invoke the Edge Function using Supabase's functions.invoke() method — this automatically attaches the user's JWT. For deep link routing on notification tap, use the flutter_local_notifications or firebase_messaging onMessageOpenedApp handler to parse the data.deep_link field and push the appropriate named route.
Ensure the retry logic in the Edge Function uses Deno's built-in setTimeout wrapped in a Promise — do not use a blocking sleep.
Testing Requirements
Write unit tests for the Supabase Edge Function covering: (1) correct FCM payload structure built for pause_activated event, (2) correct payload for mentor_reactivated event, (3) invalid/not-found FCM token triggers device_tokens deactivation, (4) rate-limit 429 triggers one retry with delay, (5) second retry failure logs error without throwing, (6) JWT with wrong organisation claim is rejected before token resolution. Write a Flutter unit test for PauseNotificationService asserting: (7) Edge Function is invoked with the correct coordinator_id and event type after pause/reactivation. Test the no-tokens case asserting the function returns without error. Use a mock FCM endpoint in tests — do not call the real FCM API.
Concurrent status transitions (e.g., coordinator and automated scheduler both attempting to update the same mentor's status simultaneously) may produce race conditions or inconsistent state in the database, leading to audit log gaps or incorrect notifications.
Mitigation & Contingency
Mitigation: Implement all status transitions as atomic Postgres RPC functions with optimistic locking (version column or updated_at check). Use database-level constraints rather than application-level guards as the final enforcement point.
Contingency: Add a compensation job that reconciles status and log table consistency on each nightly scheduler run, surfacing any discrepancies to coordinator dashboards.
The coordinator-to-mentor assignment relationship may not always be 1:1 or may be stale (coordinator reassigned after a pause was set), causing notifications to be sent to the wrong coordinator or not sent at all.
Mitigation & Contingency
Mitigation: Query the assignment relationship at notification dispatch time rather than caching it at pause creation time. Add a fallback to notify the chapter administrator if no active coordinator assignment exists.
Contingency: Log all undeliverable notification attempts with the originating mentor ID so administrators can manually follow up, and surface undelivered notification counts on the coordinator dashboard.
The CoordinatorPauseRosterScreen may load slowly for coordinators managing large rosters with many concurrent certification expiry queries, degrading usability on low-bandwidth mobile connections.
Mitigation & Contingency
Mitigation: Use a single Supabase RPC that joins mentor status, certification expiry, and assignment data in one query rather than N+1 individual calls. Implement pagination with a configurable page size and skeleton loading states.
Contingency: Add an offline cache of the last-fetched roster state using Riverpod with SharedPreferences, ensuring coordinators can at minimum view stale data when connectivity is poor.