Data Migration History
Finapp's backend was migrated from Firebase Realtime Database to Supabase Postgres + PowerSync (offline-first sync) with Supabase Auth (email/password). This page documents that migration and the script that imports a Firebase JSON export into Supabase.
The legacy Firebase (v6) source code lives on the firebase branch.
What changed
| Entity | Firebase (v6) | Current (Supabase + PowerSync) |
|---|---|---|
| Accounts | accounts with string keys | wallets table, text ids |
| Categories | String IDs, parentId as string | text ids, parentId = a category id or null (root) |
| Transactions | String IDs, references by old keys | text ids, Firebase ids reused (references preserved) |
| Settings | settings.lang (en, ru) | user_settings.locale (en, ru) |
| Icons | mdi mdi-xxx format | mdi:xxx format |
Import tooling
The importer lives at app/scripts/import-firebase.mjs. It reads a Firebase JSON export (the { accounts, categories, trns, settings, user } shape) and writes it into a Supabase project for one user.
cd app
SUPABASE_URL=https://<ref>.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=<service_role> \
node scripts/import-firebase.mjs <backup.json> --email <owner-email> --wipe
- Owner - resolved from
--email(defaults to the export'suser.email) to an existing Supabase auth uid via the GoTrue admin API, or passed directly with--user-id. The user must have signed in at least once (e.g. via Google) so the imported rows'userIdmatches the uid they log in as. - Config - reads
SUPABASE_URL+SUPABASE_SERVICE_ROLE_KEYfrom the env; with neither set it falls back to the local stack viasupabase status. Writes go through PostgREST with theservice_rolekey, so they bypass RLS and run over HTTPS (the direct Postgres connection's IPv6-only limitation does not apply). --wipedeletes the user's existingtrns/categories/walletsfirst;--dry-runprints the transformed counts and samples without writing.- Idempotent - rows upsert on their primary key and Firebase ids are reused verbatim (below), so re-running is safe.
Transforms it applies
- Ids reused, not remapped - Supabase id columns are plain
textwith no FK constraints, so the script keeps the original Firebase keys as ids;parentId/walletId/categoryIdreferences stay valid with no remap pass. - Accounts -> wallets;
settings.lang->user_settings.locale;settings.baseCurrency->user_settings.baseCurrency. - Icons -
mdi mdi-xxx->mdi:xxx; empty ->mdi:category-outline. - Transfer / adjustment - categories named
Transfer/Перевод/Adjustmentare not real categories and are skipped; their transactions becomecategoryId = 'transfer'(type 2, with theexpense*/income*fields) orcategoryId = 'adjustment'(otherwise). - Legacy fields -
walletFromId/walletToId->expenseWalletId/incomeWalletId;amountFrom/amountTo->expenseAmount/incomeAmount;description->desc. Firebase-only fields with no column (categoryorder,showStat,budgets,groups) are dropped.
No FK constraints exist in the schema, so import order does not matter. RLS policies and PowerSync sync rules ensure per-user isolation; on the owner's next login PowerSync syncs the imported rows to their device.
Current backend
The current backend is Supabase Postgres + PowerSync. See Architecture for a full description and Installation for local setup.