Profili, piano e crediti
Ogni progetto che ha un abbonamento o dei crediti ha bisogno di sapere, per ogni utente, che piano ha e quanti crediti gli restano. Questi dati non devono mai essere modificabili dal browser: altrimenti chiunque potrebbe regalarsi il piano Pro.
Il codice qui sotto è uguale per tutti i progetti: cambia solo il numero di crediti gratuiti alla registrazione, indicato nella frase "Pagamenti con Stripe" della scheda.
Come funziona
- Quando un utente si registra, un trigger crea da solo il suo profilo con piano "gratis" e i crediti iniziali.
- L'utente può LEGGERE il proprio profilo, ma non c'è nessuna regola che gli permetta di modificarlo.
- I crediti si scalano con la funzione usa_credito, che li toglie solo se ce ne sono: due richieste nello stesso istante non possono andare sotto zero.
- Le funzioni usa_credito e aggiungi_crediti le può usare solo il server (service role): le chiamano le Edge Function e il webhook di Stripe.
Il codice SQL
Da eseguire una volta nel SQL Editor di Supabase. Per cambiare i crediti gratuiti modifica il numero dopo "default" nella colonna crediti.
Profili, piano e crediti
Ogni utente ha un profilo con il suo piano e i suoi crediti. Nasce da solo alla registrazione, e l'utente non può cambiarsi piano o crediti da solo: lo fanno solo i pagamenti e le Edge Function.
create table profili (
id uuid primary key references auth.users(id) on delete cascade,
piano text not null default 'gratis' check (piano in ('gratis', 'pro')),
crediti int not null default 3 check (crediti >= 0),
stripe_customer_id text,
creato_il timestamptz not null default now()
);
alter table profili enable row level security;
-- Ognuno vede solo il proprio profilo. Nessuna regola di modifica:
-- dal browser piano e crediti non si possono cambiare.
create policy "vedo il mio profilo" on profili
for select using (auth.uid() = id);
-- Il profilo nasce da solo alla registrazione
create function nuovo_profilo() returns trigger
language plpgsql security definer set search_path = public as $
begin
insert into profili (id) values (new.id);
return new;
end $;
create trigger alla_registrazione after insert on auth.users
for each row execute function nuovo_profilo();
-- Scala un credito in modo sicuro (restituisce false se sono finiti)
create function usa_credito(utente uuid) returns boolean
language plpgsql security definer set search_path = public as $
begin
update profili set crediti = crediti - 1 where id = utente and crediti > 0;
return found;
end $;
-- Aggiunge crediti dopo un pagamento
create function aggiungi_crediti(utente uuid, quanti int) returns void
language sql security definer set search_path = public as $
update profili set crediti = crediti + quanti where id = utente;
$;
-- Solo le Edge Function (service role) possono usarle
revoke execute on function usa_credito(uuid) from public, anon, authenticated;
revoke execute on function aggiungi_crediti(uuid, int) from public, anon, authenticated;
grant execute on function usa_credito(uuid) to service_role;
grant execute on function aggiungi_crediti(uuid, int) to service_role;Usare i crediti in una Edge Function
Scalare un credito prima di un lavoro costoso
Prima si scala il credito, poi si fa il lavoro (per esempio la chiamata all'AI). Se il lavoro fallisce, il credito si restituisce con aggiungi_crediti.
const { data: ok } = await admin.rpc("usa_credito", { utente: user.id });
if (!ok) return new Response("Crediti finiti", { status: 402, headers: cors });
try {
// ... il lavoro che costa (chiamata all'AI, generazione immagine...)
} catch (errore) {
await admin.rpc("aggiungi_crediti", { utente: user.id, quanti: 1 }); // rimborso
throw errore;
}Con l'abbonamento invece dei crediti
Controllare il piano e i limiti del mese
Nei progetti ad abbonamento non si scalano crediti: si controlla il piano e, per gli utenti gratuiti, quante volte hanno usato la funzione questo mese.
const { data: profilo } = await admin.from("profili").select("piano").eq("id", user.id).single();
if (profilo?.piano !== "pro") {
const inizioMese = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString();
const { count } = await admin.from("utilizzi")
.select("id", { count: "exact", head: true })
.eq("utente_id", user.id)
.gte("creato_il", inizioMese);
if ((count ?? 0) >= 5) return new Response("Limite del piano gratuito raggiunto", { status: 402, headers: cors });
}- RLS
- Row Level Security: le regole del database che decidono chi può leggere o modificare ogni riga.
- Trigger
- Un'azione che il database fa da solo quando succede qualcosa, per esempio creare il profilo alla registrazione.
- Crediti
- Un gettone da spendere per ogni uso: si comprano a pacchetti e il sistema li scala da solo.
- Service role
- La chiave "padrona" di Supabase che ignora le regole di sicurezza: va solo nelle Edge Function, mai nel sito.