Skip to content

feat(fcm): integrate Firebase Cloud Messaging for push notifications - #29

Merged
GRACENOBLE merged 4 commits into
mainfrom
19-feat-integrate-firebase-cloud-messaging-for-push-notifications
Jun 22, 2026
Merged

GRACENOBLE merged 4 commits into
mainfrom
19-feat-integrate-firebase-cloud-messaging-for-push-notifications

Conversation

@GRACENOBLE

@GRACENOBLE GRACENOBLE commented Jun 15, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Backend (Go): NotificationSender + FCMTokenRepository interfaces, fcm_tokens migration, FCM token repository (with upsert), POST /api/v1/fcm/register and DELETE /api/v1/fcm/unregister endpoints, FCM sender via Admin SDK HTTP v1. Refactored pkg/firebase to share a single firebasesdk.App between auth and messaging clients.
  • Web (Next.js): lib/fcm.ts (permission + token), lib/useFCM.ts hook (registers token with backend), public/firebase-messaging-sw.js service worker for background messages. Firebase JS SDK installed.
  • Mobile (Android): MyFirebaseMessagingService handles onNewToken (POSTs to backend) and onMessageReceived (shows NotificationCompat notification). FcmRegistrationPayload serialisable data class. Manifest updated with POST_NOTIFICATIONS permission and service registration.
  • Docs: Fixed stale backend/docs/auth.md, created fcm.md in all three layers, updated all three _index.md files.

Test plan

  • cd backend && go vet ./... && make test — all pass including 4 FCM repository integration tests (Testcontainers) and 5 FCM handler unit tests
  • cd web && pnpm lint && pnpm build && pnpm test — 4 FCM Vitest unit tests pass; no TypeScript errors
  • cd mobile && ./gradlew lint && ./gradlew test — 4 FcmRegistrationPayloadTest JUnit tests pass; lint clean
  • Apply migration: cd backend && make migrate-up
  • Set FIREBASE_PROJECT_ID + FIREBASE_SERVICE_ACCOUNT_JSON in backend/.env and set NEXT_PUBLIC_FIREBASE_* vars in web/.env.local to test end-to-end token registration

Notes

  • FCM routes are only registered when h.fcmTokenRepo != nil (always true after this PR since NewFCMTokenRepository is always called in server.go) — auth is enforced via FirebaseAuth middleware inherited from the /api/v1 group
  • Android google-services.json + com.google.gms.google-services Gradle plugin must be added for Firebase to initialise at runtime (noted in mobile/docs/fcm.md)
  • Web service worker receives Firebase config via postMessage; the caller must send { type: 'FIREBASE_CONFIG', config } after registering the worker

Closes #19

Summary by CodeRabbit

  • New Features
    • Added Firebase Cloud Messaging (FCM) support across backend, mobile, and web, including backend device token persistence and notification sending
    • Introduced authenticated endpoints to register and unregister FCM device tokens
    • Mobile/Web now handle token acquisition and registration, plus foreground notification support
  • Database
    • Added an fcm_tokens table with upsert and user-token lookup
  • Documentation
    • Added/updated FCM documentation for backend, mobile, and web
  • Tests
    • Added unit and integration test coverage for token handling and repository behavior

Backend:
- Refactor pkg/firebase to share a single App instance between auth and
  messaging clients (NewApp → NewAuthClient + NewMessagingClient)
- Add NotificationSender and FCMTokenRepository interfaces in usecase/
- Add FCMToken domain entity and fcm_tokens migration (uuid PK, user_id,
  token UNIQUE, platform, created_at) with user_id index
- Implement FCMTokenRepository in infrastructure/database/postgres with
  upsert semantics on token conflict
- Add POST /api/v1/fcm/register and DELETE /api/v1/fcm/unregister handlers,
  guarded by FirebaseAuth middleware; wired only when Firebase is configured
- Wire FCMSender and FCMTokenRepository through bootstrap.App → server → Handler
- Add 5 handler unit tests (mock repo) and 4 repository integration tests
  (Testcontainers / real PostgreSQL)
- Regenerate Swagger docs with FCM endpoint annotations

