Skip to content

Comments

Supabase streak data sync#25

Merged
fortune710 merged 3 commits intodevfrom
cursor/DEF-81-supabase-streak-data-sync-0532
Jan 11, 2026
Merged

Supabase streak data sync#25
fortune710 merged 3 commits intodevfrom
cursor/DEF-81-supabase-streak-data-sync-0532

Conversation

@fortune710
Copy link
Owner

@fortune710 fortune710 commented Jan 11, 2026

Sync streak data to Supabase to enable cross-device synchronization and persistence.


Linear Issue: DEF-81

Open in Cursor Open in Web


Note

Enables cross-device persistence of streak stats.

  • New user_streaks table (migration) with RLS policies, unique index on user_id, and updated_at trigger
  • StreakService now falls back to Supabase on load and upserts current_streak/max_streak on save; local storage kept as cache
  • Supabase constants/schema updated to include USER_STREAKS; TypeScript Database types extended for user_streaks

Written by Cursor Bugbot for commit b3e96f9. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features
    • Streaks now sync across devices: current and max streaks plus last entry/access times are persisted remotely.
    • Sync is best-effort and non-blocking—local streaks continue to work if remote sync is unavailable.

✏️ Tip: You can customize this high-level summary in your review settings.

Co-authored-by: alebiosuf0802 <alebiosuf0802@students.bowiestate.edu>
@cursor
Copy link

cursor bot commented Jan 11, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@vercel
Copy link

vercel bot commented Jan 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
keepsafe Ready Ready Preview, Comment Jan 11, 2026 9:39pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 11, 2026

Walkthrough

Adds a Supabase-backed user_streaks table, migration, TypeScript DB types, and integrates remote fetch/upsert into the streak service for best-effort, non-blocking cross-device streak sync.

Changes

Cohort / File(s) Summary
Schema Constant
frontend/constants/supabase.ts
Adds USER_STREAKS to TABLES and a USER_STREAKS block in SCHEMA with id, user_id, current_streak, max_streak, last_entry_date, last_access_time, created_at, updated_at and FK to auth.users(id) (cascade delete).
Type Definitions
frontend/types/database.ts
Adds user_streaks to Database.public.Tables with Row, Insert, and Update shapes including last_entry_date and last_access_time (nullable) and timestamps.
DB Migration
frontend/supabase/migrations/20260111000000_streaks.sql
Creates user_streaks table (bigserial id, uuid user_id FK), unique index on user_id, NOT NULL timestamps (backfilled), RLS enabled with policies restricting access to row owner, and a trigger to update updated_at.
Service Integration
frontend/services/streak-service.ts
Imports supabase; in loadStreakData fetches remote user_streaks as a fallback and caches it locally; in saveStreakData performs a best-effort, non-blocking upsert to user_streaks with silent error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/App
    participant StreakService as Streak Service
    participant LocalStore as Local Storage
    participant Supabase as Supabase API
    participant DB as user_streaks Table

    note over Client,StreakService: Load Streak Data
    Client->>StreakService: loadStreakData()
    StreakService->>LocalStore: check cached streak
    alt cache hit
        LocalStore-->>StreakService: cached StreakData
        StreakService-->>Client: return cached StreakData
    else cache miss
        StreakService->>Supabase: fetch user_streaks by user_id
        Supabase->>DB: SELECT row
        DB-->>Supabase: row or null
        Supabase-->>StreakService: remote data
        alt remote available
            StreakService->>LocalStore: cache remote data
            LocalStore-->>StreakService: cached
            StreakService-->>Client: return remote StreakData
        else none
            StreakService-->>Client: return defaults
        end
    end

    note over Client,StreakService: Save Streak Data
    Client->>StreakService: saveStreakData(streak)
    StreakService->>LocalStore: save locally
    LocalStore-->>StreakService: saved
    StreakService-->>Client: immediate return
    par non-blocking remote sync
        StreakService->>Supabase: upsert user_streaks
        Supabase->>DB: INSERT/UPDATE (RLS)
        DB-->>Supabase: result
        Supabase-->>StreakService: success/error (handled silently)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰✨ I hop between devices, bright and quick,
