Tutti i playbookFlutter Production App

Aggiungere una tabella Supabase con RLS

Salvare nuovi dati dell'utente in Supabase dietro RLS e test pgTAP, e leggerli nell'app attraverso un repository.

Quando usarlo

L'app salva nuovi dati dell'utente in Supabase, per esempio note, preferiti o voci di un diario, e li mostra in una schermata.

Regole da rispettare

  • L'app contiene la chiave publishable, quindi la RLS è l'unica cosa che separa le righe di un utente da quelle di un altro. Ogni nuova tabella abilita la RLS nella migration che la crea.
  • Le migration sono append-only: non modificarne mai una già applicata.
  • Ogni nuova policy ha un test pgTAP che prova l'accesso consentito e rifiuta il resto.
  • Supabase si chiama solo da un repository in data, dietro un'interfaccia in domain.

Passi

  1. Crea supabase/migrations/<timestamp>_<nome>.sql con un timestamp successivo all'ultimo, sul modello di push_devices:

    create table public.notes (
      id uuid primary key default gen_random_uuid(),
      user_id uuid not null default auth.uid() references auth.users(id) on delete cascade,
      body text not null check (char_length(body) between 1 and 2000),
      created_at timestamptz not null default now()
    );
    
    create index notes_user_id_idx on public.notes (user_id);
    
    alter table public.notes enable row level security;
    
    create policy "notes_select_own" on public.notes for select
      to authenticated using ((select auth.uid()) = user_id);
    create policy "notes_insert_own" on public.notes for insert
      to authenticated with check ((select auth.uid()) = user_id);
    create policy "notes_delete_own" on public.notes for delete
      to authenticated using ((select auth.uid()) = user_id);
    
    revoke all on public.notes from anon;
    

    Aggiungi solo le operazioni che servono all'app: qui gli utenti non possono modificare una nota.

  2. Se una scrittura richiede regole che una policy non può esprimere, come cambiare proprietario o controllare altre tabelle, revoca le scritture dirette ed esponi una funzione security definer con set search_path = '' e un controllo su auth.uid(), come fa register_push_device.

  3. Aggiungi supabase/tests/database/<nome>_rls.test.sql sul modello di push_devices_rls.test.sql: due utenti in auth.users, set local role authenticated, request.jwt.claim.sub impostato a turno su ciascun utente. Verifica che un utente veda e modifichi solo le proprie righe, che scrivere una riga per un altro fallisca con 42501 e che anon non riceva nulla.

  4. Esegui supabase db reset e supabase test db in locale.

  5. In lib/features/<feature>/domain definisci il modello con fromJson e una abstract interface class per il repository, come ProfileRepository. In data implementala con SupabaseClient, come SupabaseProfileRepository: senza sessione, lancia un errore invece di eseguire la query.

  6. In application esponi il repository con un Provider che legge supabaseClientProvider, come profileRepositoryProvider, e aggiungi i provider osservati dalla schermata. Per costruire la schermata segui il playbook "Aggiungere una schermata con routing e stato".

  7. Aggiungi test Dart per la conversione del modello e per i provider con un repository fake; i test non usano la rete.

  8. Aggiungi tabella, proprietario e regola alla tabella di DATABASE.md.

Completato quando

  • i comandi di verifica passano;
  • supabase test db passa, compreso il nuovo test;
  • la tabella ha la RLS abilitata e solo le policy che servono all'app;
  • DATABASE.md elenca la tabella.