Skip to content

feat: Firebase FCM push notification channel#78

Merged
Rashed99Azm merged 1 commit into
develop-backendfrom
feat/add-firebase-push-feature
Jun 25, 2026
Merged

feat: Firebase FCM push notification channel#78
Rashed99Azm merged 1 commit into
develop-backendfrom
feat/add-firebase-push-feature

Conversation

@Rashed99Azm

Copy link
Copy Markdown
Collaborator

Summary

Adds Firebase FCM as a fourth notification channel (Push = 3) using the existing INotificationChannelHandler plug-in model — no changes to NotificationGateway dispatch logic, the channel just registers itself.

Device token management

  • UserDeviceToken entity: one row per (UserId, DeviceId). DeviceId is a stable client-generated UUID; Token is the rotating FCM registration token. Upsert on DeviceId means re-installing the app refreshes the token without creating duplicates.
  • POST /api/me/device-tokens — register or refresh (requires auth, all non-anonymous roles)
  • DELETE /api/me/device-tokens/{deviceId} — unregister
  • EF migration: AddUserDeviceToken (unique index on (user_id, device_id), index on (user_id, is_active), index on token)

Push delivery

  • PushNotificationChannelSender fetches all active tokens for the recipient, sends a single FCM multicast, then bulk-deactivates any tokens FCM rejects as UNREGISTERED, INVALID_ARGUMENT, or SENDER_ID_MISMATCH — keeps the token table clean without a separate job.
  • FirebaseMessagingService wraps the FirebaseAdmin SDK v3.1.0 with a singleton guard (DefaultInstance ?? Create) safe for hot-reload. CancellationToken is forwarded to SendEachForMulticastAsync.
  • Firebase is opt-in: if Firebase:ProjectId or Firebase:ServiceAccountJson are empty, neither IFirebaseMessagingService nor PushNotificationChannelSender are registered — both APIs start cleanly without credentials.
  • MetaData added to RenderedNotification so notification handlers can forward context (postId, communityId, locale, templateCode) into the FCM data payload for client-side deep-linking.
  • NotificationChannel.Push added to all existing notification handlers (ExpertRegistrationApproved/Rejected, NewsPublished, ResourcePublished, CountryContentRequest Approved/Rejected).

Admin test endpoint

  • POST /api/admin/notifications/test-push (Internal API, Notification_Send permission) — fires a raw FCM multicast to a single token, returns { sent, failed }. Verified live: real device received the push (sent: 1, failed: 0).

Test plan

  • POST /api/me/device-tokens with a real FCM token → row appears in user_device_token with is_active = 1
  • Call again with same deviceId, new token → row updated (no duplicate), token refreshed
  • DELETE /api/me/device-tokens/{deviceId}is_active = 0
  • POST /api/admin/notifications/test-push with a valid FCM token → sent: 1, failed: 0, push received on device
  • Same endpoint with an invalid token → sent: 0, failed: 1, token row deactivated on next real dispatch
  • Remove Firebase section from appsettings → both APIs start without error, push channel silently skipped on dispatch
  • Trigger a notification that has Push in its channels (e.g. publish a news article) → FCM multicast fires alongside email/in-app, notification_log row created for Push channel

─────────────────────────────────────────────────────

Adds push as a first-class notification channel alongside email and in-app,
following the existing INotificationChannelHandler plug-in model.

- UserDeviceToken entity: one row per (UserId, DeviceId); token rotates,
  deviceId is stable. EF migration: AddUserDeviceToken
- POST /api/me/device-tokens — register/refresh FCM token (upsert by deviceId)
- DELETE /api/me/device-tokens/{deviceId} — unregister
- PushNotificationChannelSender: fetches active tokens, sends FCM multicast,
  deactivates stale tokens on UNREGISTERED/INVALID_ARGUMENT errors
- FirebaseMessagingService wraps FirebaseAdmin SDK with singleton guard
  (DefaultInstance ?? Create) and forwards CancellationToken
- Firebase section in appsettings; channel skipped cleanly when unconfigured
- MetaData added to RenderedNotification so handlers can pass deep-link
  context (postId, communityId, etc.) into FCM data payload
- NotificationChannel.Push added to all existing notification handlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Rashed99Azm Rashed99Azm merged commit ba8021d into develop-backend Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants