Database changes live in supabase/migrations.
The current committed schema defines these Supabase tables:
profilescategoriespayment_methodstransactionsmonthly_budgetsgoalsprivacy_requests
001_init.sql: creates the finance schema, indexes, initial RLS policies, profile/category/payment-method defaults, and the initial auth user seed trigger.002_security_lgpd_hardening.sql: addsupdated_atanddeleted_atcolumns, moves helper functions into the private schema, adds privacy requests, hardens grants and RLS policies, validates transaction ownership references, and adds LGPD-oriented comments.005_add_installment_group_metadata.sql: adds stable installment grouping metadata and an authenticated-user scoped installment group index.006_add_installment_prepayment_metadata.sql: adds installment prepayment metadata and an authenticated-user scoped prepayment month index.010_stop_writing_plaintext_profile_pii.sql: stops the signup trigger from writing plaintextprofiles.name/email(application-layer encryption now owns these fields, seelib/crypto/field-encryption.ts), and drops the now-unusedtransactions_user_id_notes_idxpartial index.
The repository should include:
- schema migrations
- table constraints
- indexes
- RLS policies
- trigger/function definitions required to run the app
- seed/default data that does not contain private user information
The repository must not include:
- Supabase service-role keys
- OAuth client secrets
.env.local- production database URLs
- dumps with real user data
- access tokens or refresh tokens
idemail— application-layer encrypted (lib/crypto/field-encryption.ts), never plaintext at rest. Distinct fromauth.users.email, which Supabase Auth manages and is unaffected.name— application-layer encrypted (lib/crypto/field-encryption.ts), never plaintext at rest.created_atupdated_atdeleted_at
private.handle_new_user() no longer writes plaintext name/email into profiles (see migration 010). lib/auth/encrypted-profile.ts fills them in, encrypted, on the user's next authenticated request.
iduser_idnameicongroup_typeis_defaultmonthly_limitcreated_atupdated_atdeleted_at
iduser_idnametypecredit_limitdue_dayclosing_daycreated_atupdated_atdeleted_at
The application has backward-compatible fallbacks for older environments that do not have all optional payment-method fields. The current committed schema includes credit-card limit and due/closing day support, but does not define an is_default column for payment methods.
iduser_idamountcategory_iddatedescription— application-layer encrypted with a deterministic IV (lib/crypto/field-encryption.ts) so equality lookups (subscription grouping) still work; never plaintext at rest.kindinstallment_group_idinstallment_numberinstallment_totaladvanced_to_monthadvanced_atnotes— application-layer encrypted (lib/crypto/field-encryption.ts), random IV; never plaintext at rest.payment_method_idcreated_atupdated_atdeleted_at
Installments and subscriptions are modeled as multiple transaction rows. Installment rows from the same original purchase share installment_group_id, use 1-based installment_number, and store the original purchase count in installment_total. Installment groups are still user-owned transaction rows and must always be queried with the authenticated user's scope.
Installment prepayment uses advanced_to_month and advanced_at. The original transaction date, category, payment method, and installment metadata are preserved for auditability. Payment and invoice views use advanced_to_month as the payment context so advanced installments appear in the target month and no longer appear as future obligations.
Subscription rows use notes values beginning with subscription; paused subscriptions use subscription paused. Since notes is stored encrypted, this prefix check happens in application code against the decrypted value (lib/finance/transactions.ts), not as a SQL LIKE predicate.
iduser_idmonthincomeneeds_limitwants_limitsavings_limitcreated_atupdated_atdeleted_at
The current UI calculates 50/30/20 budget data from transactions and category limits. The monthly_budgets table is available in the schema for persisted monthly budget plans.
iduser_idnameicontarget_amountcurrent_amountdeadlinecolorcreated_atupdated_atdeleted_at
lib/finance/transactions.ts calls the add_goal_funds RPC when adding funds to an existing goal. If an environment does not already provide this RPC, add a migration before using the goal funding flow.
iduser_idrequest_typestatusdetailsresponserequested_atresolved_atupdated_at
The table supports LGPD workflows for access, export, correction, deletion, consent, and support requests.
private.handle_new_user()creates a profile, default categories, and default payment methods after Supabase Auth user creation.private.touch_updated_at()keepsupdated_atcurrent on updates.private.validate_transaction_owner_refs()prevents transactions from referencing categories or payment methods owned by another user.on_auth_user_createdruns after inserts onauth.users.
Email/password signups and Google OAuth signups both insert users into auth.users, so both flows use the same on_auth_user_created onboarding trigger. Do not duplicate default profile, category, or payment-method setup in frontend code.
Preferred:
supabase db pushFallback:
Apply the SQL files in supabase/migrations through the Supabase SQL editor.
- Keep RLS enabled on exposed user-owned tables.
- Force RLS where the current migrations force it.
- Policies should restrict rows by the authenticated user's ID and should be operation-specific when possible.
- Do not grant broad public write access.
- Keep unauthenticated
anonaccess revoked for user-owned finance data. - Do not expose service-role credentials to the application frontend.
- Keep privileged helper functions out of exposed schemas.