CaveSB SupabasePièce 6/7Avancé

Stockage et temps réel

Envoyer des photos de recettes dans le stockage Supabase avec des règles d'accès, et recevoir les changements de la base en direct.

Ce que tu vas apprendre

  • Envoyer une photo dans un bucket et obtenir son adresse.
  • Écrire des règles d'accès sur le stockage, comme pour les tables.
  • Réagir en direct aux changements d'une table avec Realtime.

Les buckets

Le stockage de Supabase range des fichiers (photos, PDF) dans des buckets, des dossiers de plus haut niveau. Un bucket est public ou privé.

BucketAccèsAdresse
PublicTout fichier est lisible par tousURL fixe
PrivéRéservé aux personnes autoriséesURL signée, temporaire

Crée un bucket photos-recettes dans la section Storage du tableau de bord. Pour une photo publique de plat, le bucket public suffit.

Envoyer un fichier

app/components/EnvoiPhoto.vue
<script setup lang="ts">
const supabase = useSupabaseClient()
const user = useSupabaseUser()

async function envoyer(event: Event) {
  const fichier = (event.target as HTMLInputElement).files?.[0]
  if (!fichier || !user.value) return
  const chemin = `${user.value.sub}/${fichier.name}`
  const { error } = await supabase.storage
    .from('photos-recettes')
    .upload(chemin, fichier)
  if (error) console.error(error.message)
}
</script>

Le fichier vient d'un <input type="file" @change="envoyer">. Le chemin commence par l'identifiant de l'utilisateur : chaque personne a son dossier.

Pour remplacer un fichier existant, passe l'option { upsert: true } en troisième argument.

Obtenir l'adresse d'une photo

Pour un bucket public, getPublicUrl donne l'adresse :

const { data } = supabase.storage
  .from('photos-recettes')
  .getPublicUrl('chemin/crepes.jpg')

console.log(data.publicUrl)

Pour un bucket privé, createSignedUrl crée un lien temporaire :

const { data } = await supabase.storage
  .from('photos-recettes')
  .createSignedUrl('chemin/crepes.jpg', 3600)

console.log(data?.signedUrl)

Le second argument est la durée de validité. Un lien signé ne peut pas être révoqué seul : il faut supprimer, déplacer ou renommer le fichier.

Protéger le stockage

Les fichiers sont gérés par la table storage.objects, protégée par la RLS comme tes tables. Sans politique, l'envoi est refusé.

Cette politique autorise un connecté à envoyer des fichiers uniquement dans son dossier :

create policy "Envoyer dans son dossier"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'photos-recettes'
  and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);

storage.foldername(name) découpe le chemin en dossiers. Le premier doit être ton identifiant, sub dans le JWT.

Un bucket public rend tous ses fichiers lisibles par n'importe qui. N'y mets rien de personnel.

Le temps réel

Avec Realtime, la base prévient ton site dès qu'une ligne change, sans rechargement. Tu l'utilises par exemple pour qu'une recette ajoutée apparaisse chez tous les visiteurs.

Il faut d'abord activer Realtime sur la table. Dans le tableau de bord, ouvre les réglages des publications et active la table dans supabase_realtime. En SQL :

alter publication supabase_realtime
add table recettes;

S'abonner aux changements

app/pages/recettes.vue
<script setup lang="ts">
const supabase = useSupabaseClient()

const canal = supabase
  .channel('recettes-en-direct')
  .on(
    'postgres_changes',
    { event: '*', schema: 'public', table: 'recettes' },
    (payload) => console.log(payload)
  )
  .subscribe()

onUnmounted(() => supabase.removeChannel(canal))
</script>

Un canal est une connexion ouverte avec le serveur. event: '*' écoute les ajouts, modifications et suppressions. table restreint l'écoute à recettes.

removeChannel ferme le canal quand tu quittes la page. Sans lui, la connexion reste ouverte inutilement.

Realtime respecte la RLS : tu ne reçois que les changements de lignes que tu as le droit de lire. Exception documentée : les politiques ne s'appliquent pas aux suppressions, donc ne mets pas de donnée sensible dans une table écoutée en direct.

À retenir

  • Les fichiers vivent dans des buckets, publics (URL fixe) ou privés (URL signée temporaire).
  • upload envoie, getPublicUrl et createSignedUrl donnent l'adresse.
  • Le stockage a sa propre RLS, sur storage.objects : une politique par usage, par exemple un dossier par utilisateur.
  • Realtime demande d'ajouter la table à la publication supabase_realtime.
  • channel().on('postgres_changes', ...).subscribe() écoute, removeChannel ferme.
  • Aucune clé secrète dans ces exemples : tout passe par la clé publique et la RLS.

Pour aller plus loin

Sources officielles

Ensuite

Projet
Lignes de code
Niveau
Cave
Appartement
Supabase
Pièce
6/7
Plan n°
supabase/06.stockage-et-temps-reel
Lignes de code · fiches rédigées à partir des documentations officielles, sources citées en bas de chaque page.