Firebase to Supabase Migration
Overview
Full migration of Ricoya from Firebase (Firestore + Firebase Auth + Firebase Storage) to Supabase (Postgres + Supabase Auth + Cloudflare R2). Executed across multiple sessions in February 2026.
Schema Design
Created 22 Postgres tables replacing 18 Firestore collections:
| Table | Purpose |
|---|---|
| config | App configuration key-value pairs |
| tax_rates | Tax rate definitions |
| users | User profiles with auth_uid reference |
| restaurants | Restaurant info, hours, images |
| restaurant_categories | Menu categories per restaurant |
| restaurant_category_tax_mappings | Tax-to-category associations |
| menu_items | Food items with pricing and images |
| menu_customization_groups | Add-on groups for menu items |
| addresses | Customer delivery addresses |
| discount_codes | Promo codes with usage limits |
| orders | Order records with status tracking |
| order_items | Individual items within orders |
| discount_code_logs | Discount code usage history |
| assignments | Staff-to-restaurant assignments |
| restaurant_logs | Restaurant activity audit trail |
| file_uploads | Uploaded file metadata |
| ratings | Customer ratings for orders |
| _id_map | Firebase-to-Supabase ID mapping |
| push_subscriptions | Web push notification endpoints |
| restaurant_sales_view | SQL VIEW replacing pre-computed reports |
Supporting infrastructure:
- 7 enums for order status, payment method, user role, etc.
- 24 indexes for query performance
- 4 PL/pgSQL functions for atomic operations (order creation with discount, driver claim, credit grant/redeem)
- RLS policies on all tables
Auth Migration
Migrated 79 users from Firebase Auth to Supabase Auth:
- Google OAuth users and phone OTP users created via Admin API
- Random passwords assigned (users re-authenticate via Google/Phone)
- Firebase UID stored in user metadata for reference
- 2 test accounts skipped
- ID mapping file generated for Firebase UID to Supabase UUID lookup
Data Migration
764 rows migrated across 17 tables:
- config: 5, tax_rates: 2, users: 79, restaurants: 10
- restaurant_categories: 61, category_tax_mappings: 7
- menu_items: 313, customization_groups: 34
- addresses: 49, discount_codes: 6
- orders: 45, order_items: 62, discount_code_logs: 7
- assignments: 10, restaurant_logs: 48, file_uploads: 26
- _id_map: 592 (ID mapping records)
- 31 records skipped (test data, orphaned references, deleted codes)
Repository Layer Rewrite
17 repository files rewritten from Firestore SDK to Supabase client:
- All repos now use Supabase query builder instead of Firestore collection queries
- Dual-ID problem eliminated with single Postgres SERIAL primary keys
- Firestore transactions replaced with RPC calls to PL/pgSQL functions
- Server-side repos use admin client (service role key)
API Route Rewrite
Approximately 50 API route files rewritten using 4 parallel agents:
- 10 admin routes (addresses, assignments, categories, discount-codes, drivers, orders, order-items)
- 11 admin routes (reports, restaurants, taxes, users)
- 18 routes (driver, payment, order-detail, cart, public, push)
- 3 services + login page + profile page + ManagerConsole + 3 scripts
New route created for Supabase OAuth redirect handling.
Client Library Changes
- Browser client created via @supabase/ssr
- Server client created with service role key
- Server auth changed from Firebase verifyIdToken to Supabase auth.getUser
- AuthContext changed from Google popup to OAuth redirect, Phone reCAPTCHA to OTP
- API client changed from Firebase getIdToken to Supabase getSession
Build Fixes
After migration, 10+ files had Firebase-specific type references:
- user.uid changed to user.id (Supabase User type)
- getIdToken() replaced with getSession()
- phoneNumber changed to phone
- displayName changed to user_metadata.display_name
- Restaurant model updated with numeric id, legacy_firestore_id, legacy_numeric_id
Legacy Compatibility
The codebase uses firestore_id as a compatibility layer:
- Restaurant URLs use legacy Firebase document IDs
- 4 repositories resolve legacy_firestore_id to numeric Supabase ID before querying
- restaurantsRepo maps legacy_firestore_id (or String(id) fallback) to firestore_id field