Skip to content

fix(notifications): upsert device token on (user_id, endpoint)#2

Draft
SIRXIII wants to merge 1 commit into
mainfrom
claude/cool-faraday-1MPQp
Draft

fix(notifications): upsert device token on (user_id, endpoint)#2
SIRXIII wants to merge 1 commit into
mainfrom
claude/cool-faraday-1MPQp

Conversation

@SIRXIII
Copy link
Copy Markdown
Owner

@SIRXIII SIRXIII commented May 23, 2026

Summary

FCM device-token registration failed on every app launch (Phase 23 simulator verification) with:

DeviceTokenSaveException: Failed to save token: there is no unique or exclusion constraint matching the ON CONFLICT specification

This silently broke push notifications on device — including the Phase 21 "trainer account approved" push.

Root cause: SupabaseDeviceTokenRepository.saveToken upserted into push_subscriptions with onConflict: 'user_id,device_token', but the live table (Supabase project qecwxvvlpvrnrqyrdxrj, shared with the FitConnect web app) has no such unique constraint. Its only matching unique key is push_subscriptions_user_id_endpoint_key on (user_id, endpoint).

Fix: Target the constraint that actually exists — onConflict: 'user_id,endpoint'. The DTO already writes endpoint = 'fcm:' + device_token, a 1:1 function of the token, so conflicting on (user_id, endpoint) gives identical re-registration idempotency. No schema change, so the shared (user_id, endpoint) constraint the webapp relies on is untouched.

Why a client fix, not a migration

A migration adding a (user_id, device_token) unique constraint would modify a shared production database (hard to reverse, needs a duplicate-row guard, complicated by nullable device_token for web-push rows). The client-only fix is minimal, fully reversible, and risk-free for FitConnect.

Verification

Confirmed directly against the live DB (no rows written — a deliberately fake user_id is blocked by the FK):

ON CONFLICT target Result
(user_id, device_token) (old) 42P10: there is no unique or exclusion constraint… — reproduces the exact reported error
(user_id, endpoint) (new) passes conflict inference; only 23503 FK violation from the fake user — proves the upsert is valid

Note: the iOS simulator step from the task could not be run here — this is a Linux cloud container (no macOS/Xcode). Verification was done at the PostgreSQL layer where the bug lives, plus a unit test. A simulator run against the live backend is still worth doing locally to confirm end-to-end.

Changes

  • supabase_device_token_repository.dart: onConflict: 'user_id,device_token''user_id,endpoint' (+ explanatory comment).
  • device_token_dto.dart: corrected the stale conflict-handling doc comment.
  • supabase_device_token_repository_test.dart: regression test asserting saveToken upserts with onConflict: 'user_id,endpoint'.

Follow-up (out of scope, flagged)

The live table has a CHECK constraint platform IN ('web','ios'), but SupabaseDeviceTokenRepository sends 'android' for non-iOS devices. On Android this upsert would fail the check constraint — a separate latent bug to address before Android launch (either widen the shared CHECK or map the platform value).

Test plan

  • flutter analyze clean
  • flutter test test/features/notifications/data/repositories/supabase_device_token_repository_test.dart passes
  • flutter run on iOS simulator against the live backend — confirm no DeviceTokenSaveException in logs and a row appears in push_subscriptions

https://claude.ai/code/session_012unpTSSNZTagJj9VwuqU45


Generated by Claude Code

FCM device-token registration failed on every launch with
"DeviceTokenSaveException: ... no unique or exclusion constraint
matching the ON CONFLICT specification" (42P10), silently breaking
push notifications on device.

The repository upserted into push_subscriptions with
onConflict: 'user_id,device_token', but the live table (shared with
the FitConnect web app) has no such unique constraint. Its only
matching unique key is push_subscriptions_user_id_endpoint_key on
(user_id, endpoint). Since endpoint = 'fcm:' + device_token is a 1:1
function of the token, conflicting on (user_id, endpoint) yields
identical re-registration idempotency without touching the shared
schema the webapp relies on.

Add a regression test asserting saveToken upserts with
onConflict: 'user_id,endpoint'.

https://claude.ai/code/session_012unpTSSNZTagJj9VwuqU45
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