Web:
- Install firebase JS SDK
- Add lib/fcm.ts: requestPermissionAndGetToken() and onForegroundMessage()
- Add lib/useFCM.ts hook: requests permission, registers token with backend
- Add public/firebase-messaging-sw.js service worker for background messages
- Add 4 Vitest unit tests (vi.hoisted mocks for firebase/app and firebase/messaging)

Mobile:
- Add Firebase BOM 33.7.0 + firebase-messaging-ktx to version catalog and build.gradle
- Add MyFirebaseMessagingService: onNewToken (posts to backend), onMessageReceived
  (shows NotificationCompat notification, channel creation guarded for API 26+)
- Add FcmRegistrationPayload (@serializable data class)
- Register service in AndroidManifest with MESSAGING_EVENT intent filter
- Add POST_NOTIFICATIONS and INTERNET permissions
- Add 4 JUnit unit tests for FcmRegistrationPayload serialisation

Docs:
- Fix stale backend/docs/auth.md (verifier wiring was incorrect)
- Create backend/docs/fcm.md, web/docs/fcm.md, mobile/docs/fcm.md
- Update all three _index.md files

Closes #19
@GRACENOBLE GRACENOBLE linked an issue Jun 15, 2026 that may be closed by this pull request
8 tasks
@github-actions github-actions Bot added area: backend Go REST API area: web Next.js web app area: mobile Android app (Kotlin + Jetpack Compose) type: chore Cleanup or maintenance tasks labels Jun 15, 2026
@coderabbitai

coderabbitai Bot commented Jun 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@GRACENOBLE, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 14 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e21bd2cf-78b4-4037-a943-75c7f8cf615e

📥 Commits

Reviewing files that changed from the base of the PR and between 243ea93 and a6e49b1.

📒 Files selected for processing (12)
  • .github/workflows/mobile-ci.yml
  • backend/internal/infrastructure/database/migrations/20260615220942_add_fcm_tokens.sql
  • backend/internal/infrastructure/database/postgres/fcm_token_repository.go
  • backend/internal/infrastructure/database/postgres/fcm_token_repository_test.go
  • backend/internal/server/server.go
  • backend/internal/transport/handlers/fcm_handler.go
  • backend/internal/transport/handlers/fcm_handler_test.go
  • backend/internal/usecase/notification.go
  • mobile/app/src/main/java/com/company/template/MainActivity.kt
  • web/lib/useFCM.test.ts
  • web/lib/useFCM.ts
  • web/public/firebase-messaging-sw.js
📝 Walkthrough

Walkthrough

Adds end-to-end Firebase Cloud Messaging integration across all three platforms. The backend gains a new FCMToken domain model, usecase interfaces, a PostgreSQL token repository with migration, a refactored Firebase pkg (split into app.go, admin.go, messaging.go), authenticated REST endpoints for token registration/unregistration, and updated Swagger specs. The web client adds an FCM helper module, a useFCM React hook, and a background service worker. The Android app adds MyFirebaseMessagingService with token registration and notification display.

Changes

Backend FCM Integration