I store each leap, each daily trick.
Supabase holds my carrot trail,
So streaks persist when I set sail.
Hoppity-hop, synced and slick!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Supabase streak data sync' directly and concisely describes the main change: adding Supabase synchronization for streak data across devices, which aligns with the core objective and all file changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Co-authored-by: alebiosuf0802 <alebiosuf0802@students.bowiestate.edu>
@fortune710 fortune710 marked this pull request as ready for review January 11, 2026 20:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @frontend/supabase/migrations/20260111000000_streaks.sql:
- Around line 57-62: The CREATE POLICY "Users can update own streak" for table
user_streaks is split by an accidental semicolon after the USING clause, causing
the subsequent WITH CHECK to be a syntax error; fix it by removing the semicolon
so the statement reads as a single CREATE POLICY ... FOR UPDATE ... TO
authenticated USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id)
ensuring the USING and WITH CHECK clauses are part of the same statement.
🧹 Nitpick comments (3)
frontend/supabase/migrations/20260111000000_streaks.sql (1)

28-39: Remove redundant backfill and ALTER statements.

Lines 24-25 already define created_at and updated_at as NOT NULL DEFAULT now(), so the backfill (lines 29-35) and subsequent ALTER TABLE ... SET NOT NULL (lines 37-39) are unnecessary. These operations only make sense when migrating an existing table with nullable columns.

♻️ Simplify by removing redundant code
 );
 
--- Backfill and enforce NOT NULL constraints for existing rows (if any)
-UPDATE user_streaks
-SET created_at = now()
-WHERE created_at IS NULL;
-
-UPDATE user_streaks
-SET updated_at = now()
-WHERE updated_at IS NULL;
-
-ALTER TABLE user_streaks
-  ALTER COLUMN created_at SET NOT NULL,
-  ALTER COLUMN updated_at SET NOT NULL;
-
 -- Enable Row Level Security
 ALTER TABLE user_streaks ENABLE ROW LEVEL SECURITY;
frontend/services/streak-service.ts (2)

99-114: Refactor to avoid unsafe type assertions.

The double as never assertions on line 107 suppress TypeScript's type checking. This suggests a mismatch between the upsert payload and the expected types.

