## Phase 1: Critical Feature Completion (Beacon Voting) - Add VouchBeacon, ReportBeacon, RemoveBeaconVote methods to PostRepository - Implement beacon voting HTTP handlers with confidence score calculations - Register new beacon routes: /beacons/:id/vouch, /beacons/:id/report, /beacons/:id/vouch (DELETE) - Auto-flag beacons at 5+ reports, confidence scoring (0.5 base + 0.1 per vouch) ## Phase 2: Feed Logic & Post Distribution Integrity - Verify unified feed logic supports all content types (Standard, Quips, Beacons) - Ensure proper distribution: Profile Feed + Main/Home Feed for followers - Beacon Map integration for location-based content - Video content filtering for Quips feed ## Phase 3: The Notification System - Create comprehensive NotificationService with FCM integration - Add CreateNotification method to NotificationRepository - Implement smart deep linking: beacon_map, quip_feed, main_feed - Trigger notifications for beacon interactions and cross-post comments - Push notification logic with proper content type detection ## Phase 4: The Great Supabase Purge - Delete function_proxy.go and remove /functions/:name route - Remove SupabaseURL, SupabaseKey from config.go - Remove SupabaseID field from User model - Clean all Supabase imports and dependencies - Sanitize codebase of legacy Supabase references ## Phase 5: Flutter Frontend Integration - Implement vouchBeacon(), reportBeacon(), removeBeaconVote() in ApiService - Replace TODO delay in video_comments_sheet.dart with actual publishComment call - Fix compilation errors (named parameters, orphaned child properties) - Complete frontend integration with Go API endpoints ## Additional Improvements - Fix compilation errors in threaded_comment_widget.dart (orphaned child property) - Update video_comments_sheet.dart to use proper named parameters - Comprehensive error handling and validation - Production-ready notification system with deep linking ## Migration Status: 100% Complete - Backend: Fully migrated from Supabase to custom Go/Gin API - Frontend: Integrated with new Go endpoints - Notifications: Complete FCM integration with smart routing - Database: Clean of all Supabase dependencies - Features: All functionality preserved and enhanced Ready for VPS deployment and production testing!
3.8 KiB
JWT 401 "Invalid JWT" Fix
Date: January 11, 2026
Issue: Edge Functions returning 401 "Invalid JWT" errors for feed endpoints
Status: Fixed
Problem
The feed-personal and feed-sojorn Edge Functions were returning 401 "Invalid JWT" errors, causing feeds to fail to load despite the user having a valid session.
Symptoms
feed-personalandfeed-sojornfunctions returned 401 with{code: 401, message: "Invalid JWT"}profilefunction worked fine (no 401 errors)- Session refresh didn't resolve the issue
- Multiple concurrent requests caused repeated refresh attempts
Root Cause
The feed functions were explicitly passing the JWT to supabase.auth.getUser(jwt):
// BEFORE (broken):
const { data: { user }, error: authError } = await supabase.auth.getUser(jwt);
The JWT was extracted from the request's Authorization header. Even after the Flutter client refreshed its session and obtained a new token, subsequent API calls would still send the old/stale JWT in the header because:
- The request was already in flight when refresh happened
- Cached/old tokens weren't being properly invalidated
- The Edge Function validated the stale JWT and rejected it
Meanwhile, the profile function worked because it called supabase.auth.getUser() without passing the JWT explicitly:
// AFTER (fixed):
const { data: { user }, error: authError } = await supabase.auth.getUser();
This lets the Supabase SDK use its internal session state (which gets updated after refresh) rather than trusting the potentially stale header token.
Solution
Edge Functions Changes
Changed both feed-personal and feed-sojorn to NOT pass the JWT to getUser():
// AFTER (fixed):
const { data: { user }, error: authError } = await supabase.auth.getUser();
Flutter App Changes (api_service.dart)
Added proper 401 retry logic:
_callFunctionnow re-throwsFunctionException- Allows callers to catch 401 errors- 401 retry with session refresh in
getPersonalFeed,getSojornFeed, andgetProfile - Concurrent refresh handling - Multiple simultaneous 401s share a single refresh future via
_refreshInFlight - Removed artificial delays - No more unnecessary 500ms/1000ms delays after refresh
Files Modified
supabase/functions/feed-personal/index.ts- Removed JWT parameter fromgetUser()supabase/functions/feed-sojorn/index.ts- Removed JWT parameter fromgetUser()sojorn_app/lib/services/api_service.dart- Added 401 retry logic with session refresh
How to Deploy
Run the deployment script from the project root:
.\deploy_all_functions.ps1
This uses --no-verify-jwt flag which is required for the supabase-js v2 SDK that supports ES256 JWTs.
Lessons Learned
- Don't explicitly pass JWTs to
getUser()- Let the SDK handle authentication automatically - The Supabase SDK handles auth internally - Trust its internal session state
- Profile function was the clue - It worked because it didn't pass the JWT explicitly
- Check how similar functions work - When one function works and another doesn't, compare their implementations
Prevention
When creating new Edge Functions:
- Always use
supabase.auth.getUser()without passing the JWT parameter - Trust the Supabase SDK's internal session handling
- If you need the user's ID, get it from
user.idafter callinggetUser()without parameters - Don't extract and pass JWTs from request headers manually