Layer / File(s) Summary
Domain model and usecase interfaces
backend/internal/domain/fcm_token.go, backend/internal/usecase/notification.go
Defines the FCMToken struct and the NotificationSender and FCMTokenRepository interfaces that all other backend layers depend on.
Firebase pkg refactor: app init, auth, and messaging clients
backend/pkg/firebase/app.go, backend/pkg/firebase/admin.go, backend/pkg/firebase/messaging.go
Extracts Firebase app initialization into a new NewApp, refactors NewAuthClient to accept a pre-initialized *firebase.App, and adds NewMessagingClient wrapping SendToToken and SendMulticast.
Database schema and PostgreSQL repository
backend/internal/infrastructure/database/migrations/20260615220942_add_fcm_tokens.sql, backend/internal/infrastructure/database/postgres/fcm_token_repository.go, backend/internal/infrastructure/database/postgres/fcm_token_repository_test.go
Adds the fcm_tokens migration (UUID PK, unique token, user_id index, rollback), implements SaveToken upsert, GetTokensByUserID, and DeleteToken, plus integration tests covering all four scenarios.
Handler struct, FCM HTTP handlers, route wiring, and server bootstrap
backend/internal/transport/handlers/handler.go, backend/internal/transport/handlers/fcm_handler.go, backend/internal/transport/handlers/routes.go, backend/internal/server/server.go, backend/internal/bootstrap/bootstrap.go, backend/internal/transport/handlers/fcm_handler_test.go, backend/internal/transport/handlers/health_handler_test.go
Extends Handler and NewHandler with fcmSender/fcmTokenRepo, implements RegisterFCMToken and UnregisterFCMToken with Firebase claims auth, conditionally registers routes when the repo is non-nil, wires FCMTokenRepository and FCMSender through server and bootstrap.
Swagger spec and backend docs
backend/docs/swagger/docs.go, backend/docs/swagger/swagger.json, backend/docs/swagger/swagger.yaml, backend/docs/fcm.md, backend/docs/auth.md, backend/docs/_index.md
Adds registerFCMTokenRequest/unregisterFCMTokenRequest definitions and the two new BearerAuth-secured FCM path specs to all Swagger formats; adds backend/docs/fcm.md and updates auth and index docs.

Web FCM Integration

Layer / File(s) Summary
FCM client library, service worker, and useFCM hook
web/package.json, web/lib/fcm.ts, web/lib/useFCM.ts, web/public/firebase-messaging-sw.js
Adds the firebase npm dependency, lazy-initialized Firebase app, requestPermissionAndGetToken and onForegroundMessage helpers, a background message service worker with FIREBASE_CONFIG injection, and the useFCM hook that POSTs the token to the backend.
Web FCM tests and docs
web/lib/fcm.test.ts, web/docs/fcm.md, web/docs/_index.md
Adds Vitest tests for permission/granted/denied paths and onForegroundMessage, plus a comprehensive docs page and index entry.

Mobile (Android) FCM Integration

Layer / File(s) Summary
Android FCM Gradle build configuration and gitignore
mobile/gradle/libs.versions.toml, mobile/app/build.gradle.kts, mobile/build.gradle.kts, mobile/.gitignore
Adds firebaseBom and googleServices version catalog entries, Firebase BOM and messaging-ktx library dependencies, Google Services Gradle plugin to both module and root build files, and adds google-services.json to the gitignore.
Android manifest permissions, service registration, and FCM implementation
mobile/app/src/main/AndroidManifest.xml, mobile/app/src/main/java/com/company/template/fcm/FcmRegistrationPayload.kt, mobile/app/src/main/java/com/company/template/fcm/MyFirebaseMessagingService.kt
Adds POST_NOTIFICATIONS and INTERNET permissions, registers MyFirebaseMessagingService with the MESSAGING_EVENT intent filter, defines FcmRegistrationPayload data class, and implements the service for onNewToken (HTTP POST to backend) and onMessageReceived (foreground notification display).
Android MainActivity notification permission and FCM token logging
mobile/app/src/main/java/com/company/template/MainActivity.kt
Adds runtime permission request for POST_NOTIFICATIONS on API 33+, gates Sentry initialization via a new shouldInitSentry helper, and retrieves and logs the FCM token on app start.
Android FCM payload tests and docs
mobile/app/src/test/java/com/company/template/fcm/FcmRegistrationPayloadTest.kt, mobile/docs/fcm.md, mobile/docs/_index.md
Adds FcmRegistrationPayloadTest covering serialization, defaults, override, and round-trip, plus the FCM docs page and updated index.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over Client,FCMTokenRepository: Token Registration Flow
  end
  participant Client as Client (Web/Android)
  participant useFCM as useFCM / MyFirebaseMessagingService
  participant FirebaseSDK as Firebase SDK
  participant BackendAPI as POST /api/v1/fcm/register
  participant FCMTokenRepository as FCMTokenRepository (Postgres)

  Client->>useFCM: mount / onNewToken callback
  useFCM->>FirebaseSDK: requestPermission + getToken / FirebaseMessagingService.onNewToken
  FirebaseSDK-->>useFCM: FCM registration token
  useFCM->>BackendAPI: POST {token, platform} + Bearer idToken
  BackendAPI->>BackendAPI: validate FirebaseClaimsKey
  BackendAPI->>FCMTokenRepository: SaveToken(userID, token, platform) upsert
  FCMTokenRepository-->>BackendAPI: ok
  BackendAPI-->>useFCM: 200 {message: "FCM token registered"}