♻️ Use proper typing for upsert
+    type UserStreakInsert = Database['public']['Tables']['user_streaks']['Insert'];
+
     try {
       const { error } = await supabase
         .from('user_streaks')
-        .upsert({
+        .upsert<UserStreakInsert>({
           user_id: userId,
           current_streak: data.currentStreak,
           max_streak: data.maxStreak,
-        } as never, { onConflict: 'user_id' } as never);
+        }, { onConflict: 'user_id' });

Add this import at the top:

 import { supabase } from '@/lib/supabase';
+import type { Database } from '@/types/database';

90-115: Consider conflict resolution for concurrent updates.

The current implementation has a race condition: if a user updates their streak on device A and device B simultaneously, the last write wins without merging. For example, max_streak could be lost if device A has max_streak: 10 and device B (with stale data) writes max_streak: 5.

Consider implementing optimistic locking or read-before-write logic:

// Pseudo-code for safer upsert
const { data: current } = await supabase
  .from('user_streaks')
  .select('max_streak')
  .eq('user_id', userId)
  .single();

const safeMaxStreak = Math.max(
  data.maxStreak, 
  current?.max_streak ?? 0
);

await supabase.from('user_streaks').upsert({
  user_id: userId,
  current_streak: data.currentStreak,
  max_streak: safeMaxStreak,
});

Would you like me to generate a complete implementation with conflict resolution?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f41a88 and b3e96f9.

📒 Files selected for processing (4)
  • frontend/constants/supabase.ts
  • frontend/services/streak-service.ts
  • frontend/supabase/migrations/20260111000000_streaks.sql
  • frontend/types/database.ts
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/services/streak-service.ts (3)
frontend/lib/supabase.ts (1)
  • supabase (14-30)
frontend/lib/logger.ts (1)
  • error (32-35)
frontend/services/device-storage.ts (1)
  • deviceStorage (170-170)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (4)
frontend/constants/supabase.ts (1)

15-15: LGTM! Clean addition of USER_STREAKS constant and schema.

The new table constant and schema definition follow the existing patterns and correctly match the migration structure.

Also applies to: 125-132

frontend/supabase/migrations/20260111000000_streaks.sql (2)

45-68: LGTM! Comprehensive RLS policies for user isolation.

The policies correctly ensure that authenticated users can only access their own streak records via auth.uid() = user_id checks across all operations.


70-78: LGTM! Proper indexing and auto-timestamp trigger.

The unique index on user_id enforces one-row-per-user and the trigger keeps updated_at in sync.

frontend/types/database.ts (1)

320-345: LGTM! Type definitions correctly model the user_streaks table.

The Row, Insert, and Update types accurately reflect the schema defined in the migration, with appropriate optionality for auto-generated and defaulted fields.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Co-authored-by: alebiosuf0802 <alebiosuf0802@students.bowiestate.edu>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @frontend/services/streak-service.ts:
- Around line 100-116: Remove the unsafe "as never" casts on the Supabase upsert
call and use the generated Database types instead: call
supabase.from<'user_streaks'>(...) and pass the correctly typed payload (e.g.,
the insert type for the user_streaks table from your Database types) to upsert,
and supply the appropriate options type for onConflict; update the function
(saveStreakData or the surrounding method) to import and use the Database types
so TypeScript validates the shape of user_id, current_streak, max_streak,
last_entry_date, and last_access_time rather than bypassing checks.
🧹 Nitpick comments (1)
frontend/supabase/migrations/20260111000000_streaks.sql (1)

32-43: Backfill is redundant for a new table.

Since created_at and updated_at are already defined with NOT NULL DEFAULT now() in the CREATE TABLE statement (lines 28-29), the backfill and ALTER statements are unnecessary for a fresh table. This only matters if the migration could run against an existing table with a different schema.

If this migration is always for a new table, consider removing these statements for clarity.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3e96f9 and 6733b91.

📒 Files selected for processing (4)
  • frontend/constants/supabase.ts
  • frontend/services/streak-service.ts
  • frontend/supabase/migrations/20260111000000_streaks.sql
  • frontend/types/database.ts
🔇 Additional comments (7)
frontend/supabase/migrations/20260111000000_streaks.sql (2)

45-76: LGTM! RLS policies and unique index are well-designed.

The RLS policies correctly restrict access to the authenticated user's own row using auth.uid() = user_id. The unique index on user_id properly supports the upsert operation used in the streak service.


78-82: No action required for the trigger function dependency.

The update_updated_at_column() function is already defined in migration 20250825200450_misty_boat.sql, so the trigger dependency is properly satisfied.

frontend/constants/supabase.ts (1)

15-15: LGTM!

The USER_STREAKS table constant and schema definition are consistent with the SQL migration and TypeScript types.

Also applies to: 125-134

frontend/services/streak-service.ts (3)

34-42: Cache-first strategy may return stale data on cross-device sync.

The current implementation returns cached data immediately without checking if newer data exists remotely. For cross-device sync (the stated goal of this PR), a user switching devices may see outdated streak data until the local cache is cleared or expires.

Consider:

  1. Remote-first with local fallback for offline support, or
  2. A background refresh that updates cache from remote after returning cached data

Is this cache-first behavior intentional? If offline-first is a priority, this is fine, but it may surprise users expecting immediate cross-device sync.


44-78: LGTM on Supabase fallback implementation.

The use of maybeSingle() is appropriate for the single-row-per-user pattern, and the error handling correctly logs without throwing (best-effort sync). The data mapping to StreakData is accurate.


135-185: LGTM!

The streak calculation logic is sound, and the Supabase sync is properly integrated via saveStreakData.

frontend/types/database.ts (1)

320-351: LGTM!

The user_streaks type definitions are well-structured and consistent with the SQL migration schema. The nullable fields (last_entry_date, last_access_time) and optional insert fields are correctly typed.

@fortune710 fortune710 merged commit cc3fb54 into dev Jan 11, 2026
3 checks passed
@fortune710 fortune710 deleted the cursor/DEF-81-supabase-streak-data-sync-0532 branch February 5, 2026 23:20
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