Loading
sequenceDiagram
  rect rgba(255, 200, 150, 0.5)
    Note over bootstrap,NotificationSender: Backend Firebase Initialization
  end
  participant bootstrap
  participant NewApp as firebase.NewApp
  participant NewAuthClient as firebase.NewAuthClient
  participant NewMessagingClient as firebase.NewMessagingClient
  participant server

  bootstrap->>NewApp: NewApp(ctx, projectID, credentialsJSON)
  NewApp-->>bootstrap: *firebase.App
  bootstrap->>NewAuthClient: NewAuthClient(ctx, app)
  NewAuthClient-->>bootstrap: FirebaseAdminClient → App.Firebase
  bootstrap->>NewMessagingClient: NewMessagingClient(ctx, app)
  NewMessagingClient-->>bootstrap: NotificationSender → App.FCMSender
  bootstrap->>server: NewServer(app) with FCMSender + FCMTokenRepository
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 Hop, hop — a token arrives on the wire,
Saved in Postgres, the users' devices conspire.
The Android wakes, the browser blinks bright,
A push notification dances through the night.
Three platforms aligned, the FCM dream entire! 🔔

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(fcm): integrate Firebase Cloud Messaging for push notifications' accurately and concisely summarizes the main change—integrating FCM across the fullstack application for push notifications.
Linked Issues check ✅ Passed The PR comprehensively implements all backend, web, mobile, and documentation objectives from issue #19, including Firebase integration, token management, endpoints, service workers, handlers, and comprehensive test coverage across all layers.
Out of Scope Changes check ✅ Passed All changes are directly aligned with FCM integration objectives. The PR includes necessary backend infrastructure (migrations, repositories, handlers), frontend implementations (web hooks, mobile service), supporting changes (Swagger docs, handler updates), and documentation—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 19-feat-integrate-firebase-cloud-messaging-for-push-notifications

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/docs/auth.md (1)

55-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update NewAuthClient docs to match current API.

Lines 55-63 still describe NewAuthClient(ctx, projectID, credentialsJSON) and in-function SDK init, but the current implementation accepts a prebuilt Firebase app (NewAuthClient(ctx, app *firebasesdk.App)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/docs/auth.md` around lines 55 - 63, Update the `NewAuthClient`
documentation in the auth.md file to reflect the current API. The function
signature should show it accepts a context and a prebuilt Firebase app
(firebasesdk.App) instead of projectID and credentialsJSON parameters. Remove
the documentation describing SDK initialization with credentialsJSON and
Application Default Credentials fallback, as the Firebase app is now passed in
as an already-initialized dependency rather than being created within the
function.
🧹 Nitpick comments (1)
backend/docs/fcm.md (1)

84-90: ⚡ Quick win

Avoid showing swallowed errors in the bootstrap example.

Lines 85-88 ignore errors (_), which is risky as a copy/paste example for backend code.

Suggested doc snippet update
 if cfg.FirebaseProjectID != "" {
-    fbApp, _  := firebase.NewApp(ctx, cfg.FirebaseProjectID, cfg.FirebaseServiceAccountJSON)
-    authClient, _ := firebase.NewAuthClient(ctx, fbApp)
-    msgClient, _  := firebase.NewMessagingClient(ctx, fbApp)
+    fbApp, err := firebase.NewApp(ctx, cfg.FirebaseProjectID, cfg.FirebaseServiceAccountJSON)
+    if err != nil { return nil, err }
+    authClient, err := firebase.NewAuthClient(ctx, fbApp)
+    if err != nil { return nil, err }
+    msgClient, err := firebase.NewMessagingClient(ctx, fbApp)
+    if err != nil { return nil, err }
     app.Firebase  = authClient
     app.FCMSender = msgClient
 }

As per coding guidelines, "Return errors up the stack — never swallow them".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/docs/fcm.md` around lines 84 - 90, The documentation example in the
Firebase initialization block is ignoring errors returned from
firebase.NewApp(), firebase.NewAuthClient(), and firebase.NewMessagingClient()
by using the blank identifier `_`. Replace the underscore error assignments with
proper error variable declarations and add error handling checks for each call.
After each function invocation, check if the error is not nil and handle it
appropriately (either by logging, returning the error up the stack, or exiting
gracefully). This ensures the documentation example follows the coding guideline
of returning errors up the stack rather than swallowing them, making it a better
reference for backend code patterns.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/docs/fcm.md`:
- Line 3: The last_verified field in backend/docs/fcm.md on line 3 contains a
future date of 2026-06-16 instead of the actual verification date. Update the
last_verified value to reflect the actual date when the documentation was last
verified, ensuring it is not a future date relative to today.

In
`@backend/internal/infrastructure/database/migrations/20260615220942_add_fcm_tokens.sql`:
- Around line 2-8: The `platform` column in the fcm_tokens table is defined as
unconstrained TEXT, allowing invalid values to be persisted in the database. Add
a CHECK constraint to the `platform` column definition in the CREATE TABLE
fcm_tokens statement to restrict it to only valid platform enum values (such as
'ios', 'android', and 'web'). This enforces data integrity at the schema layer
and prevents invalid platform values from being stored regardless of
handler-level validation.

In
`@backend/internal/infrastructure/database/postgres/fcm_token_repository_test.go`:
- Around line 60-61: Remove the blank identifier (_) that ignores errors
returned by GetTokensByUserID method calls. At each affected site in the
fcm_token_repository_test.go file—lines 60–61 (anchor), lines 75 (sibling), and
lines 80 (sibling)—capture the error return value and check it explicitly using
the test helper (such as t.Fatalf or require.NoError) to fail the test if an
error occurs. This ensures that integration failures in repository calls are not
masked and the test properly validates the behavior.

In `@backend/internal/server/server.go`:
- Line 37: The FCM route gating mechanism is ineffective because fcmTokenRepo is
unconditionally created at line 37 via postgres.NewFCMTokenRepository regardless
of Firebase configuration, and the nil check at line 55 cannot properly gate the
routes. Move the creation of fcmTokenRepo inside a conditional block that checks
whether Firebase is available, so the repo is only instantiated when Firebase is
actually configured, then pass this conditionally-created fcmTokenRepo to the
route registration at line 55.

In `@backend/internal/transport/handlers/fcm_handler.go`:
- Around line 72-85: The unregisterFCMTokenRequest handler extracts the
authenticated FirebaseToken from the context but fails to use it when deleting
the FCM token, creating a security vulnerability where any exposed token can be
revoked by another user. Extract the user ID from the authenticated token (the
val variable that is cast to usecase.FirebaseToken at line 73), and pass both
the user ID and the token to the h.fcmTokenRepo.DeleteToken method call. Update
the repository's DeleteToken method signature to accept both user_id and token
parameters, and modify the underlying SQL query to enforce deletion only when
both the user_id and token match (DELETE FROM fcm_tokens WHERE user_id = $1 AND
token = $2).

In `@backend/internal/transport/handlers/routes.go`:
- Around line 68-71: The FCM routes (RegisterFCMToken and UnregisterFCMToken)
are currently gated only on the presence of h.fcmTokenRepo, but since this repo
is always created in the server setup regardless of Firebase configuration, the
routes register even when Firebase is disabled. Replace the condition checking
h.fcmTokenRepo != nil with a proper check for Firebase availability or
enablement (likely a configuration flag or boolean field on the handler that
reflects whether Firebase is actually configured and enabled), ensuring the
routes only register when Firebase is actually available.

In `@backend/pkg/firebase/app.go`:
- Around line 14-20: The NewApp function does not validate that the projectID
parameter is non-empty before proceeding with Firebase client initialization,
causing misconfiguration to be discovered downstream rather than at bootstrap.
Add validation at the start of the NewApp function to check if projectID is an
empty string and return an error immediately if it is, ensuring fast failure
during configuration and clearer error messaging for users who have
misconfigured the application.

In `@backend/pkg/firebase/messaging.go`:
- Around line 19-21: The NewMessagingClient function does not validate that the
app parameter is non-nil before calling app.Messaging(ctx), which will cause a
nil pointer dereference panic if a nil Firebase app is passed. Add an explicit
nil check at the beginning of the NewMessagingClient function to guard against
this case, and return an appropriate error if app is nil before attempting to
call app.Messaging(ctx).

In
`@mobile/app/src/main/java/com/company/template/fcm/MyFirebaseMessagingService.kt`:
- Around line 46-49: The FCM registration request built in
MyFirebaseMessagingService using Request.Builder() for the /api/v1/fcm/register
endpoint is missing authorization headers. The backend endpoint is auth-guarded
and expects a bearer token, but the request currently sends none, causing a 401
response. Add an authorization header to the Request.Builder() call by chaining
a .header() method that includes a bearer token (obtained from your
authentication provider or token storage mechanism) to satisfy the backend's
authentication contract.
- Line 47: The backendUrl is being used directly in the .url() method call
without validating that it uses the HTTPS scheme, which violates the requirement
that all network calls must use HTTPS. Add scheme validation logic before the
URL is built to ensure that backendUrl starts with "https://" and reject or
throw an error if it uses HTTP or any other non-HTTPS scheme. Perform this
validation before passing backendUrl to the .url() method call for the FCM
register endpoint.

In `@mobile/docs/_index.md`:
- Line 3: The `last_verified` field in both documentation files is set to a
future date (2026-06-16), which is after the current date (2026-06-15) and
creates inaccurate metadata. Update the `last_verified` field in
mobile/docs/_index.md at line 3 from 2026-06-16 to the actual verification date
(a non-future date). Similarly, update the `last_verified` field in
mobile/docs/fcm.md at line 3 from 2026-06-16 to the actual verification date (a
non-future date). Both updates should use the same date to reflect when these
docs were actually verified.

In `@web/docs/fcm.md`:
- Around line 19-27: Add the language identifier `env` to the opening fence of
the code block containing the Firebase environment variables in web/docs/fcm.md
(lines 19-27). Change the opening ``` to ```env to comply with the markdownlint
MD040 rule that requires fenced code blocks to have a language identifier.

In `@web/lib/fcm.ts`:
- Around line 25-37: The requestPermissionAndGetToken() function does not handle
errors from navigator.serviceWorker.register() and getToken() calls, which can
reject due to unsupported environments, invalid VAPID keys, or permission
failures, resulting in unhandled promise rejections. Wrap the service worker
registration and token generation logic in a try-catch block, catching any
errors from both the register() call and the getToken() call, and return null
when an error occurs to gracefully handle these failure cases.
- Around line 31-37: The service worker at `web/public/firebase-messaging-sw.js`
requires a `FIREBASE_CONFIG` message to initialize Firebase before
`firebase.messaging()` becomes usable, but the token retrieval logic in the
`getToken` call does not send this message. After registering the service worker
and before calling `getToken(messaging, ...)`, send a `FIREBASE_CONFIG` message
to all clients of the registered service worker, passing the Firebase app
configuration as the message payload. This ensures the service worker
initializes Firebase properly and can handle background messaging.

In `@web/lib/useFCM.ts`:
- Around line 14-44: Create a Vitest unit test file for the useFCM hook
(following the naming convention web/lib/useFCM.test.ts or similar) and add test
cases to verify the hook's behavior. The tests should cover: the FCM token
registration flow when requestPermissionAndGetToken succeeds and fails, proper
inclusion of the Authorization header when idToken is provided and omission when
it is not, the cleanup behavior of the returned function (setting cancelled flag
and calling unsub), and the onMessage handler registration when onMessage
callback is provided. Ensure all tests follow Vitest conventions and verify both
the async registration behavior and the effect cleanup mechanism of the useFCM
hook.
- Around line 36-43: The useEffect hook in the onForegroundMessage setup returns
early when onMessage is undefined, but this early return prevents the cleanup
function from running on unmount, leaving the cancelled flag unset and allowing
pending async work to continue after component unmount. Restructure the effect
to always return a cleanup function that sets cancelled = true, regardless of
whether onMessage is defined. The conditional check for onMessage should guard
the subscription setup (the onForegroundMessage call) but not prevent the
cleanup function from being returned.
- Around line 24-34: The fetch call to '/api/v1/fcm/register' in the
requestPermissionAndGetToken().then() callback does not validate the response
status or handle network/HTTP failures, nor does it enforce the auth-guard
requirement when idToken is missing. Add error handling to check the response
status (response.ok or similar) and handle failures appropriately, and only
proceed with the registration request when idToken is present, otherwise skip or
handle the missing token case.

In `@web/public/firebase-messaging-sw.js`:
- Around line 8-10: The firebase.initializeApp() call in the FIREBASE_CONFIG
event handler can throw a duplicate-app error if the message is received
multiple times. Guard the initialization by checking firebase.apps.length before
calling firebase.initializeApp(event.data.config). Only execute the
initializeApp() and firebase.messaging() calls when firebase.apps.length is 0,
ensuring the app is only initialized once.
- Around line 1-2: The importScripts calls in the service worker are loading
Firebase SDK version 11.0.0, but the main app depends on firebase@^12.14.0,
creating a version mismatch that can cause runtime integration failures. Update
both importScripts URLs in the firebase-app-compat.js and
firebase-messaging-compat.js lines to use a version from the 12.x series
(12.14.0 or later) to match your app's Firebase dependency version.

---

Outside diff comments:
In `@backend/docs/auth.md`:
- Around line 55-63: Update the `NewAuthClient` documentation in the auth.md
file to reflect the current API. The function signature should show it accepts a
context and a prebuilt Firebase app (firebasesdk.App) instead of projectID and
credentialsJSON parameters. Remove the documentation describing SDK
initialization with credentialsJSON and Application Default Credentials
fallback, as the Firebase app is now passed in as an already-initialized
dependency rather than being created within the function.

---

Nitpick comments:
In `@backend/docs/fcm.md`:
- Around line 84-90: The documentation example in the Firebase initialization
block is ignoring errors returned from firebase.NewApp(),
firebase.NewAuthClient(), and firebase.NewMessagingClient() by using the blank
identifier `_`. Replace the underscore error assignments with proper error
variable declarations and add error handling checks for each call. After each
function invocation, check if the error is not nil and handle it appropriately
(either by logging, returning the error up the stack, or exiting gracefully).
This ensures the documentation example follows the coding guideline of returning
errors up the stack rather than swallowing them, making it a better reference
for backend code patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 42f03e70-fb93-456e-a1ab-0352054bf2f4

📥 Commits

Reviewing files that changed from the base of the PR and between 58f52df and e0acdff.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • backend/docs/_index.md
  • backend/docs/auth.md
  • backend/docs/fcm.md
  • backend/docs/swagger/docs.go
  • backend/docs/swagger/swagger.json
  • backend/docs/swagger/swagger.yaml
  • backend/internal/bootstrap/bootstrap.go
  • backend/internal/domain/fcm_token.go
  • backend/internal/infrastructure/database/migrations/20260615220942_add_fcm_tokens.sql
  • backend/internal/infrastructure/database/postgres/fcm_token_repository.go
  • backend/internal/infrastructure/database/postgres/fcm_token_repository_test.go
  • backend/internal/server/server.go
  • backend/internal/transport/handlers/fcm_handler.go
  • backend/internal/transport/handlers/fcm_handler_test.go
  • backend/internal/transport/handlers/handler.go
  • backend/internal/transport/handlers/health_handler_test.go
  • backend/internal/transport/handlers/routes.go
  • backend/internal/usecase/notification.go
  • backend/pkg/firebase/admin.go
  • backend/pkg/firebase/app.go
  • backend/pkg/firebase/messaging.go
  • mobile/app/build.gradle.kts
  • mobile/app/src/main/AndroidManifest.xml
  • mobile/app/src/main/java/com/company/template/fcm/FcmRegistrationPayload.kt
  • mobile/app/src/main/java/com/company/template/fcm/MyFirebaseMessagingService.kt
  • mobile/app/src/test/java/com/company/template/fcm/FcmRegistrationPayloadTest.kt
  • mobile/docs/_index.md
  • mobile/docs/fcm.md
  • mobile/gradle/libs.versions.toml
  • web/docs/_index.md
  • web/docs/fcm.md
  • web/lib/fcm.test.ts
  • web/lib/fcm.ts
  • web/lib/useFCM.ts
  • web/package.json
  • web/public/firebase-messaging-sw.js

Comment thread backend/docs/fcm.md
Comment thread backend/internal/infrastructure/database/postgres/fcm_token_repository_test.go Outdated
Comment thread backend/internal/server/server.go Outdated
Comment thread backend/internal/transport/handlers/fcm_handler.go
Comment thread web/lib/useFCM.ts
Comment thread web/lib/useFCM.ts
Comment thread web/lib/useFCM.ts Outdated
Comment thread web/public/firebase-messaging-sw.js Outdated
Comment thread web/public/firebase-messaging-sw.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/app/src/main/java/com/company/template/MainActivity.kt`:
- Around line 45-46: The code currently logs the raw FCM registration token
using Log.d("FCM_TOKEN", token) in the addOnSuccessListener callback within
MainActivity.kt. FCM tokens are sensitive credentials and should not be logged
in plaintext to Logcat. Remove the Log.d call entirely, or if logging is
necessary for debugging purposes, log only a portion of the token (such as the
last few characters) or a hashed/masked version instead of the complete token
string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f543aa1e-42b2-41eb-b4b6-b9b9fb50b051

📥 Commits

Reviewing files that changed from the base of the PR and between e0acdff and 243ea93.

📒 Files selected for processing (6)
  • mobile/.gitignore
  • mobile/app/build.gradle.kts
  • mobile/app/src/main/AndroidManifest.xml
  • mobile/app/src/main/java/com/company/template/MainActivity.kt
  • mobile/build.gradle.kts
  • mobile/gradle/libs.versions.toml
✅ Files skipped from review due to trivial changes (2)
  • mobile/build.gradle.kts
  • mobile/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • mobile/app/build.gradle.kts
  • mobile/app/src/main/AndroidManifest.xml

Comment thread mobile/app/src/main/java/com/company/template/MainActivity.kt
- Add CHECK constraint on platform column in fcm_tokens migration
- Scope DeleteToken to authenticated user (WHERE user_id AND token)
- Gate fcmTokenRepo creation on Firebase availability in server.go
- Fix useFCM hook: always return cleanup, handle fetch errors
- Update firebase-messaging-sw.js to SDK 12.0.0, guard duplicate init
- Add useFCM.test.ts with 5 unit tests
- Fix swallowed errors in fcm_token_repository_test.go
- Write google-services.json from secret in mobile CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the area: infra CI/CD, infrastructure, deployment, and DevOps label Jun 22, 2026
…tyResult

activity-compose 1.8.0 transitively includes Fragment 1.6+ which satisfies
the InvalidFragmentVersionForActivityResult requirement; lint cannot detect
the transitive version so we suppress it explicitly.
@GRACENOBLE
GRACENOBLE merged commit e847680 into main Jun 22, 2026
5 checks passed
@GRACENOBLE
GRACENOBLE deleted the 19-feat-integrate-firebase-cloud-messaging-for-push-notifications branch June 22, 2026 22:23
@coderabbitai coderabbitai Bot mentioned this pull request Jun 25, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend Go REST API area: infra CI/CD, infrastructure, deployment, and DevOps area: mobile Android app (Kotlin + Jetpack Compose) area: web Next.js web app type: chore Cleanup or maintenance tasks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: integrate Firebase Cloud Messaging for push notifications

1 participant