# Fichier: python_cheats/cheatsheets/firebase.txt
# Cheatsheet Firebase - Guide Complet pour Débutants


[OK] QU'EST-CE QUE FIREBASE ? (EXPLICATION DÉTAILLÉE POUR DÉBUTANTS)

Firebase est une plateforme de développement d'applications mobile et web créée par Google.
Elle fournit des services backend clés en main (Backend-as-a-Service - BaaS) qui permettent
de développer rapidement sans gérer de serveurs.

# === COMPRENDRE FIREBASE - ANALOGIE SIMPLE ===

Imaginez que vous voulez ouvrir un restaurant :

SANS FIREBASE (Méthode traditionnelle):
- Vous devez acheter/louer un local (serveur)
- Installer cuisine, tables, chaises (infrastructure)
- Gérer électricité, eau, maintenance (gestion serveur)
- Embaucher personnel (administrateurs système)
- Gérer sécurité, alarmes (sécurité réseau)
- Tout ceci AVANT même de pouvoir cuisiner (coder votre app)

AVEC FIREBASE (Méthode moderne):
- Vous louez une cuisine équipée prête à l'emploi
- Tout est déjà installé et fonctionnel
- Vous vous concentrez uniquement sur la cuisine (votre code)
- Vous payez seulement ce que vous utilisez
- L'entretien est automatique

# === QU'EST-CE QU'UN BACKEND ? ===

Backend = "Arrière-boutique" de votre application

Frontend (ce que voit l'utilisateur):
- Interface utilisateur
- Boutons, formulaires
- Design, animations
- Exemple: Page de connexion avec email/password

Backend (ce qui se passe en coulisses):
- Vérification que l'email existe
- Vérification du mot de passe
- Création de session utilisateur
- Stockage des données
- Envoi d'emails
- Traitement des paiements

Firebase REMPLACE tout le backend traditionnel !

# === ANALOGIE: APPLICATION DE MESSAGERIE ===

Vous créez une app de chat comme WhatsApp:

CE QUE VOUS CODEZ (Frontend):
- Interface de chat
- Bouton "Envoyer message"
- Affichage des messages

CE QUE FIREBASE FAIT AUTOMATIQUEMENT (Backend):
- Stocke les messages dans une base de données
- Synchronise en temps réel entre tous les utilisateurs
- Gère l'authentification (qui peut voir quoi)
- Envoie notifications push
- Stocke les photos/vidéos envoyées

Sans Firebase, vous devriez coder TOUT ça vous-même !

# === POURQUOI "SANS SERVEUR" ? ===

Serveur traditionnel:
1. Acheter/louer un ordinateur puissant (serveur)
2. Installer système d'exploitation (Linux, Windows Server)
3. Configurer base de données (MySQL, MongoDB)
4. Gérer sécurité, mises à jour
5. Surveiller 24/7
6. Gérer la charge (si 1000 utilisateurs arrivent en même temps)

Firebase (Serverless):
1. Vous écrivez votre code
2. Firebase s'occupe de TOUT le reste automatiquement
3. Scalabilité automatique (1 utilisateur ou 1 million)
4. Vous ne payez que ce que vous utilisez

# === EXEMPLE CONCRET: TODO LIST ===

APPLICATION TODO LIST SIMPLE

Fonctionnalités nécessaires:
1. Utilisateur peut créer un compte
2. Utilisateur peut se connecter
3. Utilisateur peut ajouter des tâches
4. Utilisateur peut voir ses tâches
5. Utilisateur peut modifier/supprimer tâches

SANS FIREBASE (approche traditionnelle):
[X] Louer serveur web ($10-50/mois minimum)
[X] Installer et configurer base de données
[X] Coder système d'authentification (sécurité complexe!)
[X] Coder API REST pour chaque opération
[X] Gérer sessions utilisateurs
[X] Sécuriser contre piratage
[X] Gérer backups
[X] Monitorer performances
[X] Gérer certificats SSL
Temps: 2-4 semaines pour un développeur expérimenté

AVEC FIREBASE:
[OK] 5 lignes de code pour authentification
[OK] 3 lignes de code pour ajouter une tâche
[OK] 3 lignes de code pour lire les tâches
[OK] Sécurité gérée automatiquement
[OK] Backups automatiques
[OK] SSL automatique
[OK] Gratuit jusqu'à des milliers d'utilisateurs
Temps: 2-3 heures même pour un débutant!

# === FIREBASE EST COMME... ===

- Électricité: Vous branchez et ça fonctionne, sans construire centrale électrique
- Restaurant livraison: Vous commandez, c'est livré, sans gérer logistique
- Cloud storage (Dropbox/Google Drive): Vous uploadez, c'est stocké et accessible partout
- Uber: Vous demandez une course, tout est géré automatiquement

Firebase = Vous demandez un service, il est fourni instantanément

# Services principaux:
- Authentication (authentification utilisateurs)
- Firestore Database (base de données NoSQL temps réel)
- Realtime Database (base de données JSON temps réel)
- Storage (stockage de fichiers)
- Hosting (hébergement web)
- Cloud Functions (fonctions serverless)
- Cloud Messaging (notifications push)
- Analytics (analyse d'utilisation)
- Crashlytics (rapports d'erreurs)

# === EXPLICATION DÉTAILLÉE DE CHAQUE SERVICE ===

# 1. AUTHENTICATION (Authentification)
QU'EST-CE QUE C'EST ?
Le service qui gère les comptes utilisateurs et la connexion.

ANALOGIE:
Comme le portier d'un immeuble qui vérifie votre identité avant de vous laisser entrer.

CE QUE VOUS POUVEZ FAIRE:
- Créer des comptes utilisateurs (inscription)
- Connecter les utilisateurs (login)
- Connexion avec Google, Facebook, Twitter
- Connexion avec email/mot de passe
- Réinitialiser mot de passe oublié
- Déconnexion
- Vérification d'email

EXEMPLE CONCRET:
Utilisateur clique "S'inscrire avec Google" -> Firebase vérifie avec Google -> 
Utilisateur connecté automatiquement -> Vous recevez infos utilisateur (nom, email, photo)

AVANT FIREBASE (complexe):
- Créer table utilisateurs dans base de données
- Hasher les mots de passe (sécurité)
- Créer système de tokens
- Gérer sessions
- Implémenter OAuth pour Google/Facebook
- Créer système de reset password
- Gérer emails de vérification
Temps: 1-2 semaines

AVEC FIREBASE (simple):
createUserWithEmailAndPassword(auth, email, password)
Temps: 5 minutes

# 2. FIRESTORE DATABASE (Base de données)
QU'EST-CE QUE C'EST ?
Une base de données pour stocker les données de votre application.

ANALOGIE:
Comme une bibliothèque géante où chaque livre (document) est rangé dans des étagères 
(collections), et vous pouvez retrouver n'importe quel livre instantanément.

STRUCTURE:
Collections (dossiers) > Documents (fichiers) > Champs (contenu)

Exemple pour une app de blog:
posts/                    <- Collection "posts"
  post1/                  <- Document avec ID "post1"
    title: "Mon article"  <- Champs
    author: "John"
    likes: 42
  post2/
    title: "Autre article"
    author: "Jane"
    likes: 15

CE QUE VOUS POUVEZ FAIRE:
- Créer des données (Create)
- Lire des données (Read)
- Modifier des données (Update)
- Supprimer des données (Delete)
- Rechercher dans les données
- Écouter changements en temps réel

TEMPS RÉEL = MAGIE:
Quand un utilisateur modifie une donnée, TOUS les autres utilisateurs voient 
le changement INSTANTANÉMENT, sans recharger la page!

Exemple app de chat:
Alice envoie "Bonjour" -> Stocké dans Firestore -> Bob voit "Bonjour" apparaître 
INSTANTANÉMENT sur son écran (sans recharger!)

AVANT FIREBASE:
- Installer MySQL/MongoDB
- Créer schémas de tables
- Écrire requêtes SQL
- Gérer connexions
- Implémenter WebSockets pour temps réel
- Gérer indexation
Temps: Plusieurs jours

AVEC FIREBASE:
addDoc(collection(db, 'messages'), { text: 'Bonjour' })
Temps: 1 ligne de code

# 3. STORAGE (Stockage de fichiers)
QU'EST-CE QUE C'EST ?
Service pour stocker et servir des fichiers (images, vidéos, PDF, etc.)

ANALOGIE:
Comme Google Drive ou Dropbox, mais intégré directement dans votre app.

CE QUE VOUS POUVEZ FAIRE:
- Upload de photos de profil
- Upload de vidéos
- Stockage de documents PDF
- Upload de fichiers audio
- Téléchargement de fichiers
- Génération d'URLs pour accès

EXEMPLE CONCRET:
Utilisateur choisit photo de profil -> Vous uploadez vers Firebase Storage -> 
Firebase génère URL sécurisée -> Vous affichez la photo partout dans l'app

CARACTÉRISTIQUES:
- Fichiers accessibles par URL
- Redimensionnement automatique possible
- CDN intégré (fichiers servis rapidement partout dans le monde)
- Sécurisé par règles d'accès

AVANT FIREBASE:
- Configurer serveur de fichiers
- Gérer uploads
- Générer URLs
- Configurer CDN
- Gérer sécurité
- Optimiser images
Temps: 1 semaine

AVEC FIREBASE:
uploadBytes(storageRef, file)
Temps: 2 lignes de code

# 4. HOSTING (Hébergement web)
QU'EST-CE QUE C'EST ?
Service pour mettre votre site web en ligne.

ANALOGIE:
Comme louer une vitrine sur l'avenue principale d'internet.

CE QUE VOUS POUVEZ FAIRE:
- Héberger site web statique
- Héberger applications React/Vue/Angular
- Certificat SSL automatique (HTTPS)
- Domaine personnalisé (monsite.com)
- CDN mondial intégré

PROCESSUS SIMPLE:
1. Vous développez votre site localement
2. Vous tapez: firebase deploy
3. Votre site est en ligne!
4. URL générée: monprojet.web.app

AVANT FIREBASE:
- Acheter hébergement ($5-20/mois)
- Configurer serveur web (Apache/Nginx)
- Installer certificat SSL
- Configurer DNS
- Upload fichiers via FTP
Temps: Plusieurs heures

AVEC FIREBASE:
firebase deploy
Temps: 30 secondes

# 5. CLOUD FUNCTIONS (Fonctions serverless)
QU'EST-CE QUE C'EST ?
Code backend qui s'exécute automatiquement en réponse à des événements.

ANALOGIE:
Comme avoir un assistant qui fait des tâches automatiquement quand quelque chose se passe.

EXEMPLES D'UTILISATION:

Exemple 1 - Email de bienvenue:
Événement: Nouvel utilisateur créé
-> Function s'exécute automatiquement
-> Envoie email "Bienvenue!"

Exemple 2 - Traitement d'image:
Événement: Photo uploadée
-> Function s'exécute
-> Crée miniature 200x200px
-> Optimise la photo
-> Sauvegarde versions

Exemple 3 - Nettoyage automatique:
Événement: Utilisateur supprime compte
-> Function s'exécute
-> Supprime toutes ses données
-> Supprime ses fichiers
-> Notifie administrateur

DÉCLENCHEURS (Triggers):
- Nouveau document Firestore créé
- Document modifié
- Fichier uploadé dans Storage
- Utilisateur créé/supprimé
- Requête HTTP reçue
- Horaire planifié (tous les jours à 2h)

AVANT FIREBASE:
- Louer serveur dédié
- Configurer cron jobs
- Gérer événements
- Maintenir serveur actif 24/7
Coût: $20-100/mois minimum

AVEC FIREBASE:
- Écrivez la fonction
- Deploy
- Elle s'exécute automatiquement
- Vous payez seulement quand elle s'exécute
Coût: Souvent GRATUIT (125K exécutions/mois gratuit)

# 6. CLOUD MESSAGING (Notifications push)
QU'EST-CE QUE C'EST ?
Envoyer notifications à vos utilisateurs même quand app fermée.

ANALOGIE:
Comme envoyer un SMS, mais c'est gratuit et apparaît comme notification app.

EXEMPLES:
- "Vous avez un nouveau message"
- "Votre commande est livrée"
- "Martin a aimé votre photo"
- "Rappel: Rendez-vous dans 1h"

CE QUI SE PASSE:
Votre app -> Firebase -> Serveurs Google -> Téléphone utilisateur -> Notification!

Fonctionne sur:
- Android
- iOS
- Web (navigateur)

# 7. ANALYTICS (Analyse d'utilisation)
QU'EST-CE QUE C'EST ?
Comprendre comment les utilisateurs utilisent votre app.

CE QUE VOUS DÉCOUVREZ:
- Combien d'utilisateurs actifs
- Quelles pages sont les plus visitées
- Où les utilisateurs quittent l'app
- D'où viennent les utilisateurs (pays, ville)
- Quel appareil ils utilisent (iPhone, Android, etc.)
- Combien de temps ils restent

EXEMPLE:
Votre app de recettes:
- 1000 utilisateurs actifs hier
- 70% regardent recettes desserts
- Durée moyenne: 5 minutes
- 80% sur mobile
- Pic d'utilisation: 19h-21h

Cette information vous aide à améliorer l'app!

# 8. CRASHLYTICS (Rapports d'erreurs)
QU'EST-CE QUE C'EST ?
Détecte automatiquement quand votre app plante et vous envoie rapport détaillé.

ANALOGIE:
Comme une boîte noire d'avion qui enregistre tout avant un crash.

CE QUE VOUS RECEVEZ:
- Message d'erreur exact
- À quelle ligne de code ça plante
- Quel appareil (iPhone 12, Samsung Galaxy, etc.)
- Version du système (iOS 15, Android 12)
- Que faisait l'utilisateur juste avant

SANS CRASHLYTICS:
Utilisateur: "Votre app plante!"
Vous: "Où? Quand? Quel téléphone? Que faisiez-vous?"
Utilisateur: "Je sais pas... ça plantait juste"
Vous: "[LOUDLY_CRYING_FACE]" (impossible à débugger)

AVEC CRASHLYTICS:
Crash -> Rapport automatique détaillé -> Vous savez exactement quoi corriger

# === RÉCAPITULATIF SIMPLE ===

Firebase c'est comme commander un repas complet au restaurant:

Authentication = Portier qui vérifie identité
Firestore = Cuisine qui prépare et stocke la nourriture
Storage = Réfrigérateur pour ingrédients spéciaux (fichiers)
Hosting = La salle où clients mangent (votre site web)
Functions = Chef cuisinier qui réagit aux commandes
Messaging = Serveur qui apporte notifications
Analytics = Livre de comptes qui suit les clients
Crashlytics = Inspecteur de santé qui détecte problèmes

Le tout géré automatiquement, vous vous concentrez sur le menu (votre code)!

# Avantages:
[OK] Configuration rapide
[OK] Pas de gestion serveur
[OK] Scalabilité automatique
[OK] Sécurité intégrée
[OK] Gratuit pour débuter
[OK] Intégration Google Cloud

# === EXPLICATION DÉTAILLÉE DES AVANTAGES ===

# 1. CONFIGURATION RAPIDE (10 minutes vs 1 semaine)

EXEMPLE CONCRET - Créer une app de chat:

Méthode traditionnelle (1 semaine minimum):
Jour 1: Louer serveur, installer Linux
Jour 2: Installer Node.js, configurer base données
Jour 3: Coder API REST pour messages
Jour 4: Implémenter WebSockets temps réel
Jour 5: Coder système authentification
Jour 6: Sécuriser, tester
Jour 7: Déployer, configurer DNS

Avec Firebase (30 minutes):
1. Créer compte Firebase (2 min)
2. Créer projet (2 min)
3. npm install firebase (1 min)
4. Copier-coller config (5 min)
5. Coder envoi message (10 min)
6. Coder affichage messages (10 min)
7. C'EST FINI! App fonctionnelle!

POURQUOI C'EST RAPIDE?
Tout est pré-construit. Comme acheter un gâteau vs le faire de zéro.

# 2. PAS DE GESTION SERVEUR

QU'EST-CE QU'UN SERVEUR?
Un ordinateur puissant qui tourne 24/7 pour gérer votre app.

PROBLÈMES TRADITIONNELS:
- Serveur tombe en panne à 3h du matin -> Vous devez vous réveiller et réparer
- Mise à jour sécurité -> Vous devez l'installer manuellement
- Disque dur plein -> Vous devez ajouter de l'espace
- Trop de visiteurs -> Serveur ralentit/crash
- Virus/Piratage -> Vous devez sécuriser

AVEC FIREBASE:
Firebase gère TOUT automatiquement. Vous dormez tranquille!

ANALOGIE:
Voiture traditionnelle: Vous gérez essence, vidange, réparations, assurance
Uber: Vous demandez une course, tout est géré

# 3. SCALABILITÉ AUTOMATIQUE

SCALABILITÉ = Capacité à gérer beaucoup d'utilisateurs

SCÉNARIO RÉEL:
Vous créez une app. Jour 1: 10 utilisateurs. Mois 6: 100,000 utilisateurs!

Serveur traditionnel:
10 users -> 1 serveur petit ($20/mois) [OK]
100 users -> Serveur ralentit... [ATTENTION]
1,000 users -> Serveur crash! [IMPACT]
-> Vous devez acheter serveur plus gros ($100/mois)
-> Migrer toutes les données (risqué!)
-> Reconfigurer tout
-> App indisponible pendant migration

10,000 users -> Encore plus gros serveur ($500/mois)
-> Même processus pénible
-> Stress permanent

100,000 users -> Plusieurs serveurs, load balancer, complexité extrême
-> Embaucher DevOps engineer ($80k/an)

AVEC FIREBASE:
10 users -> Fonctionne parfaitement [OK]
100 users -> Fonctionne parfaitement [OK]
1,000 users -> Fonctionne parfaitement [OK]
10,000 users -> Fonctionne parfaitement [OK]
100,000 users -> Fonctionne parfaitement [OK]
1,000,000 users -> Fonctionne parfaitement [OK]

VOUS NE FAITES RIEN! C'est automatique!

POURQUOI?
Google utilise la même infrastructure que Gmail, YouTube, Google Search.
Votre petite app bénéficie de l'infrastructure qui gère des MILLIARDS d'utilisateurs!

ANALOGIE:
Traditionnel: Vous gérez magasin. Beaucoup de clients -> Vous devez agrandir, embaucher
Firebase: Vous vendez sur Amazon. Ils gèrent logistique, peu importe le volume

# 4. SÉCURITÉ INTÉGRÉE

SÉCURITÉ = Protéger vos données des pirates

PROBLÈMES DE SÉCURITÉ COURANTS:
1. Injection SQL -> Pirate vole toutes vos données
2. XSS (Cross-Site Scripting) -> Pirate exécute code malicieux
3. CSRF (Cross-Site Request Forgery) -> Pirate fait actions à votre place
4. Man-in-the-middle -> Pirate intercepte communications
5. DDoS -> Pirate submerge serveur de requêtes

EXEMPLE PIRATAGE TYPIQUE (sans Firebase):
Site de e-commerce mal sécurisé:
-> Pirate trouve faille SQL injection
-> Télécharge base de données avec cartes bancaires
-> Vous perdez clients, confiance, argent
-> Poursuites légales possibles

AVEC FIREBASE:
[OK] Protection injection SQL (pas de SQL direct!)
[OK] Protection XSS (sandboxing automatique)
[OK] HTTPS forcé partout (chiffrement)
[OK] Protection DDoS (infrastructure Google)
[OK] Authentification sécurisée (OAuth2)
[OK] Security Rules (contrôle d'accès)
[OK] Audits sécurité réguliers par Google

Firebase utilise même sécurité que:
- Gmail (1.5 milliards d'utilisateurs)
- Google Drive
- YouTube

Si Google peut sécuriser ça, votre app est bien protégée!

MAIS ATTENTION:
Firebase VOUS DONNE les outils. Vous devez les utiliser correctement!
- Ne pas rendre toutes données publiques
- Bien configurer Security Rules
- Ne pas partager API keys

C'est comme avoir une porte blindée: encore faut-il la fermer à clé!

# 5. GRATUIT POUR DÉBUTER (Plan Spark)

COÛT TRADITIONNEL:
Serveur basique: $10-20/mois
Base de données: $10/mois
Stockage fichiers: $5/mois
Certificat SSL: $50/an
CDN: $20/mois
Backup: $10/mois
Total minimum: $50-80/mois
AVANT MÊME D'AVOIR UN SEUL UTILISATEUR!

FIREBASE PLAN GRATUIT (Spark):
- 50,000 lectures Firestore/jour
- 20,000 écritures/jour
- 1 GB stockage Firestore
- 5 GB stockage fichiers
- 10 GB hébergement web
- 125,000 exécutions Functions/mois
- Authentification ILLIMITÉE
- 0$ jusqu'à ces limites!

COMBIEN D'UTILISATEURS POSSIBLE GRATUIT?

Exemple app simple (todo list):
Chaque utilisateur par jour:
- 50 lectures (voir ses tâches)
- 10 écritures (ajouter tâches)
- 100 KB stockage

Avec plan gratuit: 400-500 utilisateurs actifs GRATUITEMENT!

Pour petit projet/prototype: PARFAIT!

QUAND PAYER?
Seulement quand vous grandissez (bon problème à avoir!)
Et plan payant = Pay-as-you-go (payez seulement ce que vous utilisez)

# 6. INTÉGRATION GOOGLE CLOUD

GOOGLE CLOUD = Plateforme cloud professionnelle de Google

CE QUE ÇA SIGNIFIE:
Firebase connecté à écosystème Google entier:
- Google Maps (ajouter cartes facilement)
- Google Analytics (analyses avancées)
- Google AI (intelligence artificielle)
- Cloud Vision (reconnaissance d'images)
- Cloud Translation (traduction automatique)
- BigQuery (analyse de données massives)

EXEMPLE CONCRET:
Vous créez app de photos:

Fonctionnalité: "Détecter ce qui est dans la photo"
Sans intégration: Coder IA vous-même (des mois de travail)
Avec Firebase + Google Cloud Vision:

uploadPhoto() -> Firebase Storage
-> Cloud Function détecte upload
-> Appelle Cloud Vision API
-> Retourne: "chien, parc, soleil"
-> Sauvegarde tags dans Firestore
-> Total: 20 lignes de code!

AUTRE EXEMPLE:
App multilingue:

Message en français -> Cloud Translation API -> Traduit en 100+ langues
Avec intégration: 5 lignes de code
Sans: Impossible sans équipe entière

INFRASTRUCTURE GOOGLE:
Firebase tourne sur même infrastructure que:
- Gmail (99.99% uptime = quasi jamais en panne)
- YouTube (gère des milliards de vidéos)
- Google Search (des millions de requêtes/seconde)

Votre petite app bénéficie de cette puissance!

RÉSUMÉ:
Firebase + Google Cloud = Superpouvoirs pour votre app
Sans écrire code complexe!

# Idéal pour:
- Applications mobiles (iOS/Android)
- Applications web (React, Vue, Angular)
- Prototypes et MVPs
- Applications temps réel (chat, collaboration)
- Applications sans backend complexe

# === EXPLICATION: POUR QUI EST FIREBASE? ===

# 1. APPLICATIONS MOBILES (iOS/Android)

POURQUOI PARFAIT?
Les apps mobiles ont besoin de:
- Synchronisation entre appareils
- Notifications push
- Stockage cloud
- Authentification simple

Firebase fait TOUT ça nativement!

EXEMPLE - App de fitness:
iPhone: Utilisateur ajoute "Course 5km"
-> Sync Firebase
-> Apple Watch: Voit la course
-> iPad: Voit la course
-> Android: Ami voit la course (si partagée)
TOUT EN TEMPS RÉEL!

SDK NATIFS:
- Swift (iOS): pod 'Firebase'
- Kotlin (Android): implementation 'com.google.firebase'
- React Native: @react-native-firebase
- Flutter: flutter_firebase

# 2. APPLICATIONS WEB (React, Vue, Angular)

POURQUOI PARFAIT?
Les SPAs (Single Page Applications) modernes ont besoin de:
- Backend API
- Base de données temps réel
- Authentification
- Hébergement

Firebase = Solution complète!

EXEMPLE - Dashboard analytics:
Graphique montre données en temps réel
-> Nouvelle vente -> Graph se met à jour INSTANTANÉMENT
-> Tous les admins connectés voient changement
-> Sans recharger page!

INTÉGRATIONS:
- React: npm install firebase
- Vue: npm install firebase
- Angular: npm install @angular/fire
- Next.js: Compatible natif
- Svelte: npm install firebase

# 3. PROTOTYPES ET MVPs

MVP = Minimum Viable Product (Produit Minimum Viable)
Prototype = Version test rapide

POURQUOI IDÉAL?

Situation typique startup:
- Vous avez une idée d'app
- Besoin tester si ça intéresse les gens
- Budget limité
- Besoin lancer VITE

AVEC FIREBASE:
Week 1: Coder prototype
Week 2: Montrer à 100 utilisateurs test
Week 3: Collecter feedback
Week 4: Ajuster
-> Décider si continuer ou pivoter

SANS FIREBASE:
Week 1-4: Configurer infrastructure
Week 5-8: Coder backend
Week 9-12: Coder frontend
Week 13: Enfin tester avec utilisateurs
-> 3 mois perdus, peut-être pour rien!

EXEMPLE RÉEL:
Instagram au début:
- 2 développeurs
- Prototype en 8 semaines
- Lancé avec infrastructure simple
-> 1 million utilisateurs en 2 mois
-> Si avaient passé 6 mois sur infrastructure: opportunité ratée!

Firebase = Lancer vite, valider idée, ajuster

# 4. APPLICATIONS TEMPS RÉEL

TEMPS RÉEL = Changements visibles instantanément sans recharger

EXEMPLES D'APPS TEMPS RÉEL:

Chat/Messagerie:
-> Message envoyé = apparaît chez destinataire instantanément
-> "En train d'écrire..." en direct
-> Messages lus en temps réel

Collaboration (Google Docs style):
-> Plusieurs personnes éditent document ensemble
-> Chacun voit changements des autres en direct
-> Pas de conflits

Jeux multijoueurs:
-> Joueur bouge = autres voient mouvement instantanément
-> Score mis à jour en temps réel

Tableaux de bord live:
-> Nouvelles ventes = graphique se met à jour immédiatement
-> Alertes en temps réel

Suivi de livraison:
-> Livreur bouge = client voit position sur carte en direct
-> Comme Uber Eats

POURQUOI FIREBASE EXCELLENT POUR ÇA?

Firestore + onSnapshot = Magie temps réel!

Code simple:
onSnapshot(collection(db, 'messages'), (snapshot) => {
  // S'exécute AUTOMATIQUEMENT à chaque changement!
  snapshot.forEach(doc => afficherMessage(doc.data()))
})

Sans Firebase:
-> Implémenter WebSockets (complexe!)
-> Gérer connexions
-> Gérer reconnexions
-> Gérer conflits
-> Synchroniser états
-> Des semaines de travail!

# 5. APPLICATIONS SANS BACKEND COMPLEXE

BACKEND COMPLEXE = Quand vous avez besoin de:
- Logique métier sophistiquée
- Calculs lourds côté serveur
- Intégrations complexes avec autres systèmes
- Transactions financières complexes
- IA/Machine Learning custom

BACKEND SIMPLE = Quand vous avez besoin de:
- Stocker données utilisateurs
- Authentification
- Upload fichiers
- Notifications
- CRUD basique (Create, Read, Update, Delete)

FIREBASE PARFAIT POUR:
[OK] Todo list
[OK] App de notes
[OK] Blog
[OK] Portfolio
[OK] E-commerce simple
[OK] Réseau social simple
[OK] App de rencontres
[OK] App de recettes
[OK] Galerie photos
[OK] Forum
[OK] Chat
[OK] App de fitness
[OK] App de météo

FIREBASE MOINS ADAPTÉ POUR:
[X] Plateforme bancaire (réglementations strictes)
[X] Système de trading algorithmique (latence critique)
[X] ERP entreprise (logique métier complexe)
[X] Moteur de recherche (besoins spécifiques)
[X] App nécessitant calculs lourds serveur (big data processing)

MAIS: Même pour ces cas, Firebase peut être utilisé pour PARTIE de l'app!

Exemple app de trading:
- Algo trading: Serveur custom
- Interface utilisateur: Firebase Hosting
- Authentification: Firebase Auth
- Notifications: Firebase Messaging
- Chat support: Firestore

Firebase = Fondation solide, vous ajoutez pièces custom si besoin

# === RÉCAPITULATIF: DEVRIEZ-VOUS UTILISER FIREBASE? ===

UTILISEZ FIREBASE SI:
[OK] Vous démarrez un nouveau projet
[OK] Vous voulez lancer rapidement
[OK] Vous êtes petit équipe/solo
[OK] Vous avez budget limité
[OK] Vous voulez fonctionnalités temps réel
[OK] Vous développez app mobile
[OK] Vous créez prototype/MVP
[OK] Vous ne voulez pas gérer serveurs
[OK] Vous avez besoin scalabilité automatique

NE UTILISEZ PAS FIREBASE SI:
[X] Besoin contrôle total infrastructure
[X] Réglementations nécessitent serveurs propres
[X] Backend extrêmement complexe
[X] Besoin requêtes SQL complexes (JOINs multiples)
[X] Vous aimez vraiment gérer serveurs vous-même (masochiste? [SMILING_FACE_WITH_OPEN_MOUTH_AND_SMILING_EYES])

RÈGLE SIMPLE:
90% des apps peuvent utiliser Firebase.
Si vous vous demandez "Est-ce que Firebase convient?", la réponse est probablement OUI!

Commencez avec Firebase. Si vraiment besoin autre chose plus tard, vous migrerez.
Mais souvent, Firebase suffit même pour grandes apps!

Exemples apps qui utilisent Firebase:
- Duolingo (apprentissage langues)
- Lyft (VTC)
- Alibaba (e-commerce)
- The New York Times (news)
- Venmo (paiements)

Si ça convient pour eux, ça conviendra probablement pour vous! [RAPIDE]

# Idéal pour:
- Applications mobiles (iOS/Android)
- Applications web (React, Vue, Angular)
- Prototypes et MVPs
- Applications temps réel (chat, collaboration)
- Applications sans backend complexe


[OK] CRÉATION DE COMPTE & PROJET (GUIDE PAS À PAS DÉBUTANT)


# === POURQUOI CRÉER UN COMPTE FIREBASE? ===

ANALOGIE:
Firebase = magasin géant d'outils pour développeurs
Compte = Carte de membre pour accéder au magasin
Projet = Votre panier spécifique pour une app

1 compte = Plusieurs projets possibles
Exemple:
- Compte: john.doe@gmail.com
- Projet 1: TodoApp
- Projet 2: ChatApp  
- Projet 3: BlogPersonnel

# === ÉTAPE 1: CRÉER COMPTE FIREBASE ===

# 1. Créer un compte Firebase
# Aller sur: https://firebase.google.com/

QU'EST-CE QUI SE PASSE:
1. Navigateur -> Ouvre site Firebase
2. Vous voyez page d'accueil avec gros bouton

# Cliquer: "Get Started" ou "Commencer"

IMPORTANT: Besoin d'un compte Google!

Si vous avez déjà Gmail:
-> Utilisez ce compte (recommandé)

Si pas de compte Google:
-> Créer compte Google d'abord (gmail.com)
-> Revenir sur Firebase

# Se connecter avec compte Google

QU'EST-CE QUI SE PASSE:
1. Popup Google apparaît
2. Liste vos comptes Google (si plusieurs)
3. Choisissez compte à utiliser
4. Google demande permissions:
   - "Firebase veut accéder à profil basique"
   - C'est normal! Acceptez
5. Redirigé vers Console Firebase

CONSOLE FIREBASE = Tableau de bord où vous gérez tout

PREMIÈRE FOIS:
Vous voyez page "Bienvenue"
Pas de projets encore
Bouton "+ Add project" ou "+ Ajouter un projet"

# === ÉTAPE 2: CRÉER PREMIER PROJET ===

# 2. Créer un nouveau projet
# Dans la console Firebase:
# - Cliquer "Add project" / "Ajouter un projet"

QU'EST-CE QU'UN PROJET?
Projet = Container pour UNE application complète

Exemple:
- Projet "TodoApp" = Votre app de tâches
- Projet "ChatApp" = Votre app de chat

Chaque projet a:
- Sa propre base de données
- Ses propres utilisateurs
- Son propre stockage
- Ses propres règles de sécurité

# - Entrer nom du projet: "mon-super-projet"

POPUP APPARAÎT - Étape 1/3: "Nom du projet"

CONSEILS NOMMAGE:
[OK] Utilisez nom descriptif: "TodoApp", "ChatMesPotes", "BlogCuisine"
[OK] Lettres, chiffres, tirets OK
[X] Pas d'espaces
[X] Pas de caractères spéciaux (@#$%)

Exemple: "ma-premiere-app"

SOUS LE NOM:
Vous voyez: "Project ID: ma-premiere-app-a1b2c"

PROJECT ID = Identifiant unique mondial
- Généré automatiquement
- Vous pouvez le modifier maintenant
- IMPOSSIBLE à changer après!
- Utilisé dans URLs (ma-premiere-app-a1b2c.web.app)

CONSEIL: Laissez généré automatiquement (sauf si nom spécifique voulu)

# - (Optionnel) Activer Google Analytics

POPUP - Étape 2/3: "Google Analytics"

GOOGLE ANALYTICS = Outil pour comprendre vos utilisateurs

CE QUE VOUS VERREZ:
- Combien d'utilisateurs par jour
- Quelles pages visitent
- Quel pays, quel appareil
- Combien de temps dans l'app

Toggle: "Enable Google Analytics for this project"

POUR DÉBUTANT:
-> ACTIVEZ (c'est gratuit et très utile!)

Si vous activez, étape supplémentaire:
"Create or choose Analytics account"

OPTIONS:
1. "Default Account for Firebase" (recommandé)
2. "Create new account" (si vous voulez nom spécifique)

CONSEIL DÉBUTANT: Choisissez "Default Account for Firebase"

Checkboxes:
[x] J'accepte conditions Google Analytics
[x] J'accepte partage données (optionnel mais recommandé)

# - Cliquer "Create project"

POPUP - Étape 3/3: "Création en cours..."

QU'EST-CE QUI SE PASSE:
Firebase crée infrastructure pour votre projet:
1. Création base de données
2. Configuration stockage
3. Initialisation authentification
4. Configuration analytics
5. Génération clés API

DURÉE: 30 secondes à 1 minute

Barre de progression animée:
"Creating your project..."
"Setting up Firebase..."
"Almost there..."

# Cliquer "Continue" quand "Your project is ready!"

FÉLICITATIONS! [BRAVO]
Votre projet Firebase est créé!

Vous êtes maintenant dans le DASHBOARD du projet

# === COMPRENDRE LE DASHBOARD (TABLEAU DE BORD) ===

QU'EST-CE QUE VOUS VOYEZ:

BARRE LATÉRALE GAUCHE (Menu principal):

[GRAPHIQUE] Project Overview (Vue d'ensemble)
-> Statistiques générales
-> Utilisateurs actifs
-> État des services

[RAPIDE] Upgrade (Plan tarifaire)
-> Actuellement: Spark (Gratuit)
-> Possibilité upgrade vers Blaze (Payant)

BUILD (Construire - Services principaux):
[SECURISE] Authentication -> Gérer utilisateurs
[ARCHIVE] Firestore Database -> Base de données
[SAUVEGARDE] Realtime Database -> Base données JSON
[PACKAGE] Storage -> Stockage fichiers
[WEB] Hosting -> Hébergement web
[RAPIDE] Functions -> Cloud functions
[NOTIF] Messaging -> Notifications

RELEASE & MONITOR (Lancer & Surveiller):
[HAUSSE] Analytics -> Statistiques utilisation
[IMPACT] Crashlytics -> Rapports erreurs
[RAPIDE] Performance -> Vitesse app

PARTIE CENTRALE:
"Get started by adding Firebase to your app"

3 OPTIONS (3 icônes):
1. [MOBILE] iOS (Icône Apple)
2. [BOT] Android (Icône Android)
3. [WEB] Web (Icône </>)

# === ÉTAPE 3: AJOUTER UNE APPLICATION ===

# 3. Ajouter une application

QUESTION: Quelle plateforme?
- Web: Si vous créez site web ou app React/Vue/Angular
- iOS: Si vous créez app iPhone/iPad (Swift/SwiftUI)
- Android: Si vous créez app Android (Kotlin/Java)

POUR CE GUIDE: On choisit WEB (le plus courant pour débuter)

# Dans le projet, cliquer sur l'icône correspondante:
# - Web: icône </>

POPUP: "Add Firebase to your web app"

# Pour une app Web:
# - Entrer le nom: "Mon App Web"

EXEMPLE NOM:
- "Frontend TodoApp"
- "Site Web Principal"
- "Interface Utilisateur"

Ce nom est JUSTE pour vous organiser (si plusieurs apps dans projet)

# - (Optionnel) Cocher "Set up Firebase Hosting"

FIREBASE HOSTING = Pour mettre votre site en ligne

SI VOUS VOULEZ:
[x] Héberger site web complet -> COCHEZ
[ ] Juste utiliser Firebase dans app existante -> NE COCHEZ PAS

POUR DÉBUTER: NE COCHEZ PAS (vous configurerez plus tard si besoin)

# - Cliquer "Register app"

QU'EST-CE QUI SE PASSE:
Firebase génère configuration unique pour votre app

ÉCRAN SUIVANT: "Add Firebase SDK"

VOUS VOYEZ CODE JAVASCRIPT:

# - Copier les credentials de configuration

# Configuration générée (exemple):
const firebaseConfig = {
  apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  authDomain: "mon-projet.firebaseapp.com",
  projectId: "mon-projet",
  storageBucket: "mon-projet.appspot.com",
  messagingSenderId: "123456789012",
  appId: "1:123456789012:web:abcdef123456"
};

EXPLICATION DE CHAQUE CLÉ:

apiKey: "AIzaSy..."
-> Clé API pour communiquer avec Firebase
-> Comme un mot de passe pour votre app
-> PAS secret (peut être public) mais à protéger quand même

authDomain: "mon-projet.firebaseapp.com"
-> Domaine pour authentification
-> Utilisé pour redirections OAuth (Google login, etc.)

projectId: "mon-projet"
-> ID unique de votre projet
-> Utilisé partout dans Firebase

storageBucket: "mon-projet.appspot.com"
-> Où vos fichiers seront stockés
-> Comme adresse d'un entrepôt

messagingSenderId: "123456..."
-> Pour notifications push
-> Identifie votre app pour Google Cloud Messaging

appId: "1:123456..."
-> Identifiant unique de cette app web
-> Permet Firebase de différencier vos apps (si plusieurs)

# IMPORTANT: Ne jamais committer apiKey dans code public

QU'EST-CE QUE ÇA VEUT DIRE?

Si votre code est sur GitHub PUBLIC:
-> Tout le monde peut voir votre apiKey
-> Risque: Quelqu'un utilise votre quota Firebase
-> Risque: Factures surprises!

SOLUTION: Utiliser variables d'environnement

AU LIEU DE:
const apiKey = "AIzaSyXXXXX"; // Dans code

FAIRE:
const apiKey = process.env.REACT_APP_FIREBASE_API_KEY; // Depuis .env

Fichier .env (pas commité sur Git):
REACT_APP_FIREBASE_API_KEY=AIzaSyXXXXX

Fichier .gitignore:
.env  # <- Empêche .env d'aller sur GitHub

MAIS: apiKey pas aussi sensible qu'on pense
-> Peut être dans code frontend (normal)
-> Vraie sécurité = Security Rules (qu'on verra plus tard)

# Pour production, utiliser variables d'environnement

ENVIRONNEMENTS:

Développement (local):
-> .env.development
-> Utilise projet Firebase "test"

Production (en ligne):
-> .env.production  
-> Utilise projet Firebase "production"

POURQUOI SÉPARER?
-> Tester sans affecter vraies données
-> Erreurs en dev n'impactent pas users

EXEMPLE CONFIGURATION REACT:

# .env (React)
REACT_APP_FIREBASE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
REACT_APP_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com
REACT_APP_FIREBASE_PROJECT_ID=mon-projet
REACT_APP_FIREBASE_STORAGE_BUCKET=mon-projet.appspot.com
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=123456789012
REACT_APP_FIREBASE_APP_ID=1:123456789012:web:abcdef123456

POURQUOI "REACT_APP_" ?
-> Convention Create React App
-> Seulement variables avec REACT_APP_ sont accessibles

AUTRES FRAMEWORKS:

# .env (Vue/Vite)
VITE_FIREBASE_API_KEY=AIzaSyXXX...
VITE_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com

# .env (Next.js)
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyXXX...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com

RÈGLE: Préfixe nécessaire pour variables publiques

# IMPORTANT: Ajouter .env au .gitignore
echo ".env" >> .gitignore

QU'EST-CE QUE .gitignore?
Fichier qui dit à Git "ignore ces fichiers, ne les mets pas sur GitHub"

COMMANDE EXPLICATION:
echo ".env"  -> Écrit texte ".env"
>>           -> Ajoute à la fin du fichier
.gitignore   -> Nom du fichier

RÉSULTAT:
Fichier .env créé localement
Mais JAMAIS envoyé sur GitHub
Chaque développeur a son propre .env

# === FÉLICITATIONS! ===

Vous avez:
[OK] Créé compte Firebase
[OK] Créé votre premier projet
[OK] Ajouté une app web
[OK] Obtenu credentials de configuration
[OK] Compris comment sécuriser ces credentials

PROCHAINE ÉTAPE:
Installer Firebase dans votre code et commencer à l'utiliser!

# === RÉCAPITULATIF VISUEL ===

1. firebase.google.com
   v
2. "Get Started" -> Se connecter avec Google
   v
3. Console Firebase (Dashboard)
   v
4. "+ Add project"
   v
5. Nom projet: "ma-premiere-app"
   v
6. Enable Google Analytics: OUI
   v
7. "Create project"
   v  
8. Attendre 30 secondes...
   v
9. "Continue" -> Dans le projet!
   v
10. Cliquer icône Web (</>)
    v
11. Nom app: "Mon App Web"
    v
12. "Register app"
    v
13. COPIER firebaseConfig
    v
14. Sauvegarder dans votre code
    v
15. PRÊT À CODER! [RAPIDE]

TEMPS TOTAL: 5-10 minutes max!

Comparé à configuration serveur traditionnel (plusieurs jours), c'est MAGIQUE! *


[OK] INSTALLATION - JAVASCRIPT/WEB


# === Installation via NPM (Recommandé) ===

# Installer Firebase SDK
npm install firebase

# Installer des packages spécifiques (modulaire)
npm install firebase/app
npm install firebase/auth
npm install firebase/firestore
npm install firebase/storage

# Versions:
# Firestore operations
collection(db, 'users')
doc(db, 'users', 'userId')
addDoc(collection, data)
setDoc(doc, data)
getDoc(doc)
getDocs(collection)
updateDoc(doc, data)
deleteDoc(doc)
query(collection, where(), orderBy(), limit())
onSnapshot(doc/query, callback)

# Auth operations
createUserWithEmailAndPassword(auth, email, password)
signInWithEmailAndPassword(auth, email, password)
signInWithPopup(auth, provider)
signOut(auth)
onAuthStateChanged(auth, callback)
updateProfile(user, data)

# Storage operations
ref(storage, path)
uploadBytes(ref, file)
uploadBytesResumable(ref, file)
getDownloadURL(ref)
deleteObject(ref)
listAll(ref)


[OK] GLOSSAIRE FIREBASE


# Authentication (Auth)
Service d'authentification gérant connexion/inscription utilisateurs

# Firestore
Base de données NoSQL temps réel, structure: collections > documents

# Realtime Database
Base de données JSON temps réel, plus ancienne que Firestore

# Storage
Service de stockage de fichiers (images, vidéos, documents)

# Hosting
Service d'hébergement pour sites web statiques

# Cloud Functions
Fonctions serverless exécutées dans le cloud

# Security Rules
Règles définissant qui peut lire/écrire les données

# SDK (Software Development Kit)
Bibliothèque logicielle pour intégrer Firebase

# Admin SDK
SDK avec privilèges admin, utilisé côté serveur

# Collection
Conteneur de documents dans Firestore (comme une table SQL)

# Document
Unité de stockage dans Firestore (comme une ligne SQL)

# Subcollection
Collection imbriquée dans un document

# Snapshot
État d'un document/collection à un instant T

# Listener
Fonction écoutant changements en temps réel

# Provider
Service d'authentification (Google, Facebook, etc.)

# Token
Jeton d'authentification prouvant identité utilisateur

# UID (User ID)
Identifiant unique d'un utilisateur

# Blob
Objet binaire (fichier) dans Storage

# Metadata
Informations sur un fichier (type, taille, etc.)

# Batch Write
Écriture de plusieurs documents en une opération atomique

# Transaction
Opération garantissant cohérence des données

# Emulator
Environnement local simulant Firebase

# Quota
Limite d'utilisation (lectures, écritures, stockage)

# Serverless
Architecture sans gestion de serveur

# CORS (Cross-Origin Resource Sharing)
Mécanisme sécurité pour requêtes cross-domain

# Webhook
URL appelée automatiquement par Firebase

# Trigger
Événement déclenchant une Cloud Function

# App Check
Protection contre abus et trafic non autorisé

# Analytics
Service d'analyse d'utilisation de l'application

# Crashlytics
Service de rapport d'erreurs et crashes

# Cloud Messaging (FCM)
Service de notifications push

# Dynamic Links
Liens intelligents fonctionnant sur web et mobile

# Extensions
Modules pré-configurés ajoutant fonctionnalités

# Firebase CLI
Outil ligne de commande pour gérer projets Firebase


[OK] ACRONYMES COURANTS


API - Application Programming Interface
SDK - Software Development Kit
NoSQL - Not Only SQL
CRUD - Create, Read, Update, Delete
JSON - JavaScript Object Notation
HTTP - HyperText Transfer Protocol
HTTPS - HTTP Secure
REST - Representational State Transfer
JWT - JSON Web Token
UID - User ID
URL - Uniform Resource Locator
CDN - Content Delivery Network
SPA - Single Page Application
PWA - Progressive Web App
SSR - Server-Side Rendering
TTL - Time To Live
CORS - Cross-Origin Resource Sharing
FCM - Firebase Cloud Messaging
GCP - Google Cloud Platform
IAM - Identity and Access Management


[OK] ERREURS COURANTES & SOLUTIONS


# auth/email-already-in-use
Erreur: Email déjà utilisé pour un autre compte
Solution: Vérifier si email existe avant inscription

async function checkEmailExists(email) {
  try {
    await signInWithEmailAndPassword(auth, email, 'dummypassword');
  } catch (error) {
    if (error.code === 'auth/user-not-found') {
      return false; // Email disponible
    }
  }
  return true; // Email existe
}


# auth/wrong-password
Erreur: Mot de passe incorrect
Solution: Afficher message clair, proposer reset password


# auth/user-not-found
Erreur: Utilisateur inexistant
Solution: Proposer inscription


# auth/weak-password
Erreur: Mot de passe trop faible (< 6 caractères)
Solution: Validation côté client avant envoi


# auth/invalid-email
Erreur: Format email invalide
Solution: Validation email avec regex

function isValidEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}


# auth/too-many-requests
Erreur: Trop de tentatives de connexion
Solution: Attendre quelques minutes, implémenter rate limiting


# permission-denied
Erreur: Security Rules refusent accès
Solution: Vérifier rules, vérifier authentification


# not-found
Erreur: Document/fichier introuvable
Solution: Vérifier existence avant accès

const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
  // Document existe
} else {
  // Document introuvable
}


# storage/unauthorized
Erreur: Pas autorisé à accéder au fichier
Solution: Vérifier Storage Rules, vérifier authentification


# storage/object-not-found
Erreur: Fichier introuvable dans Storage
Solution: Vérifier chemin fichier


# storage/quota-exceeded
Erreur: Quota Storage dépassé
Solution: Supprimer fichiers inutiles, passer plan Blaze


# functions/internal
Erreur: Erreur interne Cloud Function
Solution: Vérifier logs functions avec `firebase functions:log`


# functions/deadline-exceeded
Erreur: Fonction a dépassé timeout
Solution: Augmenter timeout ou optimiser code


[OK] TIPS & ASTUCES


# 1. Utiliser aliases pour collections
const COLLECTIONS = {
  USERS: 'users',
  POSTS: 'posts',
  COMMENTS: 'comments'
};

// Utiliser
collection(db, COLLECTIONS.USERS);


# 2. Créer helpers réutilisables
// helpers/firestore.js
export async function addDocument(collectionName, data) {
  return await addDoc(collection(db, collectionName), {
    ...data,
    createdAt: new Date(),
    updatedAt: new Date()
  });
}

export async function updateDocument(collectionName, docId, data) {
  return await updateDoc(doc(db, collectionName, docId), {
    ...data,
    updatedAt: new Date()
  });
}


# 3. Gérer états de chargement
function App() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);

  useEffect(() => {
    getData()
      .then(result => setData(result))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <Loader />;
  if (error) return <Error message={error.message} />;
  return <Content data={data} />;
}


# 4. Utiliser try-catch systématiquement
async function safeOperation() {
  try {
    const result = await riskyOperation();
    return { success: true, data: result };
  } catch (error) {
    console.error('Erreur:', error);
    return { success: false, error: error.message };
  }
}


# 5. Créer contexte Auth React
// AuthContext.js
import { createContext, useContext, useState, useEffect } from 'react';
import { onAuthStateChanged } from 'firebase/auth';

const AuthContext = createContext();

export function useAuthContext() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });
    return unsubscribe;
  }, []);

  const value = { user, loading };

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

// Utilisation
function App() {
  return (
    <AuthProvider>
      <Routes />
    </AuthProvider>
  );
}

function Profile() {
  const { user } = useAuthContext();
  return <div>{user?.email}</div>;
}


# 6. Valider données avant Firestore
function validatePost(data) {
  const errors = {};
  
  if (!data.title || data.title.trim().length === 0) {
    errors.title = 'Titre requis';
  }
  
  if (data.title && data.title.length > 100) {
    errors.title = 'Titre trop long (max 100)';
  }
  
  if (!data.content || data.content.trim().length === 0) {
    errors.content = 'Contenu requis';
  }
  
  return {
    isValid: Object.keys(errors).length === 0,
    errors
  };
}

// Utilisation
const { isValid, errors } = validatePost(postData);
if (!isValid) {
  console.error('Erreurs:', errors);
  return;
}

await addDocument('posts', postData);


# 7. Créer utilitaires dates
export function formatDate(date) {
  if (!date) return '';
  
  const d = date.toDate ? date.toDate() : new Date(date);
  return d.toLocaleDateString('fr-FR', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}

export function formatRelativeTime(date) {
  if (!date) return '';
  
  const d = date.toDate ? date.toDate() : new Date(date);
  const now = new Date();
  const seconds = Math.floor((now - d) / 1000);
  
  if (seconds < 60) return 'À l\'instant';
  if (seconds < 3600) return `Il y a ${Math.floor(seconds / 60)} minutes`;
  if (seconds < 86400) return `Il y a ${Math.floor(seconds / 3600)} heures`;
  return `Il y a ${Math.floor(seconds / 86400)} jours`;
}


# 8. Debounce recherches Firestore
import { useState, useEffect } from 'react';

function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// Utilisation
function SearchUsers() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearch = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearch) {
      searchUsers(debouncedSearch);
    }
  }, [debouncedSearch]);

  return (
    <input 
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Rechercher..."
    />
  );
}


# 9. Logger opérations en développement
const isDev = process.env.NODE_ENV === 'development';

export function logOperation(operation, data) {
  if (isDev) {
    console.log(`[${operation}]`, data);
  }
}

// Utilisation
await addDoc(collection(db, 'posts'), data);
logOperation('POST_CREATED', data);


# 10. Créer intercepteur d'erreurs
export function handleFirebaseError(error) {
  const errorMessages = {
    'auth/email-already-in-use': 'Cet email est déjà utilisé',
    'auth/weak-password': 'Mot de passe trop faible',
    'auth/user-not-found': 'Utilisateur introuvable',
    'auth/wrong-password': 'Mot de passe incorrect',
    'permission-denied': 'Accès refusé',
    'not-found': 'Document introuvable',
  };

  return errorMessages[error.code] || 'Une erreur est survenue';
}

// Utilisation
try {
  await signIn(email, password);
} catch (error) {
  const message = handleFirebaseError(error);
  alert(message);
}


[OK] EXTENSIONS FIREBASE POPULAIRES


# Extensions Firebase = modules pré-configurés ajoutant fonctionnalités

# 1. Resize Images
Redimensionne automatiquement images uploadées dans Storage
Use case: Créer thumbnails, versions responsive

# 2. Translate Text
Traduit texte automatiquement avec Cloud Translation API
Use case: Applications multilingues

# 3. Send Email with Sendgrid
Envoie emails via Sendgrid
Use case: Emails transactionnels, newsletters

# 4. Export Collections to BigQuery
Export automatique données Firestore vers BigQuery
Use case: Analytics avancés, data science

# 5. Trigger Email from Firestore
Envoie email automatiquement quand document créé
Use case: Notifications par email

# 6. Delete User Data
Supprime automatiquement toutes données utilisateur
Use case: Conformité RGPD

# 7. Firestore Counter
Gère compteurs distribués efficacement
Use case: Compteurs de likes, vues, etc.

# Installation extension:
# Console Firebase > Extensions > Browse Extensions
# Choisir extension > Install
# Configurer paramètres


[OK] FIREBASE VS ALTERNATIVES


# Firebase vs Supabase
Firebase:
[OK] Écosystème Google complet
[OK] Maturité et stabilité
[OK] Meilleure documentation
[X] Vendor lock-in
[X] Moins de contrôle

Supabase:
[OK] Open source
[OK] PostgreSQL (SQL)
[OK] Plus de contrôle
[X] Moins mature
[X] Moins d'intégrations

# Firebase vs AWS Amplify
Firebase:
[OK] Plus simple à démarrer
[OK] Meilleure intégration mobile
[OK] Temps réel natif

AWS Amplify:
[OK] Plus de services AWS disponibles
[OK] Plus flexible
[X] Courbe d'apprentissage plus raide

# Firebase vs Parse
Firebase:
[OK] Maintenu par Google
[OK] Plus de services
[OK] Meilleure scalabilité

Parse:
[OK] Open source
[OK] Self-hosted possible
[X] Communauté plus petite


[OK] MIGRATION & IMPORT/EXPORT


# === EXPORTER DONNÉES FIRESTORE ===

# Via Console (plan Blaze uniquement)
# Console > Firestore > Import/Export
# Choisir collections et bucket Storage

# Via CLI
gcloud firestore export gs://BUCKET_NAME

# Via code (Admin SDK)
const admin = require('firebase-admin');
const fs = require('fs');

async function exportCollection(collectionName) {
  const snapshot = await admin.firestore().collection(collectionName).get();
  const data = [];
  
  snapshot.forEach(doc => {
    data.push({
      id: doc.id,
      ...doc.data()
    });
  });
  
  fs.writeFileSync(`${collectionName}.json`, JSON.stringify(data, null, 2));
  console.log(`Exporté ${data.length} documents`);
}


# === IMPORTER DONNÉES ===

# Via Console
# Console > Firestore > Import/Export > Import

# Via code
const admin = require('firebase-admin');
const fs = require('fs');

async function importCollection(collectionName, filePath) {
  const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  const batch = admin.firestore().batch();
  
  data.forEach(doc => {
    const ref = admin.firestore().collection(collectionName).doc(doc.id);
    batch.set(ref, doc);
  });
  
  await batch.commit();
  console.log(`Importé ${data.length} documents`);
}


# === MIGRER DE AUTRE DB VERS FIRESTORE ===

# Exemple: MySQL vers Firestore
const mysql = require('mysql2/promise');
const admin = require('firebase-admin');

async function migrateFromMySQL() {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    database: 'mydb'
  });
  
  const [rows] = await connection.execute('SELECT * FROM users');
  const batch = admin.firestore().batch();
  
  rows.forEach(row => {
    const ref = admin.firestore().collection('users').doc(String(row.id));
    batch.set(ref, {
      name: row.name,
      email: row.email,
      createdAt: admin.firestore.Timestamp.fromDate(row.created_at)
    });
  });
  
  await batch.commit();
  console.log('Migration terminée');
}


[OK] CONCLUSION


Firebase est une plateforme puissante pour développer rapidement applications
web et mobile sans gérer infrastructure backend.

# Points clés à retenir:

1. Authentication: Gestion complète utilisateurs
2. Firestore: Base de données NoSQL flexible et scalable
3. Storage: Stockage fichiers avec CDN intégré
4. Hosting: Hébergement web simple et rapide
5. Functions: Code backend serverless
6. Security Rules: Protection données indispensable
7. Emulators: Développement local sans affecter production

# Quand utiliser Firebase:
[OK] Prototypes et MVPs rapides
[OK] Applications temps réel
[OK] Applications sans backend complexe
[OK] Équipes petites/moyennes
[OK] Budget limité (plan gratuit généreux)

# Quand éviter Firebase:
[X] Requêtes complexes (joins, aggregations)
[X] Besoin contrôle total infrastructure
[X] Données très sensibles nécessitant isolation
[X] Applications nécessitant SQL relationnel

# Prochaines étapes:
1. Créer compte Firebase et premier projet
2. Suivre exemples de ce guide
3. Lire documentation officielle pour approfondir
4. Rejoindre communauté (Stack Overflow, Discord)
5. Construire projet personnel pour pratiquer

# Règle d'or Firebase:
"Commencer simple, scaler progressivement, toujours sécuriser"

Bon développement avec Firebase! [HOT]


# Fin du fichier firebase.txt
# Dernière mise à jour: 2024
# Pour questions: https://stackoverflow.com/questions/tagged/firebase
# Documentation: https://firebase.google.com/docsbase v9+ (modulaire) - Recommandé, bundle plus léger
# Firebase v8 (namespace) - Ancienne version


# === Installation via CDN (Pour tests rapides) ===

<!-- Firebase v9+ (modulaire) -->
<script type="module">
  import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js'
  import { getAuth } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js'
  import { getFirestore } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js'
</script>

<!-- Firebase v8 (namespace) - Ancienne méthode -->
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>


[OK] INITIALISATION FIREBASE - JAVASCRIPT


# === Firebase v9+ (Modulaire - Recommandé) ===

// firebase-config.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
import { getAnalytics } from 'firebase/analytics';

// Configuration (copier depuis console Firebase)
const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: "mon-projet.firebaseapp.com",
  projectId: "mon-projet",
  storageBucket: "mon-projet.appspot.com",
  messagingSenderId: "123456789012",
  appId: "1:123456789012:web:abcdef123456",
  measurementId: "G-XXXXXXXXXX"
};

// Initialiser Firebase
const app = initializeApp(firebaseConfig);

// Initialiser services
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
export const analytics = getAnalytics(app);


# === Firebase v8 (Namespace - Ancienne version) ===

// firebase-config.js
const firebaseConfig = {
  apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  authDomain: "mon-projet.firebaseapp.com",
  projectId: "mon-projet",
  storageBucket: "mon-projet.appspot.com",
  messagingSenderId: "123456789012",
  appId: "1:123456789012:web:abcdef123456"
};

// Initialiser Firebase
firebase.initializeApp(firebaseConfig);

// Accéder aux services
const auth = firebase.auth();
const db = firebase.firestore();
const storage = firebase.storage();


# === Utilisation dans React ===

// App.js
import { auth, db } from './firebase-config';
import { useEffect, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';

function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
    });
    return () => unsubscribe();
  }, []);

  return (
    <div>
      {user ? <p>Connecté: {user.email}</p> : <p>Non connecté</p>}
    </div>
  );
}


# === Variables d'environnement ===

# .env (React)
REACT_APP_FIREBASE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
REACT_APP_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com
REACT_APP_FIREBASE_PROJECT_ID=mon-projet
REACT_APP_FIREBASE_STORAGE_BUCKET=mon-projet.appspot.com
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=123456789012
REACT_APP_FIREBASE_APP_ID=1:123456789012:web:abcdef123456

# .env (Vue/Vite)
VITE_FIREBASE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com

# .env (Next.js)
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=mon-projet.firebaseapp.com

# IMPORTANT: Ajouter .env au .gitignore
echo ".env" >> .gitignore


[OK] FIREBASE AUTHENTICATION


Firebase Authentication gère l'authentification des utilisateurs avec plusieurs méthodes:
- Email/Password
- Google, Facebook, Twitter, GitHub
- Téléphone (SMS)
- Anonyme
- Personnalisé (Custom tokens)


# === CONFIGURATION DANS CONSOLE FIREBASE ===

# 1. Aller dans "Authentication" > "Sign-in method"
# 2. Activer les méthodes souhaitées:
#    - Email/Password: Cliquer "Enable"
#    - Google: Activer et configurer
#    - Autres providers: Suivre instructions


# === INSCRIPTION AVEC EMAIL/PASSWORD ===

// Inscription (v9+)
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from './firebase-config';

async function signUp(email, password) {
  try {
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    const user = userCredential.user;
    console.log('Utilisateur créé:', user.uid);
    return user;
  } catch (error) {
    console.error('Erreur inscription:', error.code, error.message);
    // Erreurs communes:
    // auth/email-already-in-use - Email déjà utilisé
    // auth/invalid-email - Email invalide
    // auth/weak-password - Mot de passe trop faible (< 6 caractères)
  }
}

// Utilisation
signUp('user@example.com', 'motdepasse123');


// Inscription (v8)
firebase.auth().createUserWithEmailAndPassword(email, password)
  .then((userCredential) => {
    const user = userCredential.user;
    console.log('Utilisateur créé:', user.uid);
  })
  .catch((error) => {
    console.error(error.code, error.message);
  });


# === CONNEXION AVEC EMAIL/PASSWORD ===

// Connexion (v9+)
import { signInWithEmailAndPassword } from 'firebase/auth';

async function signIn(email, password) {
  try {
    const userCredential = await signInWithEmailAndPassword(auth, email, password);
    const user = userCredential.user;
    console.log('Connecté:', user.uid);
    return user;
  } catch (error) {
    console.error('Erreur connexion:', error.code, error.message);
    // Erreurs communes:
    // auth/user-not-found - Utilisateur inexistant
    // auth/wrong-password - Mauvais mot de passe
    // auth/invalid-email - Email invalide
    // auth/user-disabled - Compte désactivé
  }
}

// Utilisation
signIn('user@example.com', 'motdepasse123');


// Connexion (v8)
firebase.auth().signInWithEmailAndPassword(email, password)
  .then((userCredential) => {
    console.log('Connecté:', userCredential.user.uid);
  })
  .catch((error) => {
    console.error(error.code, error.message);
  });


# === DÉCONNEXION ===

// Déconnexion (v9+)
import { signOut } from 'firebase/auth';

async function logout() {
  try {
    await signOut(auth);
    console.log('Déconnecté');
  } catch (error) {
    console.error('Erreur déconnexion:', error);
  }
}

// Utilisation
logout();


// Déconnexion (v8)
firebase.auth().signOut()
  .then(() => console.log('Déconnecté'))
  .catch((error) => console.error(error));


# === OBSERVER L'ÉTAT D'AUTHENTIFICATION ===

// Observer l'utilisateur connecté (v9+)
import { onAuthStateChanged } from 'firebase/auth';

// S'exécute à chaque changement d'état (connexion/déconnexion)
onAuthStateChanged(auth, (user) => {
  if (user) {
    // Utilisateur connecté
    console.log('Utilisateur:', user.uid);
    console.log('Email:', user.email);
    console.log('Vérifié:', user.emailVerified);
  } else {
    // Utilisateur déconnecté
    console.log('Aucun utilisateur connecté');
  }
});


// Avec React Hook
import { useEffect, useState } from 'react';

function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });
    return () => unsubscribe(); // Cleanup
  }, []);

  return { user, loading };
}

// Utilisation
function App() {
  const { user, loading } = useAuth();
  
  if (loading) return <div>Chargement...</div>;
  return user ? <Dashboard /> : <Login />;
}


// Observer (v8)
firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    console.log('Connecté:', user.uid);
  } else {
    console.log('Déconnecté');
  }
});


# === CONNEXION AVEC GOOGLE (EXPLICATION COMPLÈTE) ===

CONNEXION GOOGLE = OAuth (Open Authorization)

QU'EST-CE QU'OAUTH?
-> Connexion via compte existant (Google, Facebook, etc.)
-> Pas besoin créer nouveau compte
-> Pas besoin retenir nouveau mot de passe
-> Plus rapide et plus sûr

ANALOGIE:
Au lieu de créer nouvelle carte de membre au gym:
-> Vous montrez carte d'identité (compte Google)
-> Gym vérifie avec gouvernement (Google)
-> Gouvernement dit "C'est bien lui!"
-> Gym vous laisse entrer

AVANTAGES POUR UTILISATEUR:
[OK] Un clic = connecté (très rapide!)
[OK] Pas de mot de passe à retenir
[OK] Sécurisé (Google gère sécurité)
[OK] Pas de formulaire long

AVANTAGES POUR VOUS:
[OK] Plus d'inscriptions (moins de friction)
[OK] Moins d'emails "mot de passe oublié"
[OK] Informations vérifiées (nom, email, photo)
[OK] Code simple

# === CONFIGURATION DANS CONSOLE FIREBASE ===

AVANT DE CODER: Activer dans Firebase!

1. Console Firebase > Authentication > Sign-in method
2. Cliquer sur "Google"
3. Toggle "Enable"
4. "Project support email": Choisir votre email
   (Affiché aux utilisateurs dans popup Google)
5. "Save"

FAIT! [OK]

// Configuration (v9+)
import { GoogleAuthProvider, signInWithPopup, signInWithRedirect } from 'firebase/auth';

EXPLICATION IMPORTS:

GoogleAuthProvider
-> Classe pour créer provider Google
-> Provider = Fournisseur d'authentification
-> Comme une "clé" pour utiliser Google

signInWithPopup
-> Méthode popup (fenêtre s'ouvre)
-> Recommandé pour desktop
-> Plus moderne et fluide

signInWithRedirect
-> Méthode redirect (page change)
-> Recommandé pour mobile
-> Plus compatible

# Méthode 1: Popup (recommandé pour desktop)
async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();
  
  // Ajouter scopes optionnels
  provider.addScope('profile');
  provider.addScope('email');
  
  try {
    const result = await signInWithPopup(auth, provider);
    const user = result.user;
    // Obtenir le token d'accès Google
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential.accessToken;
    console.log('Connecté avec Google:', user.email);
    return user;
  } catch (error) {
    console.error('Erreur Google:', error.code, error.message);
    // Erreurs communes:
    // auth/popup-closed-by-user - Popup fermé par l'utilisateur
    // auth/popup-blocked - Popup bloqué par le navigateur
  }
}

EXPLICATION COMPLÈTE:

const provider = new GoogleAuthProvider();
-> Crée nouvel objet GoogleAuthProvider
-> Comme créer "badge visiteur" Google
-> new = Créer nouvelle instance

provider.addScope('profile');
provider.addScope('email');
-> Scopes = Permissions demandées
-> 'profile' = Nom, photo
-> 'email' = Adresse email
-> Par défaut inclus, mais explicite c'est mieux

AUTRES SCOPES POSSIBLES:
'https://www.googleapis.com/auth/drive.file' -> Accès Google Drive
'https://www.googleapis.com/auth/calendar' -> Accès Google Calendar
'https://www.googleapis.com/auth/youtube' -> Accès YouTube

POUR DÉBUTER: 'profile' et 'email' suffisent!

const result = await signInWithPopup(auth, provider);
-> Ouvre popup Google
-> auth = Service Firebase Auth
-> provider = Provider Google qu'on a créé
-> await = Attend que utilisateur termine dans popup

QU'EST-CE QUI SE PASSE:

1. Popup Google s'ouvre (400x600px environ)
2. URL: accounts.google.com/signin/oauth
3. Utilisateur voit:
   - Logo votre app
   - "Votre App veut accéder à votre compte Google"
   - Liste permissions (email, profil)
   - Liste comptes Google disponibles
4. Utilisateur choisit compte
5. Clique "Autoriser"
6. Popup se ferme automatiquement
7. Firebase reçoit réponse de Google
8. Créée/connecte utilisateur
9. Retourne résultat

DURÉE: 2-5 secondes (si utilisateur rapide)

const user = result.user;
-> Extrait objet user du résultat
-> Contient infos utilisateur

CONTENU user AVEC GOOGLE:
user.uid -> ID unique Firebase
user.email -> Email Google
user.displayName -> Nom complet ("John Doe")
user.photoURL -> URL photo profil Google (grande qualité!)
user.emailVerified -> true (Google vérifie emails)

const credential = GoogleAuthProvider.credentialFromResult(result);
-> Obtient credentials Google
-> Contient infos supplémentaires

const token = credential.accessToken;
-> Token d'accès Google
-> Permet appeler APIs Google (Drive, Calendar, etc.)
-> Optionnel si vous utilisez seulement Firebase

ERREURS POSSIBLES:

auth/popup-closed-by-user
-> Utilisateur ferme popup avant de terminer
-> Normal! Utilisateur a changé d'avis
-> Ne rien faire ou afficher "Connexion annulée"

auth/popup-blocked
-> Navigateur bloque popup (bloqueur pub)
-> Afficher message: "Autorisez popups pour ce site"
-> OU utiliser signInWithRedirect

auth/account-exists-with-different-credential
-> Email déjà utilisé avec autre méthode
-> Ex: Inscrit avec email/password, tente Google
-> Gérer fusion de comptes (avancé)

auth/cancelled-popup-request
-> Nouvelle popup ouverte avant fermeture précédente
-> Éviter: Désactiver bouton pendant traitement

EXEMPLE BOUTON CONNEXION GOOGLE:

function GoogleSignInButton() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const handleGoogleSignIn = async () => {
    setLoading(true);
    setError('');

    try {
      await signInWithGoogle();
      // Succès! Rediriger
      window.location.href = '/dashboard';
    } catch (err) {
      if (err.code === 'auth/popup-closed-by-user') {
        setError('Connexion annulée');
      } else if (err.code === 'auth/popup-blocked') {
        setError('Veuillez autoriser les popups');
      } else {
        setError('Erreur de connexion');
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button 
        onClick={handleGoogleSignIn}
        disabled={loading}
        style={{
          background: '#4285f4',
          color: 'white',
          border: 'none',
          padding: '10px 20px',
          borderRadius: '4px',
          cursor: 'pointer',
          display: 'flex',
          alignItems: 'center',
          gap: '10px'
        }}
      >
        <img 
          src="https://www.google.com/favicon.ico" 
          alt="Google"
          width="20"
        />
        {loading ? 'Connexion...' : 'Se connecter avec Google'}
      </button>
      {error && <p style={{color: 'red'}}>{error}</p>}
    </div>
  );
}

DESIGN BOUTON:
-> Couleur Google officielle: #4285f4
-> Logo Google inclus
-> Texte clair
-> États visuels (loading, hover)
-> Professionnel!

DESIGN GUIDELINES GOOGLE:
-> Google fournit guidelines officielles
-> Bouton doit respecter charte graphique
-> Voir: developers.google.com/identity/branding-guidelines


# Méthode 2: Redirect (recommandé pour mobile)
async function signInWithGoogleRedirect() {
  const provider = new GoogleAuthProvider();
  try {
    await signInWithRedirect(auth, provider);
    // L'utilisateur sera redirigé vers Google
  } catch (error) {
    console.error(error);
  }
}

EXPLICATION:

signInWithRedirect(auth, provider)
-> Redirige page ENTIÈRE vers Google
-> Pas de popup

QU'EST-CE QUI SE PASSE:

1. Page actuelle disparaît
2. Utilisateur va sur accounts.google.com
3. Se connecte/autorise
4. Revient sur votre site
5. URL: votre-site.com?state=...&code=...
6. Firebase intercepte paramètres
7. Connecte utilisateur
8. Votre app se recharge avec user connecté

POURQUOI SUR MOBILE?
-> Popups problématiques sur mobile
-> Peuvent être bloquées
-> Redirect plus fiable
-> Expérience native

// Récupérer le résultat après redirection
import { getRedirectResult } from 'firebase/auth';

getRedirectResult(auth)
  .then((result) => {
    if (result) {
      const user = result.user;
      console.log('Connecté:', user.email);
    }
  })
  .catch((error) => {
    console.error(error);
  });

EXPLICATION:

getRedirectResult(auth)
-> Vérifie si retour de redirect
-> Appeler au chargement de page
-> Si pas de redirect en cours: retourne null
-> Si redirect terminé: retourne result

IMPORTANT: Appeler dans useEffect()!

useEffect(() => {
  getRedirectResult(auth)
    .then((result) => {
      if (result) {
        // Connexion réussie!
        console.log('User:', result.user);
      }
    })
    .catch((error) => {
      console.error('Erreur:', error);
    });
}, []);

PLACEMENT:
-> Dans App.js ou component principal
-> S'exécute au chargement
-> Une seule fois ([] dependencies)

DÉTECTION AUTOMATIQUE:
-> Firebase détecte redirect automatiquement
-> onAuthStateChanged() se déclenche
-> Pas besoin gérer manuellement souvent


// Connexion Google (v8)
const provider = new firebase.auth.GoogleAuthProvider();

firebase.auth().signInWithPopup(provider)
  .then((result) => {
    const user = result.user;
    console.log('Connecté:', user.email);
  })
  .catch((error) => {
    console.error(error);
  });


# === CONNEXION AVEC FACEBOOK ===

FACEBOOK LOGIN = OAuth via compte Facebook

PRÉREQUIS:
-> Créer app Facebook Developers
-> Obtenir App ID et App Secret
-> Plus complexe que Google

ÉTAPES CONFIGURATION:

1. CRÉER APP FACEBOOK:
   - Aller sur developers.facebook.com
   - "Create App"
   - Type: "Consumer"
   - Nom app: "Mon App"
   - Email contact

2. AJOUTER FACEBOOK LOGIN:
   - Dashboard app -> "Add Product"
   - Choisir "Facebook Login"
   - "Web" platform

3. CONFIGURER:
   - Valid OAuth Redirect URIs:
     https://mon-projet.firebaseapp.com/__/auth/handler
   (Copier depuis Firebase Console)

4. OBTENIR CREDENTIALS:
   - Settings -> Basic
   - Copier "App ID"
   - Copier "App Secret" (cliquer "Show")

5. CONFIGURER FIREBASE:
   - Console Firebase > Authentication > Sign-in method
   - Cliquer "Facebook"
   - Enable
   - Coller App ID et App Secret
   - Copier OAuth redirect URI
   - Save

MAINTENANT CODE:

// Configuration (v9+)
import { FacebookAuthProvider, signInWithPopup } from 'firebase/auth';

async function signInWithFacebook() {
  const provider = new FacebookAuthProvider();
  
  try {
    const result = await signInWithPopup(auth, provider);
    const user = result.user;
    const credential = FacebookAuthProvider.credentialFromResult(result);
    const token = credential.accessToken;
    console.log('Connecté avec Facebook:', user.email);
    return user;
  } catch (error) {
    console.error('Erreur Facebook:', error.code, error.message);
  }
}

SIMILAIRE À GOOGLE!
-> Remplacer GoogleAuthProvider par FacebookAuthProvider
-> Même logique
-> Même erreurs possibles

ATTENTION EMAIL:
-> Facebook peut ne PAS fournir email
-> user.email peut être null!
-> Demander email après si null

if (!user.email) {
  // Demander email à utilisateur
  const email = prompt('Veuillez entrer votre email');
  // Sauvegarder dans Firestore
}


# === CONNEXION AVEC GITHUB ===

GITHUB LOGIN = OAuth via compte GitHub

CONFIGURATION PLUS SIMPLE:

1. CRÉER OAUTH APP GITHUB:
   - github.com/settings/developers
   - "New OAuth App"
   - Application name: "Mon App"
   - Homepage URL: https://mon-site.com
   - Authorization callback URL:
     https://mon-projet.firebaseapp.com/__/auth/handler
   - "Register application"

2. OBTENIR CREDENTIALS:
   - Copier "Client ID"
   - "Generate new client secret"
   - Copier "Client Secret"

3. CONFIGURER FIREBASE:
   - Console Firebase > Authentication
   - Cliquer "GitHub"
   - Enable
   - Coller Client ID et Secret
   - Save

CODE:

// Configuration (v9+)
import { GithubAuthProvider, signInWithPopup } from 'firebase/auth';

async function signInWithGithub() {
  const provider = new GithubAuthProvider();
  
  try {
    const result = await signInWithPopup(auth, provider);
    const user = result.user;
    console.log('Connecté avec GitHub:', user.email);
    return user;
  } catch (error) {
    console.error('Erreur GitHub:', error.code, error.message);
    // Erreur spéciale:
    // auth/account-exists-with-different-credential
    // L'email est déjà utilisé avec un autre provider
  }
}

ERREUR COMMUNE:

auth/account-exists-with-different-credential
-> Email déjà utilisé avec autre provider
-> Ex: Inscrit avec Google, tente GitHub
-> Même email = conflit

SOLUTION:
1. Détecter erreur
2. Demander quelle méthode utiliser
3. Proposer lier comptes (avancé)

if (error.code === 'auth/account-exists-with-different-credential') {
  alert('Cet email est déjà utilisé. Connectez-vous avec Google.');
}


# === CONNEXION ANONYME ===

CONNEXION ANONYME = Connexion sans identité

POURQUOI?
-> Laisser utiliser app AVANT inscription
-> Convertir en compte permanent après
-> Bon pour démos, essais gratuits

ANALOGIE:
Essai gratuit 7 jours
-> Utilisez produit sans compte
-> Créez compte pour garder données
-> Sinon données perdues après 7 jours

EXEMPLES UTILISATION:
- Jeu: Jouer sans compte, sauvegarder après
- Todo app: Créer tâches, synchroniser après
- Calculatrice: Historique temporaire

// Connexion anonyme (v9+)
import { signInAnonymously } from 'firebase/auth';

async function signInAnonymous() {
  try {
    const userCredential = await signInAnonymously(auth);
    const user = userCredential.user;
    console.log('Connecté anonymement:', user.uid);
    console.log('Est anonyme:', user.isAnonymous); // true
    return user;
  } catch (error) {
    console.error('Erreur connexion anonyme:', error);
  }
}

EXPLICATION:

signInAnonymously(auth)
-> Pas de paramètres!
-> Firebase génère UID unique
-> Pas d'email, pas de password

user.isAnonymous
-> true pour compte anonyme
-> false pour compte normal

UTILISATEUR ANONYME A:
[OK] UID unique (comme tous utilisateurs)
[OK] Peut lire/écrire Firestore (selon rules)
[OK] Peut utiliser app normalement
[X] Pas d'email
[X] Pas de nom
[X] Données perdues si supprime app/vide cache

// Convertir compte anonyme en compte permanent
import { linkWithCredential, EmailAuthProvider } from 'firebase/auth';

async function linkAnonymousAccount(email, password) {
  const credential = EmailAuthProvider.credential(email, password);
  
  try {
    const userCred = await linkWithCredential(auth.currentUser, credential);
    console.log('Compte anonyme converti:', userCred.user);
  } catch (error) {
    console.error('Erreur conversion:', error);
  }
}

EXPLICATION:

EmailAuthProvider.credential(email, password)
-> Crée objet credential avec email/password
-> Comme créer "carte de membre permanente"

linkWithCredential(auth.currentUser, credential)
-> Lie credential au compte anonyme actuel
-> auth.currentUser = Utilisateur anonyme
-> Devient utilisateur permanent!

QU'EST-CE QUI SE PASSE:
1. Utilisateur anonyme existe (UID: abc123)
2. Lie email/password à ce UID
3. Maintenant: UID abc123 a email + password
4. user.isAnonymous -> false
5. Données conservées!
6. Peut se reconnecter avec email/password

EXEMPLE COMPLET:

function ConvertAnonymousPrompt() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleConvert = async () => {
    try {
      await linkAnonymousAccount(email, password);
      alert('Compte créé! Vos données sont sauvegardées.');
    } catch (error) {
      if (error.code === 'auth/email-already-in-use') {
        alert('Cet email existe déjà');
      } else {
        alert('Erreur: ' + error.message);
      }
    }
  };

  return (
    <div className="convert-prompt">
      <h3>Sauvegarder vos données</h3>
      <p>Créez un compte pour ne pas perdre votre progression!</p>
      
      <input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      
      <input
        type="password"
        placeholder="Mot de passe"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      
      <button onClick={handleConvert}>
        Créer mon compte
      </button>
    </div>
  );
}

QUAND AFFICHER?
-> Après X minutes d'utilisation
-> Quand utilisateur accomplit tâche importante
-> Avant fonctionnalité premium
-> Popup subtile, pas agressive

CONVERSION AVEC GOOGLE:

import { linkWithPopup, GoogleAuthProvider } from 'firebase/auth';

async function linkWithGoogle() {
  const provider = new GoogleAuthProvider();
  
  try {
    const result = await linkWithPopup(auth.currentUser, provider);
    console.log('Compte anonyme lié à Google:', result.user);
  } catch (error) {
    console.error('Erreur:', error);
  }
}

PLUS SIMPLE!
-> Un clic Google
-> Compte converti
-> UX excellente


# === RÉINITIALISATION MOT DE PASSE ===

MOT DE PASSE OUBLIÉ = Permettre reset password

PROCESSUS:
1. Utilisateur entre email
2. Firebase envoie email avec lien
3. Utilisateur clique lien
4. Entre nouveau mot de passe
5. Mot de passe changé!

// Envoyer email de réinitialisation (v9+)
import { sendPasswordResetEmail } from 'firebase/auth';

async function resetPassword(email) {
  try {
    await sendPasswordResetEmail(auth, email);
    console.log('Email de réinitialisation envoyé');
  } catch (error) {
    console.error('Erreur:', error.code, error.message);
    // Erreurs:
    // auth/user-not-found - Email inexistant
    // auth/invalid-email - Email invalide
  }
}

// Utilisation
resetPassword('user@example.com');

EXPLICATION:

sendPasswordResetEmail(auth, email)
-> Envoie email automatiquement
-> Firebase gère tout!
-> Template email configurable

QU'EST-CE QUI SE PASSE:

1. Firebase vérifie si email existe
2. Génère lien unique temporaire
3. Envoie email avec lien
4. Lien valide 1 heure
5. Utilisateur clique lien
6. Redirigé vers page Firebase
7. Entre nouveau mot de passe
8. Mot de passe changé!

EMAIL CONTIENT:
- Lien: https://mon-projet.firebaseapp.com/__/auth/action?...
- Bouton "Reset password"
- Expire dans 1 heure
- IP et localisation (sécurité)

PERSONNALISER EMAIL:

Console Firebase > Authentication > Templates > Password reset

Vous pouvez:
- Changer texte
- Changer design (limité)
- Ajouter logo
- Changer langue

EXEMPLE FORMULAIRE:

function ForgotPasswordForm() {
  const [email, setEmail] = useState('');
  const [sent, setSent] = useState(false);
  const [error, setError] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');

    try {
      await sendPasswordResetEmail(auth, email);
      setSent(true);
    } catch (err) {
      if (err.code === 'auth/user-not-found') {
        setError('Aucun compte avec cet email');
      } else {
        setError('Erreur lors de l\'envoi');
      }
    }
  };

  if (sent) {
    return (
      <div>
        <h2>Email envoyé !</h2>
        <p>Vérifiez votre boîte mail ({email})</p>
        <p>Le lien expire dans 1 heure.</p>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Mot de passe oublié</h2>
      
      {error && <p style={{color: 'red'}}>{error}</p>}
      
      <input
        type="email"
        placeholder="Votre email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        required
      />
      
      <button type="submit">
        Envoyer lien de réinitialisation
      </button>
    </form>
  );
}

ÉTATS:
1. Formulaire (enter email)
2. Envoi...
3. Confirmé! (check emails)

UX IMPORTANTE:
-> Toujours dire "Email envoyé" même si inexistant
-> Empêche énumération emails
-> Sécurité!


// Réinitialisation (v8)
firebase.auth().sendPasswordResetEmail(email)
  .then(() => console.log('Email envoyé'))
  .catch((error) => console.error(error));


# === VÉRIFICATION EMAIL ===

VÉRIFICATION EMAIL = Confirmer que email est réel

POURQUOI?
-> Éviter faux comptes
-> Sécurité (reset password, notifications)
-> Qualité base utilisateurs

// Envoyer email de vérification (v9+)
import { sendEmailVerification } from 'firebase/auth';

async function verifyEmail() {
  try {
    await sendEmailVerification(auth.currentUser);
    console.log('Email de vérification envoyé');
  } catch (error) {
    console.error('Erreur:', error);
  }
}

QUAND APPELER?
-> Juste après inscription
-> Ou quand utilisateur essaie fonctionnalité nécessitant vérification

// Vérifier si email est vérifié
const user = auth.currentUser;
if (user) {
  console.log('Email vérifié:', user.emailVerified);
}

user.emailVerified
-> true = Vérifié
-> false = Pas encore vérifié

FORCER VÉRIFICATION:

function ProtectedFeature() {
  const { user } = useAuth();

  if (!user.emailVerified) {
    return (
      <div>
        <h2>Vérifiez votre email</h2>
        <p>Un email vous a été envoyé</p>
        <button onClick={() => sendEmailVerification(user)}>
          Renvoyer email
        </button>
      </div>
    );
  }

  return <div>Fonctionnalité protégée!</div>;
}

RAFRAÎCHIR STATUT:

import { reload } from 'firebase/auth';

async function checkEmailVerified() {
  await reload(auth.currentUser);
  return auth.currentUser.emailVerified;
}

reload(auth.currentUser)
-> Recharge infos utilisateur depuis serveur
-> Met à jour emailVerified
-> Nécessaire car info mise en cache

EXEMPLE POLLING:

useEffect(() => {
  if (user && !user.emailVerified) {
    // Vérifier toutes les 5 secondes
    const interval = setInterval(async () => {
      await reload(user);
      if (user.emailVerified) {
        clearInterval(interval);
        alert('Email vérifié!');
      }
    }, 5000);

    return () => clearInterval(interval);
  }
}, [user]);

POLLING = Vérifier régulièrement
-> Détecte quand utilisateur clique lien
-> Met à jour UI automatiquement
-> Évite recharger page manuellement


# === MISE À JOUR PROFIL ===

PROFIL = Informations publiques utilisateur

// Mettre à jour profil utilisateur (v9+)
import { updateProfile } from 'firebase/auth';

async function updateUserProfile(displayName, photoURL) {
  try {
    await updateProfile(auth.currentUser, {
      displayName: displayName,
      photoURL: photoURL
    });
    console.log('Profil mis à jour');
  } catch (error) {
    console.error('Erreur mise à jour:', error);
  }
}

// Utilisation
updateUserProfile('John Doe', 'https://example.com/photo.jpg');

EXPLICATION:

updateProfile(auth.currentUser, { ... })
-> Met à jour profil utilisateur actuel
-> Objet avec champs à modifier

CHAMPS MODIFIABLES:
- displayName: Nom affiché
- photoURL: URL photo profil

CHAMPS NON MODIFIABLES (updateProfile):
- email: Utiliser updateEmail()
- password: Utiliser updatePassword()
- uid: JAMAIS modifiable

EXEMPLE FORMULAIRE PROFIL:

function ProfileForm() {
  const { user } = useAuth();
  const [name, setName] = useState(user?.displayName || '');
  const [photo, setPhoto] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      let photoURL = user.photoURL;

      // Upload photo si nouvelle
      if (photo) {
        const storageRef = ref(storage, `avatars/${user.uid}`);
        await uploadBytes(storageRef, photo);
        photoURL = await getDownloadURL(storageRef);
      }

      // Mettre à jour profil
      await updateProfile(user, {
        displayName: name,
        photoURL: photoURL
      });

      alert('Profil mis à jour!');
    } catch (error) {
      alert('Erreur: ' + error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Mon Profil</h2>
      
      <div>
        {user.photoURL && (
          <img src={user.photoURL} alt="Avatar" width="100" />
        )}
      </div>
      
      <input
        type="text"
        placeholder="Nom"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      
      <input
        type="file"
        accept="image/*"
        onChange={(e) => setPhoto(e.target.files[0])}
      />
      
      <button type="submit" disabled={loading}>
        {loading ? 'Mise à jour...' : 'Sauvegarder'}
      </button>
    </form>
  );
}

COMBINE:
-> Firebase Auth (displayName, photoURL)
-> Firebase Storage (upload photo)
-> Firestore (données supplémentaires comme bio)

DONNÉES AUTH VS FIRESTORE:

Auth (limité):
- displayName
- photoURL
- email
- emailVerified

Firestore (illimité):
- Bio
- Date naissance
- Localisation
- Préférences
- Tout ce que vous voulez!

RECOMMANDATION:
-> displayName et photoURL dans Auth (rapide)
-> Autres infos dans Firestore document

// Changer email (v9+)
import { updateEmail } from 'firebase/auth';

async function changeEmail(newEmail) {
  try {
    await updateEmail(auth.currentUser, newEmail);
    console.log('Email changé');
  } catch (error) {
    console.error('Erreur:', error);
    // Peut nécessiter réauthentification récente
  }
}

ATTENTION:
-> Opération sensible
-> Nécessite réauth récente (<5 min)
-> Si pas récent: erreur auth/requires-recent-login


// Changer mot de passe (v9+)
import { updatePassword } from 'firebase/auth';

// Changer mot de passe (v9+)
import { updatePassword } from 'firebase/auth';

async function changePassword(newPassword) {
  try {
    await updatePassword(auth.currentUser, newPassword);
    console.log('Mot de passe changé');
  } catch (error) {
    console.error('Erreur:', error);
    // Peut nécessiter réauthentification récente
  }
}


# === RÉAUTHENTIFICATION ===

RÉAUTHENTIFICATION = Se reconnecter pour opération sensible

POURQUOI?
-> Sécurité supplémentaire
-> Empêche pirate avec session volée
-> Comme demander mot de passe avant achat

QUAND NÉCESSAIRE?
-> Changer email
-> Changer mot de passe
-> Supprimer compte
-> Opérations financières

// Réauthentifier pour opérations sensibles (v9+)
import { reauthenticateWithCredential, EmailAuthProvider } from 'firebase/auth';

async function reauthenticate(password) {
  const user = auth.currentUser;
  const credential = EmailAuthProvider.credential(user.email, password);
  
  try {
    await reauthenticateWithCredential(user, credential);
    console.log('Réauthentifié');
  } catch (error) {
    console.error('Erreur réauthentification:', error);
  }
}

EXPLICATION:

EmailAuthProvider.credential(email, password)
-> Crée objet credential
-> Contient email + password

reauthenticateWithCredential(user, credential)
-> Vérifie credential contre Firebase
-> Si correct: session "rafraîchie"
-> Peut maintenant faire opérations sensibles

QU'EST-CE QUI SE PASSE:
1. Utilisateur entre mot de passe
2. Firebase vérifie avec serveur
3. Si correct: timestamp session mis à jour
4. Opérations sensibles autorisées 5 minutes
5. Après 5 min: redemander réauth

RÉAUTH AVEC GOOGLE:

import { reauthenticateWithPopup, GoogleAuthProvider } from 'firebase/auth';

async function reauthWithGoogle() {
  const provider = new GoogleAuthProvider();
  
  try {
    await reauthenticateWithPopup(auth.currentUser, provider);
    console.log('Réauthentifié avec Google');
  } catch (error) {
    console.error('Erreur:', error);
  }
}

PLUS SIMPLE!
-> Popup Google
-> Utilisateur confirme
-> Réauthentifié

EXEMPLE COMPLET SUPPRESSION COMPTE:

function DeleteAccountButton() {
  const [confirmPassword, setConfirmPassword] = useState('');
  const [showConfirm, setShowConfirm] = useState(false);
  const { user } = useAuth();

  const handleDelete = async () => {
    try {
      // 1. Réauthentifier
      const credential = EmailAuthProvider.credential(
        user.email,
        confirmPassword
      );
      await reauthenticateWithCredential(user, credential);

      // 2. Supprimer compte
      await deleteUser(user);

      alert('Compte supprimé');
      window.location.href = '/';
    } catch (error) {
      if (error.code === 'auth/wrong-password') {
        alert('Mot de passe incorrect');
      } else {
        alert('Erreur: ' + error.message);
      }
    }
  };

  if (!showConfirm) {
    return (
      <button 
        onClick={() => setShowConfirm(true)}
        style={{background: 'red', color: 'white'}}
      >
        Supprimer mon compte
      </button>
    );
  }

  return (
    <div style={{border: '2px solid red', padding: '20px'}}>
      <h3>[ATTENTION] Attention</h3>
      <p>Cette action est IRRÉVERSIBLE!</p>
      <p>Toutes vos données seront perdues.</p>
      
      <input
        type="password"
        placeholder="Confirmez votre mot de passe"
        value={confirmPassword}
        onChange={(e) => setConfirmPassword(e.target.value)}
      />
      
      <button onClick={handleDelete} style={{background: 'red'}}>
        Oui, supprimer définitivement
      </button>
      
      <button onClick={() => setShowConfirm(false)}>
        Annuler
      </button>
    </div>
  );
}

UX SÉCURITÉ:
-> Double confirmation (bouton + password)
-> Avertissement clair
-> Couleur rouge (danger)
-> Empêche suppression accidentelle


# === SUPPRIMER COMPTE ===

// Supprimer compte utilisateur (v9+)
import { deleteUser } from 'firebase/auth';

async function deleteAccount() {
  try {
    await deleteUser(auth.currentUser);
    console.log('Compte supprimé');
  } catch (error) {
    console.error('Erreur suppression:', error);
    // Nécessite réauthentification récente
  }
}

ATTENTION:
-> IRRÉVERSIBLE!
-> Supprime compte Firebase Auth
-> NE SUPPRIME PAS automatiquement:
  - Documents Firestore
  - Fichiers Storage
  - Autres données associées

CLEANUP NÉCESSAIRE:

async function deleteAccountCompletely() {
  const user = auth.currentUser;
  const userId = user.uid;

  try {
    // 1. Réauthentifier
    const password = prompt('Confirmez votre mot de passe');
    const credential = EmailAuthProvider.credential(user.email, password);
    await reauthenticateWithCredential(user, credential);

    // 2. Supprimer données Firestore
    await deleteDoc(doc(db, 'users', userId));

    // 3. Supprimer fichiers Storage
    const avatarRef = ref(storage, `avatars/${userId}`);
    await deleteObject(avatarRef).catch(() => {
      // Fichier peut ne pas exister
    });

    // 4. Supprimer compte Auth
    await deleteUser(user);

    alert('Compte et données supprimés');
    window.location.href = '/';
  } catch (error) {
    alert('Erreur: ' + error.message);
  }
}

MEILLEURE APPROCHE: CLOUD FUNCTION

Cloud Function pour cleanup automatique:

exports.onUserDelete = functions.auth.user().onDelete((user) => {
  const uid = user.uid;

  // Supprimer Firestore
  admin.firestore().collection('users').doc(uid).delete();

  // Supprimer Storage
  const bucket = admin.storage().bucket();
  bucket.deleteFiles({ prefix: `users/${uid}/` });

  console.log('Cleanup terminé pour:', uid);
});

AUTOMATIQUE!
-> Utilisateur supprime compte
-> Function se déclenche
-> Nettoie tout
-> Vous ne gérez rien!


# === OBTENIR TOKEN ID ===

TOKEN ID = Jeton JWT pour authentifier requêtes

QUAND UTILISER?
-> Appeler votre API backend
-> Prouver identité utilisateur
-> Accès sécurisé ressources

// Obtenir token JWT pour l'utilisateur (v9+)
import { getIdToken } from 'firebase/auth';

async function getUserToken() {
  try {
    const token = await getIdToken(auth.currentUser);
    console.log('Token:', token);
    // Utiliser ce token pour appels API backend
    return token;
  } catch (error) {
    console.error('Erreur token:', error);
  }
}

EXPLICATION:

getIdToken(auth.currentUser)
-> Obtient token JWT (JSON Web Token)
-> Valide 1 heure
-> Rafraîchi automatiquement

TOKEN CONTIENT:
- UID utilisateur
- Email
- Permissions
- Expiration
- Signature cryptographique

FORMAT TOKEN:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMzQ1... (très long!)

UTILISATION AVEC API:

async function callProtectedAPI() {
  const token = await getIdToken(auth.currentUser);

  const response = await fetch('https://mon-api.com/protected', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  const data = await response.json();
  return data;
}

BACKEND VÉRIFIE TOKEN:

// Node.js backend
const admin = require('firebase-admin');

async function verifyToken(req, res) {
  const token = req.headers.authorization?.split('Bearer ')[1];

  try {
    const decodedToken = await admin.auth().verifyIdToken(token);
    const uid = decodedToken.uid;
    
    // Token valide! Utilisateur authentifié
    res.json({ message: 'Accès autorisé', uid: uid });
  } catch (error) {
    res.status(401).json({ error: 'Non autorisé' });
  }
}

SÉCURITÉ:
-> Token signé par Google (impossible falsifier)
-> Expire après 1 heure
-> Vérifié côté serveur

FORCER REFRESH TOKEN:

const token = await getIdToken(auth.currentUser, true); // force refresh

QUAND FORCER?
-> Après changement permissions
-> Après mise à jour claims personnalisés
-> Sinon: automatique


# === GESTION MULTI-TENANTS ===

MULTI-TENANTS = Plusieurs organisations dans un projet

EXEMPLE:
SaaS B2B où chaque entreprise = tenant
- Tenant "entreprise-a"
- Tenant "entreprise-b"
- Utilisateurs isolés par tenant

// Définir tenant ID (pour Firebase multi-tenants)
import { getAuth } from 'firebase/auth';

const auth = getAuth(app);
auth.tenantId = 'tenant-id-123';

CONFIGURATION:
1. Activer multi-tenancy dans Console Firebase
2. Créer tenants via Admin SDK ou Console
3. Assigner tenantId dans app

UTILISATION:
-> Tous appels auth utilisent ce tenant
-> Utilisateurs isolés par tenant
-> Rules Firestore peuvent utiliser tenantId

AVANCÉ:
-> Pas nécessaire pour débutants
-> Seulement si vous construisez SaaS multi-entreprise


# === RÉCAPITULATIF AUTHENTICATION COMPLET ===

VOUS SAVEZ MAINTENANT:

[OK] BASES:
- Inscription email/password
- Connexion email/password
- Déconnexion
- Observer état auth
- Gérer erreurs

[OK] OAUTH:
- Connexion Google (popup/redirect)
- Connexion Facebook
- Connexion GitHub
- Gestion credentials

[OK] ANONYME:
- Connexion anonyme
- Conversion en compte permanent
- Cas d'usage

[OK] SÉCURITÉ:
- Réinitialisation mot de passe
- Vérification email
- Réauthentification
- Changement email/password

[OK] PROFIL:
- Mise à jour displayName
- Mise à jour photoURL
- Upload avatar
- Données Auth vs Firestore

[OK] AVANCÉ:
- Obtenir token ID
- Supprimer compte (avec cleanup)
- Multi-tenancy

AVEC CES CONNAISSANCES:
Vous pouvez construire système auth complet pour 99% des apps! [BRAVO]

PROCHAINE ÉTAPE:
Firestore Database - Stocker et gérer données! [ARCHIVE]


[OK] CLOUD FIRESTORE DATABASE


Firestore est une base de données NoSQL flexible et scalable.
Structure: Collections > Documents > Sous-collections

# Différences avec Realtime Database:
- Firestore: Meilleure scalabilité, requêtes plus puissantes, offline moderne
- Realtime Database: Plus simple, meilleures performances temps réel, moins cher


# === STRUCTURE FIRESTORE ===

# Exemple de structure:
users/                          # Collection
  user123/                      # Document (ID: user123)
    name: "John"                # Champs
    email: "john@example.com"
    age: 30
    posts/                      # Sous-collection
      post1/                    # Sous-document
        title: "Mon post"
        content: "..."

# Règles importantes:
- Les documents contiennent des champs (clé-valeur)
- Les documents peuvent contenir des sous-collections
- Pas de collections vides (doivent contenir au moins 1 document)
- ID de document: auto-généré ou personnalisé
- Max 1MB par document


# === CONFIGURATION DANS CONSOLE FIREBASE ===

# 1. Aller dans "Firestore Database"
# 2. Cliquer "Create database"
# 3. Choisir mode:
#    - Production mode: Règles strictes (recommandé)
#    - Test mode: Accès ouvert (30 jours)
# 4. Choisir région (proche de vos utilisateurs)


# === INITIALISATION FIRESTORE ===

// Initialisation (v9+)
import { getFirestore } from 'firebase/firestore';
import { app } from './firebase-config';

export const db = getFirestore(app);


// Initialisation (v8)
const db = firebase.firestore();


# === AJOUTER UN DOCUMENT ===

// Ajouter document avec ID auto-généré (v9+)
import { collection, addDoc } from 'firebase/firestore';

async function addUser(name, email, age) {
  try {
    const docRef = await addDoc(collection(db, 'users'), {
      name: name,
      email: email,
      age: age,
      createdAt: new Date()
    });
    console.log('Document créé avec ID:', docRef.id);
    return docRef.id;
  } catch (error) {
    console.error('Erreur ajout:', error);
  }
}

// Utilisation
addUser('John Doe', 'john@example.com', 30);


// Ajouter document avec ID personnalisé (v9+)
import { doc, setDoc } from 'firebase/firestore';

async function createUserWithId(userId, name, email) {
  try {
    await setDoc(doc(db, 'users', userId), {
      name: name,
      email: email,
      createdAt: new Date()
    });
    console.log('Document créé avec ID:', userId);
  } catch (error) {
    console.error('Erreur:', error);
  }
}

// Utilisation
createUserWithId('user123', 'John Doe', 'john@example.com');


// Ajouter (v8)
db.collection('users').add({
  name: 'John Doe',
  email: 'john@example.com',
  age: 30
})
.then((docRef) => {
  console.log('ID:', docRef.id);
})
.catch((error) => {
  console.error(error);
});


# === LIRE UN DOCUMENT ===

LIRE = Obtenir données d'un document spécifique

// Lire un document (v9+)
import { doc, getDoc } from 'firebase/firestore';

async function getUser(userId) {
  try {
    const docRef = doc(db, 'users', userId);
    const docSnap = await getDoc(docRef);
    
    if (docSnap.exists()) {
      console.log('Données:', docSnap.data());
      return docSnap.data();
    } else {
      console.log('Document inexistant');
      return null;
    }
  } catch (error) {
    console.error('Erreur lecture:', error);
  }
}

// Utilisation
const userData = await getUser('user123');
console.log(userData.name); // "John Doe"

EXPLICATION COMPLÈTE:

import { doc, getDoc } from 'firebase/firestore';
-> doc = Référencer document
-> getDoc = Lire document

const docRef = doc(db, 'users', userId);
-> Crée référence au document
-> Comme "pointeur" vers document
-> db = Firestore
-> 'users' = Collection
-> userId = ID document

docRef NE CONTIENT PAS LES DONNÉES!
-> Juste une référence (chemin)
-> Comme adresse postale (pas la lettre)

const docSnap = await getDoc(docRef);
-> LIT réellement le document
-> Envoie requête à Firebase
-> await = Attend réponse serveur
-> docSnap = Snapshot (photo instantanée des données)

QU'EST-CE QUI SE PASSE:

1. getDoc() contacte serveurs Firebase
2. Cherche document avec ID "user123" dans "users"
3. Si trouvé: Télécharge données
4. Si pas trouvé: Retourne snapshot vide
5. Retourne snapshot (10-100ms selon connexion)

docSnap CONTIENT:
- .exists() -> true/false (document existe?)
- .data() -> Objet avec données
- .id -> ID du document
- .ref -> Référence au document

if (docSnap.exists()) {
-> TOUJOURS vérifier existence!
-> Si vous lisez ID inexistant: exists() = false
-> Si vous appelez .data() sur doc inexistant: undefined

docSnap.data()
-> Retourne objet JavaScript avec données
-> Exemple:
{
  name: "John Doe",
  email: "john@example.com",
  age: 30,
  createdAt: Timestamp
}

return docSnap.data();
-> Retourne données à l'appelant

UTILISATION:

const userData = await getUser('user123');

if (userData) {
  console.log('Nom:', userData.name);
  console.log('Email:', userData.email);
  console.log('Âge:', userData.age);
} else {
  console.log('Utilisateur introuvable');
}

EXEMPLE AFFICHAGE PROFIL:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      try {
        const docRef = doc(db, 'users', userId);
        const docSnap = await getDoc(docRef);

        if (docSnap.exists()) {
          setUser(docSnap.data());
        } else {
          setError('Utilisateur introuvable');
        }
      } catch (err) {
        setError('Erreur chargement: ' + err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]);

  if (loading) return <div>Chargement...</div>;
  if (error) return <div>Erreur: {error}</div>;
  if (!user) return <div>Utilisateur introuvable</div>;

  return (
    <div className="profile">
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
      <p>Âge: {user.age}</p>
      {user.bio && <p>Bio: {user.bio}</p>}
    </div>
  );
}

PATTERN 3 ÉTATS:
1. loading = true -> Affiche loader
2. error existe -> Affiche erreur
3. user existe -> Affiche données

TOUJOURS ces 3 états pour bonne UX!

ACCÉDER CHAMPS SPÉCIFIQUES:

const userData = await getUser('user123');

userData.name           // "John Doe"
userData.email          // "john@example.com"
userData.age            // 30
userData.bio            // undefined (si n'existe pas)
userData?.bio           // undefined (safe avec ?)
userData.bio || 'N/A'   // "N/A" si undefined


// Lire (v8)
db.collection('users').doc('user123').get()
  .then((doc) => {
    if (doc.exists) {
      console.log('Données:', doc.data());
    } else {
      console.log('Document inexistant');
    }
  })
  .catch((error) => {
    console.error(error);
  });


# === LIRE TOUS LES DOCUMENTS D'UNE COLLECTION ===

LIRE TOUS = Obtenir tous documents d'une collection

// Lire tous les documents (v9+)
import { collection, getDocs } from 'firebase/firestore';

async function getAllUsers() {
  try {
    const querySnapshot = await getDocs(collection(db, 'users'));
    const users = [];
    
    querySnapshot.forEach((doc) => {
      users.push({
        id: doc.id,
        ...doc.data()
      });
    });
    
    console.log('Nombre d\'utilisateurs:', users.length);
    return users;
  } catch (error) {
    console.error('Erreur:', error);
  }
}

// Utilisation
const allUsers = await getAllUsers();
allUsers.forEach(user => {
  console.log(`${user.id}: ${user.name}`);
});

EXPLICATION:

import { collection, getDocs } from 'firebase/firestore';
-> collection = Référencer collection
-> getDocs = Lire plusieurs documents (plural!)

const querySnapshot = await getDocs(collection(db, 'users'));
-> getDocs() = Lit TOUS documents de collection
-> collection(db, 'users') = Référence collection "users"
-> querySnapshot = Collection de snapshots

QU'EST-CE QUI SE PASSE:

1. Firebase cherche tous documents dans "users"
2. Télécharge tous en une requête
3. Retourne querySnapshot (collection de docs)
4. Durée: 50-200ms selon nombre docs

querySnapshot CONTIENT:
- .size -> Nombre de documents
- .empty -> true si aucun document
- .docs -> Array de DocumentSnapshots
- .forEach() -> Itérer sur documents

const users = [];
-> Array pour stocker utilisateurs

querySnapshot.forEach((doc) => { ... });
-> Itère sur chaque document
-> doc = DocumentSnapshot (comme getDoc)

users.push({
  id: doc.id,
  ...doc.data()
});

DÉCOMPOSITION:

doc.id
-> ID du document
-> Exemple: "user123"

doc.data()
-> Données du document
-> { name: "John", email: "..." }

...doc.data()
-> Spread operator
-> "Déploie" l'objet

RÉSULTAT:
{
  id: "user123",
  name: "John Doe",
  email: "john@example.com",
  age: 30
}

ID DANS DONNÉES!
-> Pratique pour affichage
-> Facile manipuler
-> Un objet = tout!

EXEMPLE LISTE UTILISATEURS:

function UsersList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const querySnapshot = await getDocs(collection(db, 'users'));
        const usersData = [];
        
        querySnapshot.forEach((doc) => {
          usersData.push({
            id: doc.id,
            ...doc.data()
          });
        });
        
        setUsers(usersData);
      } catch (error) {
        console.error('Erreur:', error);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  if (loading) return <div>Chargement...</div>;

  return (
    <div>
      <h2>Utilisateurs ({users.length})</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <strong>{user.name}</strong> - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

AFFICHAGE:

Utilisateurs (3)
• John Doe - john@example.com
• Alice Smith - alice@example.com
• Bob Johnson - bob@example.com

key={user.id}
-> IMPORTANT pour React!
-> Optimise renders
-> Unique garanti

ALTERNATIVE AVEC .map():

const querySnapshot = await getDocs(collection(db, 'users'));
const users = querySnapshot.docs.map(doc => ({
  id: doc.id,
  ...doc.data()
}));

PLUS CONCIS!
-> querySnapshot.docs = Array de docs
-> .map() transforme chaque doc
-> Résultat: Array d'utilisateurs

ATTENTION PERFORMANCE:

getDocs() télécharge TOUS documents!

Si collection a 10,000 documents:
-> 10,000 lectures facturées!
-> Téléchargement peut être lent
-> App peut ralentir

SOLUTION:
-> Utiliser limit()
-> Pagination
-> Requêtes filtrées
-> On verra plus tard!


// Lire tous (v8)
db.collection('users').get()
  .then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
      console.log(doc.id, '=>', doc.data());
    });
  })
  .catch((error) => {
    console.error(error);
  });


# === METTRE À JOUR UN DOCUMENT ===

METTRE À JOUR = Modifier champs existants sans écraser document

// Mettre à jour des champs (v9+)
import { doc, updateDoc } from 'firebase/firestore';

async function updateUser(userId, updates) {
  try {
    const docRef = doc(db, 'users', userId);
    await updateDoc(docRef, updates);
    console.log('Document mis à jour');
  } catch (error) {
    console.error('Erreur mise à jour:', error);
  }
}

// Utilisation
updateUser('user123', {
  age: 31,
  city: 'Paris'
});

EXPLICATION:

updateDoc(docRef, updates)
-> Met à jour champs spécifiés
-> GARDE autres champs intacts!
-> Si champ n'existe pas: l'ajoute

QU'EST-CE QUI SE PASSE:

AVANT UPDATE:
{
  name: "John Doe",
  email: "john@example.com",
  age: 30
}

APRÈS updateDoc({ age: 31, city: 'Paris' }):
{
  name: "John Doe",      // Inchangé [OK]
  email: "john@example.com",  // Inchangé [OK]
  age: 31,               // Mis à jour [OK]
  city: "Paris"          // Ajouté [OK]
}

IMPORTANT:
-> name et email toujours là!
-> age modifié
-> city ajouté
-> PAS d'écrasement!

// Mettre à jour avec opérateurs
import { updateDoc, increment, arrayUnion, serverTimestamp } from 'firebase/firestore';

await updateDoc(doc(db, 'users', 'user123'), {
  age: increment(1),                    // Incrémenter de 1
  hobbies: arrayUnion('reading'),       // Ajouter à tableau (sans doublons)
  lastUpdated: serverTimestamp()        // Timestamp serveur
});

OPÉRATEURS SPÉCIAUX:

increment(n)
-> Incrémente/Décrémente nombre
-> Atomique (sûr concurrent)

EXEMPLE:
age: 30
increment(1) -> age: 31
increment(-5) -> age: 26

CAS D'USAGE:
- Compteur likes
- Compteur vues
- Score jeu
- Solde wallet

arrayUnion(value)
-> Ajoute élément à array
-> SI déjà présent: ignore (pas de doublons)
-> SI array n'existe pas: crée array

EXEMPLE:
hobbies: ["sport", "music"]
arrayUnion('reading') -> ["sport", "music", "reading"]
arrayUnion('sport') -> ["sport", "music"] (déjà présent)

CAS D'USAGE:
- Tags
- Followers
- Favoris
- Permissions

arrayRemove(value)
-> Retire élément de array

EXEMPLE:
hobbies: ["sport", "music", "reading"]
arrayRemove('music') -> ["sport", "reading"]

serverTimestamp()
-> Timestamp du serveur Firebase
-> Plus fiable que new Date() (client)

POURQUOI?
- Timezone serveur correcte
- Pas d'horloge client déréglée
- Cohérence garantie

delete()
-> Supprime champ

import { deleteField } from 'firebase/firestore';

await updateDoc(doc(db, 'users', 'user123'), {
  city: deleteField()  // Supprime champ "city"
});

EXEMPLE COMPLET - LIKE POST:

async function likePost(postId, userId) {
  const postRef = doc(db, 'posts', postId);
  
  await updateDoc(postRef, {
    likes: increment(1),           // +1 like
    likedBy: arrayUnion(userId),   // Ajouter userId
    lastLiked: serverTimestamp()   // Timestamp
  });
}

RÉSULTAT:
{
  title: "Mon post",
  likes: 43,              // Était 42
  likedBy: ["user1", "user2", "user3"],  // user3 ajouté
  lastLiked: Timestamp(2024, 1, 15, ...)
}

ATOMIQUE!
-> Même si 100 users likent en même temps
-> Compteur correct
-> Pas de race conditions


// Mettre à jour (v8)
db.collection('users').doc('user123').update({
  age: 31,
  city: 'Paris'
})
.then(() => console.log('Mis à jour'))
.catch((error) => console.error(error));


# === SET VS UPDATE (DIFFÉRENCE CRITIQUE) ===

SET et UPDATE = Différents!

// setDoc(): Écrase ou crée le document
await setDoc(doc(db, 'users', 'user123'), {
  name: 'John'  // Tous les autres champs sont supprimés
});

AVANT:
{
  name: "John Doe",
  email: "john@example.com",
  age: 30,
  city: "Paris"
}

APRÈS setDoc({ name: 'John' }):
{
  name: "John"  // TOUT LE RESTE SUPPRIMÉ! [IMPACT]
}

setDoc() = ÉCRASE TOUT!
-> Comme réécrire fichier entièrement
-> Ancien contenu perdu
-> Seulement ce que vous passez reste

// setDoc() avec merge: Met à jour sans écraser
await setDoc(doc(db, 'users', 'user123'), {
  city: 'Paris'  // Ne supprime pas les autres champs
}, { merge: true });

AVANT:
{
  name: "John Doe",
  email: "john@example.com",
  age: 30
}

APRÈS setDoc({ city: 'Paris' }, { merge: true }):
{
  name: "John Doe",       // Gardé [OK]
  email: "john@example.com",  // Gardé [OK]
  age: 30,                // Gardé [OK]
  city: "Paris"           // Ajouté [OK]
}

{ merge: true } = Fusionne au lieu d'écraser!
-> Comme updateDoc()
-> Mais crée doc si n'existe pas

// updateDoc(): Met à jour seulement les champs spécifiés
// Erreur si document n'existe pas
await updateDoc(doc(db, 'users', 'user123'), {
  city: 'Paris'
});

ERREUR si doc inexistant!
-> "Document not found"
-> Ne crée PAS document

TABLEAU COMPARATIF:

                    setDoc()    setDoc(merge)   updateDoc()
Écrase tout         [OK]           [X]               [X]
Garde autres champs [X]           [OK]               [OK]
Crée si inexistant  [OK]           [OK]               [X]
Erreur si inexiste  [X]           [X]               [OK]

QUAND UTILISER QUOI?

setDoc() SANS MERGE:
-> Initialisation complète
-> Reset total
-> Vous voulez état propre

EXEMPLE:
await setDoc(doc(db, 'users', userId), {
  name: name,
  email: email,
  createdAt: new Date(),
  role: 'user',
  status: 'active'
});

setDoc() AVEC MERGE:
-> Mise à jour partielle
-> Création si besoin
-> Sûr (ne perd pas données)

EXEMPLE:
await setDoc(doc(db, 'settings', userId), {
  theme: 'dark'
}, { merge: true });

updateDoc():
-> Mise à jour partielle
-> Document DOIT exister
-> Détection erreur si inexistant

EXEMPLE:
await updateDoc(doc(db, 'users', userId), {
  lastLogin: new Date()
});

RECOMMANDATION GÉNÉRALE:
-> Création: setDoc() sans merge
-> Update: updateDoc()
-> Update/Create: setDoc() avec merge


# === SUPPRIMER UN DOCUMENT ===

SUPPRIMER = Effacer document complètement

// Supprimer document (v9+)
import { doc, deleteDoc } from 'firebase/firestore';

async function deleteUser(userId) {
  try {
    await deleteDoc(doc(db, 'users', userId));
    console.log('Document supprimé');
  } catch (error) {
    console.error('Erreur suppression:', error);
  }
}

// Utilisation
deleteUser('user123');

EXPLICATION:

deleteDoc(doc(db, 'users', userId))
-> Supprime document complètement
-> doc(...) = Référence au document
-> Document disparaît de Firestore

QU'EST-CE QUI SE PASSE:

1. Firebase localise document
2. Supprime document
3. Supprime des index
4. Libère espace stockage
5. Document n'existe plus

IMPORTANT:
-> IRRÉVERSIBLE! Pas de corbeille
-> Sous-collections PAS supprimées!
-> Références ailleurs deviennent invalides

SOUS-COLLECTIONS RESTENT!

AVANT:
users/
  alice123/
    name: "Alice"
    posts/
      post1/
        title: "Hello"

APRÈS deleteDoc(doc(db, 'users', 'alice123')):
users/
  alice123/ <- SUPPRIMÉ
    posts/     <- RESTE! (orphelin)
      post1/
        title: "Hello"

PROBLÈME:
-> Sous-collections orphelines
-> Comptent dans quota
-> Invisibles dans console (parent supprimé)

SOLUTION: Supprimer sous-collections d'abord

async function deleteUserCompletely(userId) {
  // 1. Supprimer sous-collection posts
  const postsSnap = await getDocs(collection(db, 'users', userId, 'posts'));
  const deletePromises = postsSnap.docs.map(doc => deleteDoc(doc.ref));
  await Promise.all(deletePromises);

  // 2. Supprimer document user
  await deleteDoc(doc(db, 'users', userId));

  console.log('Utilisateur et posts supprimés');
}

MEILLEURE SOLUTION: Cloud Function

exports.deleteUserData = functions.firestore
  .document('users/{userId}')
  .onDelete(async (snap, context) => {
    const userId = context.params.userId;

    // Supprimer toutes sous-collections
    await deleteCollection(db, `users/${userId}/posts`, 100);
    await deleteCollection(db, `users/${userId}/comments`, 100);
  });

AUTOMATIQUE!
-> User supprimé -> Function se déclenche
-> Nettoie tout
-> Vous ne gérez rien!


// Supprimer (v8)
db.collection('users').doc('user123').delete()
  .then(() => console.log('Supprimé'))
  .catch((error) => console.error(error));


// Supprimer un champ spécifique
import { deleteField } from 'firebase/firestore';

await updateDoc(doc(db, 'users', 'user123'), {
  city: deleteField()  // Supprime le champ "city"
});

SUPPRIMER CHAMP ≠ SUPPRIMER DOCUMENT

SUPPRIMER CHAMP:
{
  name: "John",
  email: "john@example.com",
  city: "Paris"
}

Après deleteField():
{
  name: "John",
  email: "john@example.com"
  // city n'existe plus
}

Document toujours là, champ disparu!

QUAND UTILISER?
-> Données temporaires (code promo utilisé)
-> Champs obsolètes
-> Nettoyage

ALTERNATIVE:
-> Mettre null
-> Mais champ existe toujours (compte dans taille)

city: null ≠ deleteField()

null -> Champ existe, valeur null
deleteField() -> Champ n'existe plus


# === RÉCAPITULATIF OPÉRATIONS DE BASE ===

[OK] CRÉER:
addDoc() -> ID auto
setDoc() -> ID personnalisé

[OK] LIRE:
getDoc() -> Un document
getDocs() -> Tous documents

[OK] METTRE À JOUR:
updateDoc() -> Champs spécifiques
setDoc(merge: true) -> Fusion
increment(), arrayUnion(), etc.

[OK] SUPPRIMER:
deleteDoc() -> Document entier
deleteField() -> Champ spécifique

AVEC CES 4 OPÉRATIONS (CRUD):
Vous pouvez construire 90% des apps! [BRAVO]

PROCHAINE ÉTAPE:
Requêtes avancées (where, orderBy, limit) pour filtrer données! [RECHERCHE]async function changePassword(newPassword) {
  try {
    await updatePassword(auth.currentUser, newPassword);
    console.log('Mot de passe changé');
  } catch (error) {
    console.error('Erreur:', error);
    // Peut nécessiter réauthentification récente
  }
}

EXPLICATION:

updatePassword(auth.currentUser, newPassword)
-> Change mot de passe utilisateur actuel
-> newPassword = Nouveau mot de passe (min 6 caractères)

ATTENTION:
-> Opération très sensible!
-> Nécessite connexion récente (<5 min)
-> Si trop vieux: auth/requires-recent-login

WORKFLOW RECOMMANDÉ:
1. Demander mot de passe actuel
2. Réauthentifier
3. Changer mot de passe

EXEMPLE COMPLET:

function ChangePasswordForm() {
  const [currentPassword, setCurrentPassword] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [error, setError] = useState('');
  const { user } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');

    // Validation
    if (newPassword !== confirmPassword) {
      setError('Les mots de passe ne correspondent pas');
      return;
    }

    if (newPassword.length < 6) {
      setError('Mot de passe minimum 6 caractères');
      return;
    }

    try {
      // 1. Réauthentifier avec mot de passe actuel
      const credential = EmailAuthProvider.credential(
        user.email,
        currentPassword
      );
      await reauthenticateWithCredential(user, credential);

      // 2. Changer mot de passe
      await updatePassword(user, newPassword);

      alert('Mot de passe changé avec succès!');
      
      // Reset form
      setCurrentPassword('');
      setNewPassword('');
      setConfirmPassword('');
    } catch (err) {
      if (err.code === 'auth/wrong-password') {
        setError('Mot de passe actuel incorrect');
      } else {
        setError('Erreur: ' + err.message);
      }
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Changer mot de passe</h2>
      
      {error && <p style={{color: 'red'}}>{error}</p>}
      
      <input
        type="password"
        placeholder="Mot de passe actuel"
        value={currentPassword}
        onChange={(e) => setCurrentPassword(e.target.value)}
        required
      />
      
      <input
        type="password"
        placeholder="Nouveau mot de passe"
        value={newPassword}
        onChange={(e) => setNewPassword(e.target.value)}
        required
      />
      
      <input
        type="password"
        placeholder="Confirmer nouveau mot de passe"
        value={confirmPassword}
        onChange={(e) => setConfirmPassword(e.target.value)}
        required
      />
      
      <button type="submit">Changer mot de passe</button>
    </form>
  );
}

VALIDATIONS IMPORTANTES:
[OK] Mots de passe correspondent
[OK] Minimum 6 caractères
[OK] Réauthentification avec ancien password
[OK] Messages d'erreur clairs


# === REQUÊTES AVANCÉES ===

// Requêtes avec where() (v9+)
import { collection, query, where, getDocs } from 'firebase/firestore';

// Trouver utilisateurs adultes
async function getAdultUsers() {
  const q = query(
    collection(db, 'users'),
    where('age', '>=', 18)
  );
  
  const querySnapshot = await getDocs(q);
  querySnapshot.forEach((doc) => {
    console.log(doc.id, '=>', doc.data());
  });
}


// Opérateurs de comparaison:
where('age', '==', 30)        // Égal
where('age', '!=', 30)        // Différent
where('age', '<', 30)         // Inférieur
where('age', '<=', 30)        // Inférieur ou égal
where('age', '>', 18)         // Supérieur
where('age', '>=', 18)        // Supérieur ou égal
where('name', 'in', ['John', 'Jane'])           // Dans tableau
where('name', 'not-in', ['Bob', 'Alice'])       // Pas dans tableau
where('city', 'array-contains', 'Paris')        // Tableau contient
where('tags', 'array-contains-any', ['tech', 'sports'])  // Contient au moins un


// Requêtes multiples
const q = query(
  collection(db, 'users'),
  where('age', '>=', 18),
  where('city', '==', 'Paris')
);


// IMPORTANT: Limitation Firestore
// Impossible de combiner: != et not-in avec d'autres where
// Impossible de faire: where('a', '!=', x) et where('b', '!=', y)


// Requêtes (v8)
db.collection('users')
  .where('age', '>=', 18)
  .get()
  .then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
      console.log(doc.data());
    });
  });


# === TRI ET LIMITE ===

// Trier et limiter résultats (v9+)
import { query, orderBy, limit, startAfter, getDocs } from 'firebase/firestore';

// Trier par âge décroissant
const q1 = query(
  collection(db, 'users'),
  orderBy('age', 'desc')
);

// Limiter à 10 résultats
const q2 = query(
  collection(db, 'users'),
  orderBy('age', 'desc'),
  limit(10)
);

// Trier par plusieurs champs
const q3 = query(
  collection(db, 'users'),
  orderBy('city'),
  orderBy('age', 'desc')
);

// Pagination: obtenir les 10 suivants
const first = query(
  collection(db, 'users'),
  orderBy('age'),
  limit(10)
);

const snapshot = await getDocs(first);
const lastVisible = snapshot.docs[snapshot.docs.length - 1];

const next = query(
  collection(db, 'users'),
  orderBy('age'),
  startAfter(lastVisible),
  limit(10)
);


// Trier (v8)
db.collection('users')
  .orderBy('age', 'desc')
  .limit(10)
  .get()
  .then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
      console.log(doc.data());
    });
  });


# === ÉCOUTER EN TEMPS RÉEL (REALTIME) ===

// Écouter les changements d'un document (v9+)
import { doc, onSnapshot } from 'firebase/firestore';

const unsubscribe = onSnapshot(doc(db, 'users', 'user123'), (doc) => {
  if (doc.exists()) {
    console.log('Données actuelles:', doc.data());
  } else {
    console.log('Document supprimé');
  }
});

// Arrêter l'écoute
unsubscribe();


// Écouter une collection (v9+)
import { collection, onSnapshot } from 'firebase/firestore';

const unsubscribe = onSnapshot(collection(db, 'users'), (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === 'added') {
      console.log('Nouveau:', change.doc.data());
    }
    if (change.type === 'modified') {
      console.log('Modifié:', change.doc.data());
    }
    if (change.type === 'removed') {
      console.log('Supprimé:', change.doc.data());
    }
  });
});


// Écouter avec requête
const q = query(collection(db, 'users'), where('age', '>', 18));
const unsubscribe = onSnapshot(q, (snapshot) => {
  const users = [];
  snapshot.forEach((doc) => {
    users.push(doc.data());
  });
  console.log('Utilisateurs adultes:', users);
});


// Avec React Hook
import { useEffect, useState } from 'react';

function useUsers() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onSnapshot(collection(db, 'users'), (snapshot) => {
      const usersData = [];
      snapshot.forEach((doc) => {
        usersData.push({ id: doc.id, ...doc.data() });
      });
      setUsers(usersData);
      setLoading(false);
    });

    return () => unsubscribe(); // Cleanup
  }, []);

  return { users, loading };
}


// Écouter (v8)
db.collection('users').doc('user123')
  .onSnapshot((doc) => {
    console.log('Données:', doc.data());
  });


# === TRANSACTIONS ===

// Les transactions garantissent l'intégrité des données
import { runTransaction, doc } from 'firebase/firestore';

async function transferMoney(fromUserId, toUserId, amount) {
  try {
    await runTransaction(db, async (transaction) => {
      const fromRef = doc(db, 'users', fromUserId);
      const toRef = doc(db, 'users', toUserId);

      // Lire les documents
      const fromDoc = await transaction.get(fromRef);
      const toDoc = await transaction.get(toRef);

      if (!fromDoc.exists() || !toDoc.exists()) {
        throw new Error('Utilisateur inexistant');
      }

      const fromBalance = fromDoc.data().balance;
      const toBalance = toDoc.data().balance;

      if (fromBalance < amount) {
        throw new Error('Solde insuffisant');
      }

      // Écrire les modifications
      transaction.update(fromRef, { balance: fromBalance - amount });
      transaction.update(toRef, { balance: toBalance + amount });
    });

    console.log('Transfert réussi');
  } catch (error) {
    console.error('Échec transaction:', error);
  }
}


// Transaction (v8)
db.runTransaction((transaction) => {
  return transaction.get(docRef).then((doc) => {
    const newValue = doc.data().value + 1;
    transaction.update(docRef, { value: newValue });
  });
});


# === BATCH WRITES (ÉCRITURES PAR LOT) ===

// Écrire plusieurs documents en une seule opération atomique
import { writeBatch, doc } from 'firebase/firestore';

async function batchUpdate() {
  const batch = writeBatch(db);

  // Ajouter opérations au batch (max 500)
  batch.set(doc(db, 'users', 'user1'), { name: 'Alice' });
  batch.update(doc(db, 'users', 'user2'), { age: 25 });
  batch.delete(doc(db, 'users', 'user3'));

  try {
    await batch.commit();
    console.log('Batch exécuté');
  } catch (error) {
    console.error('Erreur batch:', error);
  }
}


// Batch (v8)
const batch = db.batch();
batch.set(userRef1, { name: 'Alice' });
batch.update(userRef2, { age: 25 });
batch.delete(userRef3);
batch.commit().then(() => console.log('Fait'));


# === SOUS-COLLECTIONS ===

// Ajouter document dans sous-collection (v9+)
import { collection, doc, addDoc } from 'firebase/firestore';

async function addPost(userId, title, content) {
  try {
    const postsRef = collection(db, 'users', userId, 'posts');
    const docRef = await addDoc(postsRef, {
      title: title,
      content: content,
      createdAt: new Date()
    });
    console.log('Post créé:', docRef.id);
  } catch (error) {
    console.error('Erreur:', error);
  }
}

// Utilisation
addPost('user123', 'Mon titre', 'Mon contenu');


// Lire sous-collection
async function getUserPosts(userId) {
  const postsRef = collection(db, 'users', userId, 'posts');
  const querySnapshot = await getDocs(postsRef);
  
  querySnapshot.forEach((doc) => {
    console.log(doc.id, '=>', doc.data());
  });
}


// Sous-collection (v8)
db.collection('users').doc('user123').collection('posts').add({
  title: 'Mon titre',
  content: 'Mon contenu'
});


# === REQUÊTES DE GROUPE DE COLLECTIONS ===

// Rechercher dans toutes les sous-collections avec même nom
import { collectionGroup, query, where, getDocs } from 'firebase/firestore';

async function getAllPosts() {
  // Recherche dans toutes les sous-collections "posts"
  const q = query(
    collectionGroup(db, 'posts'),
    where('published', '==', true)
  );
  
  const querySnapshot = await getDocs(q);
  querySnapshot.forEach((doc) => {
    console.log(doc.id, '=>', doc.data());
  });
}


# === TYPES DE DONNÉES FIRESTORE ===

// Types supportés:
await setDoc(doc(db, 'examples', 'types'), {
  string: 'Hello',
  number: 42,
  boolean: true,
  array: [1, 2, 3],
  object: { nested: 'value' },
  null: null,
  timestamp: new Date(),
  geopoint: new GeoPoint(48.8566, 2.3522),  // Latitude, Longitude
  reference: doc(db, 'users', 'user123'),   // Référence à document
  bytes: Bytes.fromUint8Array(new Uint8Array([1, 2, 3]))
});


// Utiliser GeoPoint
import { GeoPoint } from 'firebase/firestore';

await setDoc(doc(db, 'places', 'paris'), {
  name: 'Paris',
  location: new GeoPoint(48.8566, 2.3522)
});


// Référence à document
import { doc } from 'firebase/firestore';

await setDoc(doc(db, 'posts', 'post1'), {
  title: 'Mon post',
  author: doc(db, 'users', 'user123')  // Référence
});

// Lire référence
const postDoc = await getDoc(doc(db, 'posts', 'post1'));
const authorRef = postDoc.data().author;
const authorDoc = await getDoc(authorRef);
console.log('Auteur:', authorDoc.data().name);


# === TIMESTAMP SERVEUR ===

// Utiliser timestamp du serveur (v9+)
import { serverTimestamp } from 'firebase/firestore';

await setDoc(doc(db, 'posts', 'post1'), {
  title: 'Mon post',
  createdAt: serverTimestamp(),  // Timestamp du serveur Firebase
  updatedAt: serverTimestamp()
});


// Convertir timestamp en Date
const postDoc = await getDoc(doc(db, 'posts', 'post1'));
const timestamp = postDoc.data().createdAt;
const date = timestamp.toDate();
console.log(date.toLocaleString());


[OK] CLOUD STORAGE


Firebase Storage permet de stocker et servir des fichiers (images, vidéos, etc.)


# === CONFIGURATION DANS CONSOLE FIREBASE ===

# 1. Aller dans "Storage"
# 2. Cliquer "Get Started"
# 3. Choisir règles de sécurité
# 4. Choisir région


# === INITIALISATION STORAGE ===

// Initialisation (v9+)
import { getStorage } from 'firebase/storage';
import { app } from './firebase-config';

export const storage = getStorage(app);


// Initialisation (v8)
const storage = firebase.storage();


# === UPLOADER UN FICHIER ===

// Upload depuis input file (v9+)
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';

async function uploadFile(file) {
  try {
    // Créer référence avec chemin
    const storageRef = ref(storage, `images/${file.name}`);
    
    // Upload le fichier
    const snapshot = await uploadBytes(storageRef, file);
    console.log('Fichier uploadé:', snapshot.metadata.fullPath);
    
    // Obtenir URL de téléchargement
    const downloadURL = await getDownloadURL(snapshot.ref);
    console.log('URL:', downloadURL);
    
    return downloadURL;
  } catch (error) {
    console.error('Erreur upload:', error);
  }
}

// Utilisation avec input HTML
<input type="file" id="fileInput" />

document.getElementById('fileInput').addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (file) {
    uploadFile(file);
  }
});


// Upload avec React
import { useState } from 'react';

function FileUpload() {
  const [file, setFile] = useState(null);
  const [url, setUrl] = useState('');
  const [progress, setProgress] = useState(0);

  const handleUpload = async () => {
    if (!file) return;
    
    const storageRef = ref(storage, `images/${file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on('state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        setProgress(progress);
      },
      (error) => {
        console.error('Erreur:', error);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        setUrl(downloadURL);
      }
    );
  };

  return (
    <div>
      <input type="file" onChange={(e) => setFile(e.target.files[0])} />
      <button onClick={handleUpload}>Upload</button>
      <p>Progression: {progress}%</p>
      {url && <img src={url} alt="Uploaded" />}
    </div>
  );
}


// Upload (v8)
const storageRef = storage.ref();
const fileRef = storageRef.child('images/photo.jpg');

fileRef.put(file).then((snapshot) => {
  console.log('Uploadé');
  snapshot.ref.getDownloadURL().then((url) => {
    console.log('URL:', url);
  });
});


# === UPLOAD AVEC MÉTADONNÉES ===

// Ajouter métadonnées lors de l'upload (v9+)
import { ref, uploadBytes } from 'firebase/storage';

const metadata = {
  contentType: 'image/jpeg',
  customMetadata: {
    'uploadedBy': 'user123',
    'description': 'Photo de profil'
  }
};

const storageRef = ref(storage, 'images/photo.jpg');
await uploadBytes(storageRef, file, metadata);


# === UPLOAD AVEC SUIVI DE PROGRESSION ===

// Suivre progression upload (v9+)
import { ref, uploadBytesResumable } from 'firebase/storage';

function uploadWithProgress(file) {
  const storageRef = ref(storage, `images/${file.name}`);
  const uploadTask = uploadBytesResumable(storageRef, file);

  uploadTask.on('state_changed',
    (snapshot) => {
      // Progression
      const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
      console.log('Upload:', progress + '%');
      
      switch (snapshot.state) {
        case 'paused':
          console.log('Upload en pause');
          break;
        case 'running':
          console.log('Upload en cours');
          break;
      }
    },
    (error) => {
      // Erreur
      console.error('Erreur:', error);
      // Codes erreur:
      // storage/unauthorized - Pas autorisé
      // storage/canceled - Upload annulé
      // storage/unknown - Erreur inconnue
    },
    async () => {
      // Upload terminé
      const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
      console.log('Fichier disponible:', downloadURL);
    }
  );
  
  // Contrôles optionnels:
  // uploadTask.pause();   // Mettre en pause
  // uploadTask.resume();  // Reprendre
  // uploadTask.cancel();  // Annuler
}


# === TÉLÉCHARGER UN FICHIER ===

// Obtenir URL de téléchargement (v9+)
import { ref, getDownloadURL } from 'firebase/storage';

async function getFileURL(filePath) {
  try {
    const fileRef = ref(storage, filePath);
    const url = await getDownloadURL(fileRef);
    console.log('URL:', url);
    return url;
  } catch (error) {
    console.error('Erreur:', error);
    // storage/object-not-found - Fichier inexistant
  }
}

// Utilisation
const url = await getFileURL('images/photo.jpg');


// Télécharger fichier en mémoire
import { ref, getBytes } from 'firebase/storage';

async function downloadFile(filePath) {
  const fileRef = ref(storage, filePath);
  const bytes = await getBytes(fileRef);
  console.log('Taille:', bytes.byteLength);
  return bytes;
}


// Télécharger (v8)
storage.ref('images/photo.jpg').getDownloadURL()
  .then((url) => {
    console.log('URL:', url);
  })
  .catch((error) => {
    console.error(error);
  });


# === LISTER LES FICHIERS ===

// Lister fichiers dans un dossier (v9+)
import { ref, listAll } from 'firebase/storage';

async function listFiles(folderPath) {
  try {
    const folderRef = ref(storage, folderPath);
    const result = await listAll(folderRef);
    
    // Fichiers dans le dossier
    result.items.forEach((itemRef) => {
      console.log('Fichier:', itemRef.fullPath);
    });
    
    // Sous-dossiers
    result.prefixes.forEach((folderRef) => {
      console.log('Dossier:', folderRef.fullPath);
    });
    
    return result;
  } catch (error) {
    console.error('Erreur:', error);
  }
}

// Utilisation
await listFiles('images/');


// Lister avec pagination
import { ref, list } from 'firebase/storage';

async function listWithPagination() {
  const folderRef = ref(storage, 'images/');
  
  // Premier batch (max 100)
  const firstPage = await list(folderRef, { maxResults: 100 });
  
  // Page suivante si disponible
  if (firstPage.nextPageToken) {
    const secondPage = await list(folderRef, {
      maxResults: 100,
      pageToken: firstPage.nextPageToken
    });
  }
}


# === OBTENIR MÉTADONNÉES ===

// Obtenir métadonnées fichier (v9+)
import { ref, getMetadata } from 'firebase/storage';

async function getFileMetadata(filePath) {
  try {
    const fileRef = ref(storage, filePath);
    const metadata = await getMetadata(fileRef);
    
    console.log('Nom:', metadata.name);
    console.log('Taille:', metadata.size, 'bytes');
    console.log('Type:', metadata.contentType);
    console.log('Créé:', metadata.timeCreated);
    console.log('Modifié:', metadata.updated);
    console.log('MD5:', metadata.md5Hash);
    console.log('URL:', metadata.downloadTokens);
    
    return metadata;
  } catch (error) {
    console.error('Erreur:', error);
  }
}


# === METTRE À JOUR MÉTADONNÉES ===

// Mettre à jour métadonnées (v9+)
import { ref, updateMetadata } from 'firebase/storage';

async function updateFileMetadata(filePath) {
  const fileRef = ref(storage, filePath);
  
  const newMetadata = {
    contentType: 'image/png',
    customMetadata: {
      'location': 'Paris',
      'photographer': 'John Doe'
    }
  };
  
  try {
    const metadata = await updateMetadata(fileRef, newMetadata);
    console.log('Métadonnées mises à jour');
  } catch (error) {
    console.error('Erreur:', error);
  }
}


# === SUPPRIMER UN FICHIER ===

// Supprimer fichier (v9+)
import { ref, deleteObject } from 'firebase/storage';

async function deleteFile(filePath) {
  try {
    const fileRef = ref(storage, filePath);
    await deleteObject(fileRef);
    console.log('Fichier supprimé');
  } catch (error) {
    console.error('Erreur suppression:', error);
    // storage/object-not-found - Fichier inexistant
  }
}

// Utilisation
deleteFile('images/photo.jpg');


// Supprimer (v8)
storage.ref('images/photo.jpg').delete()
  .then(() => console.log('Supprimé'))
  .catch((error) => console.error(error));


# === STRUCTURE DE STOCKAGE RECOMMANDÉE ===

// Organisation des fichiers:
users/
  {userId}/
    profile.jpg           # Photo de profil
    documents/
      doc1.pdf
      doc2.pdf

posts/
  {postId}/
    image.jpg
    thumbnail.jpg

public/
  images/
    logo.png
    banner.jpg


// Exemple avec userId
import { getAuth } from 'firebase/auth';

const userId = getAuth().currentUser.uid;
const storageRef = ref(storage, `users/${userId}/profile.jpg`);


# === EXEMPLE COMPLET: UPLOAD AVATAR ===

// Upload avatar utilisateur complet
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { doc, updateDoc } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';

async function uploadAvatar(file) {
  try {
    const userId = getAuth().currentUser.uid;
    
    // 1. Upload vers Storage
    const storageRef = ref(storage, `users/${userId}/avatar.jpg`);
    const metadata = {
      contentType: file.type,
      customMetadata: {
        uploadedAt: new Date().toISOString()
      }
    };
    
    await uploadBytes(storageRef, file, metadata);
    console.log('Avatar uploadé');
    
    // 2. Obtenir URL
    const downloadURL = await getDownloadURL(storageRef);
    
    // 3. Mettre à jour Firestore
    await updateDoc(doc(db, 'users', userId), {
      avatarURL: downloadURL,
      updatedAt: new Date()
    });
    
    console.log('Profil mis à jour');
    return downloadURL;
    
  } catch (error) {
    console.error('Erreur:', error);
    throw error;
  }
}


[OK] SECURITY RULES (RÈGLES DE SÉCURITÉ)


Les règles de sécurité protègent vos données contre les accès non autorisés.


# === FIRESTORE RULES ===

# Emplacement: Firebase Console > Firestore Database > Rules

# Structure de base:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Règles ici
  }
}


# Accès public (DANGEREUX - seulement pour tests)
match /{document=**} {
  allow read, write: if true;
}


# Accès seulement si authentifié
match /users/{userId} {
  allow read, write: if request.auth != null;
}


# Accès seulement à ses propres données
match /users/{userId} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}


# Lecture publique, écriture authentifiée
match /posts/{postId} {
  allow read: if true;
  allow write: if request.auth != null;
}


# Validation des données
match /posts/{postId} {
  allow create: if request.auth != null
                && request.resource.data.title is string
                && request.resource.data.title.size() > 0
                && request.resource.data.title.size() <= 100;
  
  allow update: if request.auth != null
                && request.auth.uid == resource.data.authorId;
  
  allow delete: if request.auth != null
                && request.auth.uid == resource.data.authorId;
}


# Règles pour sous-collections
match /users/{userId} {
  allow read: if request.auth != null && request.auth.uid == userId;
  
  match /posts/{postId} {
    allow read: if request.auth != null;
    allow write: if request.auth != null && request.auth.uid == userId;
  }
}


# Fonctions réutilisables
function isSignedIn() {
  return request.auth != null;
}

function isOwner(userId) {
  return request.auth.uid == userId;
}

match /users/{userId} {
  allow read, write: if isSignedIn() && isOwner(userId);
}


# Variables disponibles:
// request.auth.uid - ID utilisateur
// request.auth.token.email - Email
// request.auth.token.email_verified - Email vérifié
// request.resource.data - Nouvelles données
// resource.data - Données actuelles
// request.time - Timestamp de la requête


# Exemple complet avec validation
match /posts/{postId} {
  allow read: if true;
  
  allow create: if request.auth != null
                && request.resource.data.keys().hasAll(['title', 'content', 'authorId', 'createdAt'])
                && request.resource.data.title is string
                && request.resource.data.title.size() >= 3
                && request.resource.data.title.size() <= 100
                && request.resource.data.content is string
                && request.resource.data.content.size() <= 5000
                && request.resource.data.authorId == request.auth.uid
                && request.resource.data.createdAt == request.time;
  
  allow update: if request.auth != null
                && resource.data.authorId == request.auth.uid
                && request.resource.data.authorId == resource.data.authorId;
  
  allow delete: if request.auth != null
                && resource.data.authorId == request.auth.uid;
}


# === STORAGE RULES ===

# Emplacement: Firebase Console > Storage > Rules

# Structure de base:
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // Règles ici
  }
}


# Accès public lecture, écriture authentifiée
match /{allPaths=**} {
  allow read: if true;
  allow write: if request.auth != null;
}


# Accès seulement à ses propres fichiers
match /users/{userId}/{allPaths=**} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}


# Validation type et taille de fichier
match /users/{userId}/avatar.jpg {
  allow read: if true;
  allow write: if request.auth != null
               && request.auth.uid == userId
               && request.resource.size < 5 * 1024 * 1024  // Max 5MB
               && request.resource.contentType.matches('image/.*');
}


# Limite images
match /images/{imageId} {
  allow read: if true;
  allow write: if request.auth != null
               && request.resource.size < 2 * 1024 * 1024  // Max 2MB
               && request.resource.contentType in ['image/jpeg', 'image/png', 'image/gif'];
}


# Empêcher suppression
match /permanent/{document=**} {
  allow read: if true;
  allow write: if request.auth != null && request.resource != null;
  // request.resource == null signifie suppression
}


# Variables disponibles Storage:
// request.auth.uid - ID utilisateur
// request.resource.size - Taille fichier (bytes)
// request.resource.contentType - Type MIME
// request.resource.name - Nom fichier
// resource.size - Taille actuelle
// resource.contentType - Type actuel


# Exemple règles professionnelles
match /users/{userId} {
  match /profile.jpg {
    allow read: if true;
    allow write: if request.auth != null
                 && request.auth.uid == userId
                 && request.resource.size < 5 * 1024 * 1024
                 && request.resource.contentType.matches('image/.*');
  }
  
  match /documents/{docId} {
    allow read: if request.auth != null && request.auth.uid == userId;
    allow write: if request.auth != null
                 && request.auth.uid == userId
                 && request.resource.size < 10 * 1024 * 1024
                 && request.resource.contentType == 'application/pdf';
  }
}


# Tester les règles dans Firebase Console
# Console > Firestore/Storage > Rules > Simulateur


[OK] CLOUD FUNCTIONS


Cloud Functions permet d'exécuter du code backend sans gérer de serveurs.
Déclencheurs: HTTP, Firestore, Auth, Storage, Pub/Sub, Schedule.


# === INSTALLATION ===

# Installer Firebase CLI
npm install -g firebase-tools

# Vérifier installation
firebase --version

# Se connecter à Firebase
firebase login

# Initialiser Functions dans projet
firebase init functions

# Choisir:
# - Projet Firebase existant
# - Langage: JavaScript ou TypeScript
# - ESLint: Oui (recommandé)
# - Installer dépendances: Oui

# Structure créée:
# functions/
#   node_modules/
#   index.js          # Vos fonctions
#   package.json


# === PREMIÈRE FONCTION HTTP ===

// functions/index.js
const functions = require('firebase-functions');

// Fonction HTTP simple
exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!");
});

// Déployer
firebase deploy --only functions

// URL générée:
// https://us-central1-PROJECT_ID.cloudfunctions.net/helloWorld


# Fonction HTTP avec paramètres
exports.greet = functions.https.onRequest((req, res) => {
  const name = req.query.name || 'World';
  res.json({ message: `Hello ${name}!` });
});

// Appeler: https://...cloudfunctions.net/greet?name=John
// Résultat: {"message": "Hello John!"}


# Fonction HTTP avec CORS
const cors = require('cors')({ origin: true });

exports.getData = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    res.json({ data: 'Some data' });
  });
});


# === FONCTION CALLABLE (RECOMMANDÉ) ===

// functions/index.js
const functions = require('firebase-functions');

// Fonction callable (plus sécurisée)
exports.addMessage = functions.https.onCall((data, context) => {
  // context.auth contient info utilisateur
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'Must be authenticated'
    );
  }
  
  const text = data.text;
  const uid = context.auth.uid;
  
  return {
    message: `User ${uid} said: ${text}`
  };
});


// Appeler depuis client (v9+)
import { getFunctions, httpsCallable } from 'firebase/functions';

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');

async function callFunction() {
  try {
    const result = await addMessage({ text: 'Hello' });
    console.log(result.data.message);
  } catch (error) {
    console.error('Erreur:', error);
  }
}


// Appeler (v8)
const addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: 'Hello' })
  .then((result) => console.log(result.data))
  .catch((error) => console.error(error));


# === DÉCLENCHEURS FIRESTORE ===

// Trigger quand document est créé
const admin = require('firebase-admin');
admin.initializeApp();

exports.onUserCreate = functions.firestore
  .document('users/{userId}')
  .onCreate((snap, context) => {
    const newUser = snap.data();
    const userId = context.params.userId;
    
    console.log('Nouvel utilisateur:', userId, newUser);
    
    // Envoyer email de bienvenue, etc.
    return null;
  });


// Trigger quand document est mis à jour
exports.onUserUpdate = functions.firestore
  .document('users/{userId}')
  .onUpdate((change, context) => {
    const beforeData = change.before.data();
    const afterData = change.after.data();
    const userId = context.params.userId;
    
    console.log('Utilisateur modifié:', userId);
    console.log('Avant:', beforeData);
    console.log('Après:', afterData);
    
    return null;
  });


// Trigger quand document est supprimé
exports.onUserDelete = functions.firestore
  .document('users/{userId}')
  .onDelete((snap, context) => {
    const deletedUser = snap.data();
    const userId = context.params.userId;
    
    console.log('Utilisateur supprimé:', userId);
    
    // Nettoyer Storage, etc.
    return null;
  });


// Trigger sur tous changements
exports.onUserChange = functions.firestore
  .document('users/{userId}')
  .onWrite((change, context) => {
    if (!change.before.exists) {
      // Création
      console.log('Création');
    } else if (!change.after.exists) {
      // Suppression
      console.log('Suppression');
    } else {
      // Mise à jour
      console.log('Mise à jour');
    }
    
    return null;
  });


# === EXEMPLE COMPLET: CRÉER THUMBNAIL ===

// Créer miniature quand image uploadée
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { Storage } = require('@google-cloud/storage');
const sharp = require('sharp');
const path = require('path');
const os = require('os');
const fs = require('fs');

admin.initializeApp();
const gcs = new Storage();

exports.generateThumbnail = functions.storage
  .object()
  .onFinalize(async (object) => {
    const filePath = object.name;
    const bucketName = object.bucket;
    const contentType = object.contentType;
    
    // Vérifier si c'est une image
    if (!contentType.startsWith('image/')) {
      console.log('Pas une image');
      return null;
    }
    
    // Éviter boucle infinie (ne pas traiter thumbnails)
    if (filePath.includes('thumb_')) {
      console.log('Déjà un thumbnail');
      return null;
    }
    
    const bucket = gcs.bucket(bucketName);
    const fileName = path.basename(filePath);
    const tempFilePath = path.join(os.tmpdir(), fileName);
    const thumbFileName = `thumb_${fileName}`;
    const thumbFilePath = path.join(os.tmpdir(), thumbFileName);
    
    // Télécharger fichier
    await bucket.file(filePath).download({ destination: tempFilePath });
    console.log('Image téléchargée:', tempFilePath);
    
    // Créer thumbnail avec sharp
    await sharp(tempFilePath)
      .resize(200, 200, { fit: 'cover' })
      .toFile(thumbFilePath);
    console.log('Thumbnail créé:', thumbFilePath);
    
    // Upload thumbnail
    const thumbPath = path.join(path.dirname(filePath), thumbFileName);
    await bucket.upload(thumbFilePath, {
      destination: thumbPath,
      metadata: { contentType: contentType }
    });
    console.log('Thumbnail uploadé:', thumbPath);
    
    // Nettoyer fichiers temporaires
    fs.unlinkSync(tempFilePath);
    fs.unlinkSync(thumbFilePath);
    
    return null;
  });


# Installer dépendances pour thumbnail
cd functions
npm install sharp @google-cloud/storage


# === DÉCLENCHEURS AUTHENTICATION ===

// Trigger quand utilisateur créé
exports.onUserCreate = functions.auth.user().onCreate((user) => {
  const email = user.email;
  const uid = user.uid;
  
  console.log('Nouvel utilisateur:', uid, email);
  
  // Créer document Firestore pour l'utilisateur
  return admin.firestore().collection('users').doc(uid).set({
    email: email,
    createdAt: admin.firestore.FieldValue.serverTimestamp()
  });
});


// Trigger quand utilisateur supprimé
exports.onUserDelete = functions.auth.user().onDelete((user) => {
  const uid = user.uid;
  
  console.log('Utilisateur supprimé:', uid);
  
  // Nettoyer données utilisateur
  return admin.firestore().collection('users').doc(uid).delete();
});


# === FONCTIONS PLANIFIÉES (CRON) ===

// Fonction exécutée à intervalle régulier
exports.scheduledFunction = functions.pubsub
  .schedule('every 24 hours')
  .onRun((context) => {
    console.log('Fonction planifiée exécutée');
    // Nettoyer données, envoyer rapports, etc.
    return null;
  });


// Syntaxe cron
'every 5 minutes'
'every day 00:00'
'every monday 09:00'
'0 9 * * 1'  // Chaque lundi à 9h (format cron)


// Fonction avec timezone
exports.scheduledFunction = functions.pubsub
  .schedule('every day 09:00')
  .timeZone('Europe/Paris')
  .onRun((context) => {
    console.log('Exécuté à 9h Paris');
    return null;
  });


# === ADMIN SDK DANS FUNCTIONS ===

// Utiliser Admin SDK dans functions
const admin = require('firebase-admin');
admin.initializeApp();

const db = admin.firestore();
const auth = admin.auth();
const storage = admin.storage();

// Admin SDK a accès complet (ignore security rules)


// Exemple: Créer utilisateur
exports.createUser = functions.https.onCall(async (data, context) => {
  try {
    const userRecord = await admin.auth().createUser({
      email: data.email,
      password: data.password,
      displayName: data.name
    });
    
    return { success: true, uid: userRecord.uid };
  } catch (error) {
    throw new functions.https.HttpsError('internal', error.message);
  }
});


// Exemple: Mettre à jour Firestore
exports.updateUserStats = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
  }
  
  const userId = context.auth.uid;
  
  await admin.firestore().collection('users').doc(userId).update({
    lastActive: admin.firestore.FieldValue.serverTimestamp(),
    loginCount: admin.firestore.FieldValue.increment(1)
  });
  
  return { success: true };
});


# === DÉPLOIEMENT ===

# Déployer toutes les fonctions
firebase deploy --only functions

# Déployer fonction spécifique
firebase deploy --only functions:helloWorld

# Déployer plusieurs fonctions
firebase deploy --only functions:func1,functions:func2

# Voir logs
firebase functions:log

# Voir logs en temps réel
firebase functions:log --only helloWorld

# Supprimer fonction
firebase functions:delete helloWorld


# === CONFIGURATION ET SECRETS ===

# Définir variable de configuration
firebase functions:config:set someservice.key="THE_KEY" someservice.id="THE_ID"

# Obtenir config
firebase functions:config:get

# Utiliser config dans fonction
const functions = require('firebase-functions');
const key = functions.config().someservice.key;
const id = functions.config().someservice.id;


# Stocker secrets (recommandé)
firebase functions:secrets:set API_KEY

# Utiliser secret
exports.myFunction = functions
  .runWith({ secrets: ['API_KEY'] })
  .https.onRequest((req, res) => {
    const apiKey = process.env.API_KEY;
    // ...
  });


# === CONFIGURATION AVANCÉE ===

// Configurer mémoire et timeout
exports.bigFunction = functions
  .runWith({
    timeoutSeconds: 540,        // Max 540 (9 minutes)
    memory: '2GB'               // '128MB', '256MB', '512MB', '1GB', '2GB', '4GB', '8GB'
  })
  .https.onRequest((req, res) => {
    // Fonction avec plus de ressources
  });


// Fonction dans région spécifique
exports.europeFunction = functions
  .region('europe-west1')
  .https.onRequest((req, res) => {
    res.send('Fonction en Europe');
  });


# === BONNES PRATIQUES FUNCTIONS ===

// 1. Toujours retourner Promise ou null
exports.goodFunction = functions.firestore
  .document('users/{userId}')
  .onCreate((snap, context) => {
    return admin.firestore().collection('logs').add({
      message: 'User created'
    });
  });

// Mauvais - pas de return
exports.badFunction = functions.firestore
  .document('users/{userId}')
  .onCreate((snap, context) => {
    admin.firestore().collection('logs').add({
      message: 'User created'
    });
    // Firebase pourrait terminer avant que l'opération finisse
  });


// 2. Gérer erreurs
exports.safeFunction = functions.https.onCall(async (data, context) => {
  try {
    // Opération
    return { success: true };
  } catch (error) {
    console.error('Erreur:', error);
    throw new functions.https.HttpsError('internal', 'Operation failed');
  }
});


// 3. Éviter boucles infinies
exports.updateCounter = functions.firestore
  .document('posts/{postId}')
  .onUpdate((change, context) => {
    const before = change.before.data();
    const after = change.after.data();
    
    // Vérifier si compteur a changé pour éviter boucle
    if (before.counter === after.counter) {
      return null;
    }
    
    // Mise à jour
    return change.after.ref.update({
      counterUpdatedAt: admin.firestore.FieldValue.serverTimestamp()
    });
  });


[OK] FIREBASE HOSTING


Héberger applications web statiques (HTML, CSS, JS, images).


# === INSTALLATION ===

# Installer Firebase CLI
npm install -g firebase-tools

# Se connecter
firebase login

# Initialiser Hosting
firebase init hosting

# Choisir:
# - Dossier public (défaut: public)
# - Configuration SPA (oui pour React/Vue/Angular)
# - Remplacer index.html: Non

# Fichiers créés:
# firebase.json         # Configuration
# .firebaserc          # Projet Firebase
# public/              # Dossier à déployer


# === DÉPLOIEMENT ===

# Déployer site
firebase deploy --only hosting

# URL générée:
# https://PROJECT_ID.web.app
# https://PROJECT_ID.firebaseapp.com


# Build et deploy (React/Vue/Angular)
npm run build
firebase deploy --only hosting


# === CONFIGURATION firebase.json ===

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}


# Configuration pour React
{
  "hosting": {
    "public": "build",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=31536000"
          }
        ]
      }
    ]
  }
}


# Configuration pour Vue
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}


# === DOMAINE PERSONNALISÉ ===

# 1. Console Firebase > Hosting > Add custom domain
# 2. Entrer domaine: example.com
# 3. Vérifier propriété (TXT record DNS)
# 4. Ajouter records A dans DNS:
#    example.com -> IP fournie par Firebase
# 5. Attendre propagation DNS (quelques heures)

# Firebase gère automatiquement:
# - Certificat SSL (HTTPS)
# - Renouvellement SSL
# - Redirection www


# === PRÉVISUALISATION ===

# Créer preview avant déploiement
firebase hosting:channel:deploy preview

# URL temporaire générée (7 jours par défaut):
# https://PROJECT_ID--preview-RANDOM.web.app


# === HISTORIQUE & ROLLBACK ===

# Console Firebase > Hosting > Release history
# Cliquer sur version précédente > Rollback


[OK] FIREBASE CLI - COMMANDES UTILES


# Connexion/Déconnexion
firebase login
firebase login --reauth              # Forcer réauthentification
firebase logout

# Voir utilisateur connecté
firebase login:list

# Lister projets
firebase projects:list

# Utiliser projet spécifique
firebase use PROJECT_ID
firebase use --add                   # Ajouter alias

# Initialiser projet
firebase init

# Déployer tout
firebase deploy

# Déployer services spécifiques
firebase deploy --only hosting
firebase deploy --only functions
firebase deploy --only firestore:rules
firebase deploy --only storage:rules

# Déployer fonction spécifique
firebase deploy --only functions:myFunction

# Tester localement
firebase emulators:start
firebase emulators:start --only functions,firestore

# Voir logs functions
firebase functions:log
firebase functions:log --only myFunction

# Servir Hosting localement
firebase serve
firebase serve --only hosting
firebase serve --port 3000

# Ouvrir console Firebase
firebase open

# Obtenir aide
firebase --help
firebase deploy --help


[OK] FIREBASE EMULATORS (DÉVELOPPEMENT LOCAL)


Tester Firebase localement sans affecter production.


# === INSTALLATION ===

# Initialiser emulators
firebase init emulators

# Choisir emulators:
# - Authentication
# - Firestore
# - Functions
# - Storage
# - Hosting


# === CONFIGURATION ===

# firebase.json
{
  "emulators": {
    "auth": {
      "port": 9099
    },
    "firestore": {
      "port": 8080
    },
    "functions": {
      "port": 5001
    },
    "storage": {
      "port": 9199
    },
    "hosting": {
      "port": 5000
    },
    "ui": {
      "enabled": true,
      "port": 4000
    }
  }
}


# === DÉMARRAGE ===

# Démarrer tous les emulators
firebase emulators:start

# Démarrer emulators spécifiques
firebase emulators:start --only firestore,auth

# Interface UI disponible à:
# http://localhost:4000


# === UTILISER EMULATORS DANS CODE ===

// Se connecter aux emulators (v9+)
import { getAuth, connectAuthEmulator } from 'firebase/auth';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
import { getStorage, connectStorageEmulator } from 'firebase/storage';
import { getFunctions, connectFunctionsEmulator } from 'firebase/functions';

const auth = getAuth();
const db = getFirestore();
const storage = getStorage();
const functions = getFunctions();

// Connecter aux emulators (seulement en développement)
if (location.hostname === 'localhost') {
  connectAuthEmulator(auth, 'http://localhost:9099');
  connectFirestoreEmulator(db, 'localhost', 8080);
  connectStorageEmulator(storage, 'localhost', 9199);
  connectFunctionsEmulator(functions, 'localhost', 5001);
}


// Avec variables d'environnement
if (process.env.NODE_ENV === 'development') {
  connectAuthEmulator(auth, 'http://localhost:9099');
  connectFirestoreEmulator(db, 'localhost', 8080);
}


# === IMPORT/EXPORT DONNÉES ===

# Exporter données Firestore
firebase emulators:export ./emulator-data

# Importer données au démarrage
firebase emulators:start --import=./emulator-data

# Import et export automatique
firebase emulators:start --import=./emulator-data --export-on-exit


[OK] FIREBASE AVEC PYTHON


Firebase peut aussi être utilisé avec Python via Admin SDK.


# === INSTALLATION PYTHON ===

pip install firebase-admin


# === INITIALISATION ===

# Obtenir clé privée:
# Console Firebase > Project Settings > Service Accounts
# Cliquer "Generate new private key"
# Télécharger JSON

import firebase_admin
from firebase_admin import credentials, firestore, auth, storage

# Initialiser avec clé privée
cred = credentials.Certificate('path/to/serviceAccountKey.json')
firebase_admin.initialize_app(cred)

# Ou avec credentials par défaut (Google Cloud)
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred, {
    'storageBucket': 'PROJECT_ID.appspot.com'
})


# === FIRESTORE AVEC PYTHON ===

# Obtenir référence Firestore
db = firestore.client()

# Ajouter document
doc_ref = db.collection('users').document('user123')
doc_ref.set({
    'name': 'John Doe',
    'email': 'john@example.com',
    'age': 30
})

# Ajouter avec ID auto-généré
doc_ref = db.collection('users').add({
    'name': 'Jane Doe',
    'email': 'jane@example.com'
})
print(f'Document ID: {doc_ref[1].id}')

# Lire document
doc = db.collection('users').document('user123').get()
if doc.exists:
    print(doc.to_dict())
else:
    print('Document inexistant')

# Lire tous les documents
users_ref = db.collection('users')
docs = users_ref.stream()
for doc in docs:
    print(f'{doc.id} => {doc.to_dict()}')

# Requêtes
users_ref = db.collection('users')
query = users_ref.where('age', '>=', 18)
results = query.stream()
for doc in results:
    print(doc.to_dict())

# Mettre à jour
doc_ref = db.collection('users').document('user123')
doc_ref.update({
    'age': 31,
    'city': 'Paris'
})

# Supprimer
db.collection('users').document('user123').delete()

# Transaction
transaction = db.transaction()

@firestore.transactional
def update_in_transaction(transaction, doc_ref):
    snapshot = doc_ref.get(transaction=transaction)
    transaction.update(doc_ref, {'count': snapshot.get('count') + 1})

doc_ref = db.collection('stats').document('counter')
update_in_transaction(transaction, doc_ref)


# === AUTHENTICATION AVEC PYTHON ===

from firebase_admin import auth

# Créer utilisateur
user = auth.create_user(
    email='user@example.com',
    password='secretPassword',
    display_name='John Doe'
)
print(f'UID: {user.uid}')

# Obtenir utilisateur par UID
user = auth.get_user('user_uid')
print(user.email)

# Obtenir utilisateur par email
user = auth.get_user_by_email('user@example.com')
print(user.uid)

# Mettre à jour utilisateur
auth.update_user(
    'user_uid',
    email='newemail@example.com',
    display_name='New Name'
)

# Supprimer utilisateur
auth.delete_user('user_uid')

# Vérifier token ID
id_token = 'token_from_client'
try:
    decoded_token = auth.verify_id_token(id_token)
    uid = decoded_token['uid']
    print(f'User ID: {uid}')
except:
    print('Token invalide')

# Créer custom token
custom_token = auth.create_custom_token('user_uid')
print(custom_token)


# === STORAGE AVEC PYTHON ===

from firebase_admin import storage

# Obtenir bucket
bucket = storage.bucket()

# Upload fichier
blob = bucket.blob('images/photo.jpg')
blob.upload_from_filename('local_photo.jpg')
print('Fichier uploadé')

# Télécharger fichier
blob = bucket.blob('images/photo.jpg')
blob.download_to_filename('downloaded_photo.jpg')
print('Fichier téléchargé')

# Lister fichiers
blobs = bucket.list_blobs(prefix='images/')
for blob in blobs:
    print(blob.name)

# Obtenir URL signée (temporaire)
from datetime import timedelta
url = blob.generate_signed_url(expiration=timedelta(hours=1))
print(url)

# Supprimer fichier
blob = bucket.blob('images/photo.jpg')
blob.delete()
print('Fichier supprimé')


[OK] INSTALLATION PYTHON SDK - GUIDE COMPLET

# === Installation ===
pip install firebase-admin

# Avec environnement virtuel (recommandé)
python -m venv venv
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate     # Windows
pip install firebase-admin

# Vérifier installation
pip show firebase-admin


[OK] TARIFICATION FIREBASE


Firebase offre un plan gratuit (Spark) et des plans payants (Blaze).


# === PLAN SPARK (GRATUIT) ===

Authentication:
- Illimité

Firestore:
- 50K lectures/jour
- 20K écritures/jour
- 20K suppressions/jour
- 1 GiB stockage
- 10 GiB/mois transfert

Storage:
- 5 GB stockage
- 1 GB/jour téléchargement

Hosting:
- 10 GB stockage
- 360 MB/jour transfert

Functions:
- 125K invocations/mois
- 40K GB-secondes/mois
- 40K CPU-secondes/mois


# === PLAN BLAZE (PAY AS YOU GO) ===

# Inclut quota gratuit Spark + tarifs au-delà:

Firestore:
- $0.06 / 100K lectures
- $0.18 / 100K écritures
- $0.02 / 100K suppressions
- $0.18 / GiB stockage/mois

Storage:
- $0.026 / GB stockage/mois
- $0.12 / GB téléchargement

Functions:
- $0.40 / million invocations
- $0.0000025 / GB-seconde
- $0.0000100 / GHz-seconde

# Définir budget max pour éviter surprises:
# Console Firebase > Usage and billing > Set budget


[OK] EXEMPLES PRATIQUES COMPLETS


# === EXEMPLE 1: TODO APP ===

// Todo App avec Firestore
import { collection, addDoc, getDocs, updateDoc, deleteDoc, doc, query, where, orderBy } from 'firebase/firestore';

// Ajouter todo
async function addTodo(text) {
  const userId = auth.currentUser.uid;
  await addDoc(collection(db, 'todos'), {
    text: text,
    completed: false,
    userId: userId,
    createdAt: new Date()
  });
}

// Obtenir todos
async function getTodos() {
  const userId = auth.currentUser.uid;
  const q = query(
    collection(db, 'todos'),
    where('userId', '==', userId),
    orderBy('createdAt', 'desc')
  );
  
  const snapshot = await getDocs(q);
  return snapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }));
}

// Marquer comme complété
async function toggleTodo(todoId, completed) {
  await updateDoc(doc(db, 'todos', todoId), {
    completed: !completed
  });
}

// Supprimer todo
async function deleteTodo(todoId) {
  await deleteDoc(doc(db, 'todos', todoId));
}

// Écouter en temps réel
import { onSnapshot } from 'firebase/firestore';

function listenTodos(callback) {
  const userId = auth.currentUser.uid;
  const q = query(
    collection(db, 'todos'),
    where('userId', '==', userId)
  );
  
  return onSnapshot(q, (snapshot) => {
    const todos = snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
    callback(todos);
  });
}


# === EXEMPLE 2: CHAT APP ===

// Chat en temps réel
import { collection, addDoc, query, orderBy, limit, onSnapshot } from 'firebase/firestore';

// Envoyer message
async function sendMessage(roomId, text) {
  const user = auth.currentUser;
  await addDoc(collection(db, 'rooms', roomId, 'messages'), {
    text: text,
    userId: user.uid,
    userName: user.displayName,
    userAvatar: user.photoURL,
    createdAt: new Date()
  });
}

// Écouter messages
function listenMessages(roomId, callback) {
  const q = query(
    collection(db, 'rooms', roomId, 'messages'),
    orderBy('createdAt', 'asc'),
    limit(50)
  );
  
  return onSnapshot(q, (snapshot) => {
    const messages = snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
    callback(messages);
  });
}


# === EXEMPLE 3: BLOG AVEC IMAGES ===

// Créer post avec image
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { collection, addDoc } from 'firebase/firestore';

async function createPost(title, content, imageFile) {
  const user = auth.currentUser;
  
  // Upload image
  const imageRef = ref(storage, `posts/${Date.now()}_${imageFile.name}`);
  const snapshot = await uploadBytes(imageRef, imageFile);
  const imageUrl = await getDownloadURL(snapshot.ref);
  
  // Créer post dans Firestore
  const postRef = await addDoc(collection(db, 'posts'), {
    title: title,
    content: content,
    imageUrl: imageUrl,
    authorId: user.uid,
    authorName: user.displayName,
    createdAt: new Date(),
    likes: 0
  });
  
  return postRef.id;
}


// Obtenir posts avec pagination
import { collection, query, orderBy, limit, startAfter, getDocs } from 'firebase/firestore';

let lastDoc = null;

async function getPosts() {
  let q;
  
  if (lastDoc) {
    q = query(
      collection(db, 'posts'),
      orderBy('createdAt', 'desc'),
      startAfter(lastDoc),
      limit(10)
    );
  } else {
    q = query(
      collection(db, 'posts'),
      orderBy('createdAt', 'desc'),
      limit(10)
    );
  }
  
  const snapshot = await getDocs(q);
  lastDoc = snapshot.docs[snapshot.docs.length - 1];
  
  return snapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }));
}


// Liker un post
import { doc, updateDoc, increment } from 'firebase/firestore';

async function likePost(postId) {
  await updateDoc(doc(db, 'posts', postId), {
    likes: increment(1)
  });
}


# === EXEMPLE 4: PROFIL UTILISATEUR COMPLET ===

// Créer profil après inscription
import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
import { doc, setDoc } from 'firebase/firestore';

async function signUpWithProfile(email, password, name, bio, avatarFile) {
  try {
    // 1. Créer compte Auth
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    const user = userCredential.user;
    
    // 2. Upload avatar
    let avatarUrl = null;
    if (avatarFile) {
      const storageRef = ref(storage, `users/${user.uid}/avatar.jpg`);
      const snapshot = await uploadBytes(storageRef, avatarFile);
      avatarUrl = await getDownloadURL(snapshot.ref);
    }
    
    // 3. Mettre à jour profil Auth
    await updateProfile(user, {
      displayName: name,
      photoURL: avatarUrl
    });
    
    // 4. Créer document Firestore
    await setDoc(doc(db, 'users', user.uid), {
      name: name,
      email: email,
      bio: bio,
      avatarUrl: avatarUrl,
      createdAt: new Date(),
      followers: 0,
      following: 0
    });
    
    console.log('Profil créé avec succès');
    return user;
    
  } catch (error) {
    console.error('Erreur création profil:', error);
    throw error;
  }
}


// Mettre à jour profil
async function updateUserProfile(updates) {
  const user = auth.currentUser;
  
  try {
    // Mettre à jour Auth si nom ou avatar changé
    if (updates.name || updates.avatarFile) {
      const authUpdates = {};
      
      if (updates.name) {
        authUpdates.displayName = updates.name;
      }
      
      if (updates.avatarFile) {
        const storageRef = ref(storage, `users/${user.uid}/avatar.jpg`);
        const snapshot = await uploadBytes(storageRef, updates.avatarFile);
        authUpdates.photoURL = await getDownloadURL(snapshot.ref);
        updates.avatarUrl = authUpdates.photoURL;
      }
      
      await updateProfile(user, authUpdates);
      delete updates.avatarFile;
    }
    
    // Mettre à jour Firestore
    await updateDoc(doc(db, 'users', user.uid), {
      ...updates,
      updatedAt: new Date()
    });
    
    console.log('Profil mis à jour');
  } catch (error) {
    console.error('Erreur mise à jour:', error);
    throw error;
  }
}


// Obtenir profil utilisateur
async function getUserProfile(userId) {
  const docSnap = await getDoc(doc(db, 'users', userId));
  
  if (docSnap.exists()) {
    return {
      id: docSnap.id,
      ...docSnap.data()
    };
  }
  
  return null;
}


# === EXEMPLE 5: SYSTÈME DE FOLLOW ===

// Follow utilisateur
async function followUser(targetUserId) {
  const currentUser = auth.currentUser;
  const batch = writeBatch(db);
  
  // Ajouter dans following de l'utilisateur actuel
  const followingRef = doc(db, 'users', currentUser.uid, 'following', targetUserId);
  batch.set(followingRef, {
    userId: targetUserId,
    followedAt: new Date()
  });
  
  // Ajouter dans followers de l'utilisateur cible
  const followerRef = doc(db, 'users', targetUserId, 'followers', currentUser.uid);
  batch.set(followerRef, {
    userId: currentUser.uid,
    followedAt: new Date()
  });
  
  // Incrémenter compteurs
  batch.update(doc(db, 'users', currentUser.uid), {
    following: increment(1)
  });
  batch.update(doc(db, 'users', targetUserId), {
    followers: increment(1)
  });
  
  await batch.commit();
}


// Unfollow utilisateur
async function unfollowUser(targetUserId) {
  const currentUser = auth.currentUser;
  const batch = writeBatch(db);
  
  batch.delete(doc(db, 'users', currentUser.uid, 'following', targetUserId));
  batch.delete(doc(db, 'users', targetUserId, 'followers', currentUser.uid));
  
  batch.update(doc(db, 'users', currentUser.uid), {
    following: increment(-1)
  });
  batch.update(doc(db, 'users', targetUserId), {
    followers: increment(-1)
  });
  
  await batch.commit();
}


// Vérifier si on suit un utilisateur
async function isFollowing(targetUserId) {
  const currentUser = auth.currentUser;
  const docSnap = await getDoc(
    doc(db, 'users', currentUser.uid, 'following', targetUserId)
  );
  return docSnap.exists();
}


# === EXEMPLE 6: NOTIFICATIONS ===

// Créer notification
async function createNotification(userId, type, message, metadata) {
  await addDoc(collection(db, 'users', userId, 'notifications'), {
    type: type,           // 'like', 'comment', 'follow', etc.
    message: message,
    metadata: metadata,
    read: false,
    createdAt: new Date()
  });
}


// Obtenir notifications non lues
async function getUnreadNotifications() {
  const userId = auth.currentUser.uid;
  const q = query(
    collection(db, 'users', userId, 'notifications'),
    where('read', '==', false),
    orderBy('createdAt', 'desc')
  );
  
  const snapshot = await getDocs(q);
  return snapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }));
}


// Marquer notification comme lue
async function markNotificationAsRead(notificationId) {
  const userId = auth.currentUser.uid;
  await updateDoc(
    doc(db, 'users', userId, 'notifications', notificationId),
    { read: true }
  );
}


// Écouter notifications en temps réel
function listenNotifications(callback) {
  const userId = auth.currentUser.uid;
  const q = query(
    collection(db, 'users', userId, 'notifications'),
    where('read', '==', false),
    orderBy('createdAt', 'desc')
  );
  
  return onSnapshot(q, (snapshot) => {
    const notifications = snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
    callback(notifications);
  });
}


[OK] REACT HOOKS PERSONNALISÉS


// Hook pour authentification
import { useState, useEffect } from 'react';
import { onAuthStateChanged } from 'firebase/auth';

export function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });

    return unsubscribe;
  }, []);

  return { user, loading };
}

// Utilisation
function App() {
  const { user, loading } = useAuth();
  
  if (loading) return <div>Chargement...</div>;
  
  return (
    <div>
      {user ? <Dashboard user={user} /> : <Login />}
    </div>
  );
}


// Hook pour collection Firestore
import { useState, useEffect } from 'react';
import { collection, query, onSnapshot } from 'firebase/firestore';

export function useCollection(collectionName, ...queryConstraints) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const q = query(collection(db, collectionName), ...queryConstraints);
    
    const unsubscribe = onSnapshot(q, 
      (snapshot) => {
        const documents = snapshot.docs.map(doc => ({
          id: doc.id,
          ...doc.data()
        }));
        setData(documents);
        setLoading(false);
      },
      (err) => {
        setError(err);
        setLoading(false);
      }
    );

    return unsubscribe;
  }, [collectionName]);

  return { data, loading, error };
}

// Utilisation
import { where, orderBy } from 'firebase/firestore';

function TodoList() {
  const { user } = useAuth();
  const { data: todos, loading } = useCollection(
    'todos',
    where('userId', '==', user?.uid),
    orderBy('createdAt', 'desc')
  );
  
  if (loading) return <div>Chargement...</div>;
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}


// Hook pour document Firestore
import { useState, useEffect } from 'react';
import { doc, onSnapshot } from 'firebase/firestore';

export function useDocument(collectionName, docId) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!docId) {
      setLoading(false);
      return;
    }

    const unsubscribe = onSnapshot(
      doc(db, collectionName, docId),
      (snapshot) => {
        if (snapshot.exists()) {
          setData({ id: snapshot.id, ...snapshot.data() });
        } else {
          setData(null);
        }
        setLoading(false);
      },
      (err) => {
        setError(err);
        setLoading(false);
      }
    );

    return unsubscribe;
  }, [collectionName, docId]);

  return { data, loading, error };
}

// Utilisation
function UserProfile({ userId }) {
  const { data: user, loading } = useDocument('users', userId);
  
  if (loading) return <div>Chargement...</div>;
  if (!user) return <div>Utilisateur introuvable</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}


// Hook pour Storage
import { useState } from 'react';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';

export function useStorage() {
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState(null);
  const [url, setUrl] = useState(null);

  const uploadFile = (file, path) => {
    const storageRef = ref(storage, path);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        setProgress(progress);
      },
      (error) => {
        setError(error);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        setUrl(downloadURL);
        setProgress(0);
      }
    );

    return uploadTask;
  };

  return { uploadFile, progress, error, url };
}

// Utilisation
function FileUpload() {
  const { uploadFile, progress, url } = useStorage();
  const [file, setFile] = useState(null);

  const handleUpload = () => {
    if (file) {
      uploadFile(file, `uploads/${file.name}`);
    }
  };

  return (
    <div>
      <input 
        type="file" 
        onChange={(e) => setFile(e.target.files[0])} 
      />
      <button onClick={handleUpload}>Upload</button>
      {progress > 0 && <p>Progression: {progress}%</p>}
      {url && <p>URL: {url}</p>}
    </div>
  );
}


[OK] PATTERNS AVANCÉS


# === PAGINATION FIRESTORE ===

// Pagination avec curseur
class FirestorePagination {
  constructor(collectionName, pageSize = 10) {
    this.collectionName = collectionName;
    this.pageSize = pageSize;
    this.lastDoc = null;
    this.hasMore = true;
  }

  async getNextPage() {
    if (!this.hasMore) return [];

    let q;
    if (this.lastDoc) {
      q = query(
        collection(db, this.collectionName),
        orderBy('createdAt', 'desc'),
        startAfter(this.lastDoc),
        limit(this.pageSize)
      );
    } else {
      q = query(
        collection(db, this.collectionName),
        orderBy('createdAt', 'desc'),
        limit(this.pageSize)
      );
    }

    const snapshot = await getDocs(q);
    
    if (snapshot.docs.length < this.pageSize) {
      this.hasMore = false;
    }

    if (snapshot.docs.length > 0) {
      this.lastDoc = snapshot.docs[snapshot.docs.length - 1];
    }

    return snapshot.docs.map(doc => ({
      id: doc.id,
      ...doc.data()
    }));
  }

  reset() {
    this.lastDoc = null;
    this.hasMore = true;
  }
}

// Utilisation
const paginator = new FirestorePagination('posts', 10);

async function loadMore() {
  const posts = await paginator.getNextPage();
  console.log('Posts:', posts);
  console.log('Plus de pages:', paginator.hasMore);
}


# === RECHERCHE FULL-TEXT ===

// Firestore ne supporte pas la recherche full-text native
// Solution 1: Utiliser Algolia (recommandé pour production)
// Solution 2: Recherche simple par préfixe

// Créer index pour recherche
async function createSearchIndex(text, docRef) {
  const words = text.toLowerCase().split(' ');
  const searchTerms = [];
  
  words.forEach(word => {
    for (let i = 1; i <= word.length; i++) {
      searchTerms.push(word.substring(0, i));
    }
  });
  
  await updateDoc(docRef, {
    searchTerms: searchTerms
  });
}

// Rechercher
async function search(term) {
  const q = query(
    collection(db, 'posts'),
    where('searchTerms', 'array-contains', term.toLowerCase())
  );
  
  const snapshot = await getDocs(q);
  return snapshot.docs.map(doc => ({
    id: doc.id,
    ...doc.data()
  }));
}


# === COMPTEURS DISTRIBUÉS ===

// Pour compteurs avec haute fréquence de mise à jour
// Évite conflits de transaction

class DistributedCounter {
  constructor(collectionName, docId, numShards = 10) {
    this.collectionName = collectionName;
    this.docId = docId;
    this.numShards = numShards;
  }

  async increment() {
    const shardId = Math.floor(Math.random() * this.numShards);
    const shardRef = doc(db, this.collectionName, this.docId, 'shards', String(shardId));
    
    await updateDoc(shardRef, {
      count: increment(1)
    }).catch(async () => {
      // Créer shard s'il n'existe pas
      await setDoc(shardRef, { count: 1 });
    });
  }

  async getTotal() {
    const shardsSnap = await getDocs(
      collection(db, this.collectionName, this.docId, 'shards')
    );
    
    let total = 0;
    shardsSnap.forEach(doc => {
      total += doc.data().count || 0;
    });
    
    return total;
  }
}

// Utilisation
const viewCounter = new DistributedCounter('stats', 'page-views');
await viewCounter.increment();
const total = await viewCounter.getTotal();


# === CACHE CLIENT ===

// Cache simple pour réduire lectures Firestore
class FirestoreCache {
  constructor(ttl = 5 * 60 * 1000) { // TTL: 5 minutes par défaut
    this.cache = new Map();
    this.ttl = ttl;
  }

  set(key, value) {
    this.cache.set(key, {
      value: value,
      timestamp: Date.now()
    });
  }

  get(key) {
    const item = this.cache.get(key);
    
    if (!item) return null;
    
    if (Date.now() - item.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }
    
    return item.value;
  }

  clear() {
    this.cache.clear();
  }
}

// Utilisation
const cache = new FirestoreCache();

async function getUser(userId) {
  // Vérifier cache
  const cached = cache.get(`user-${userId}`);
  if (cached) {
    console.log('Depuis cache');
    return cached;
  }
  
  // Sinon, lire Firestore
  const docSnap = await getDoc(doc(db, 'users', userId));
  const userData = { id: docSnap.id, ...docSnap.data() };
  
  // Mettre en cache
  cache.set(`user-${userId}`, userData);
  
  return userData;
}


[OK] OPTIMISATION & BONNES PRATIQUES


# === OPTIMISATION FIRESTORE ===

# 1. Minimiser lectures
# [OK] Utiliser cache client
# [OK] Utiliser onSnapshot seulement quand nécessaire
# [OK] Limiter nombre de documents retournés
# [X] Ne pas lire tous les documents à chaque fois

# 2. Index composites
# Créés automatiquement pour requêtes simples
# Pour requêtes complexes, Firebase vous demandera de créer index
# Console > Firestore > Indexes

# 3. Structure dénormalisée
# Dupliquer données plutôt que faire jointures
# Exemple: Stocker nom d'auteur dans post au lieu de seulement ID

// Mauvais (nécessite 2 lectures)
{
  title: "Mon post",
  authorId: "user123"
}

// Bon (1 seule lecture)
{
  title: "Mon post",
  authorId: "user123",
  authorName: "John Doe",
  authorAvatar: "https://..."
}

# 4. Batch writes pour opérations multiples
# Utiliser writeBatch() pour 2+ écritures liées

# 5. Utiliser serverTimestamp()
# Plus fiable que new Date() côté client

# 6. Limiter taille documents
# Max 1MB par document
# Utiliser sous-collections pour grandes quantités de données


# === OPTIMISATION STORAGE ===

# 1. Compresser images avant upload
# Utiliser bibliothèques comme browser-image-compression

import imageCompression from 'browser-image-compression';

async function uploadCompressedImage(file) {
  const options = {
    maxSizeMB: 1,
    maxWidthOrHeight: 1920,
    useWebWorker: true
  };
  
  const compressedFile = await imageCompression(file, options);
  
  const storageRef = ref(storage, `images/${compressedFile.name}`);
  await uploadBytes(storageRef, compressedFile);
}

# 2. Utiliser formats modernes
# WebP pour images (meilleure compression)
# MP4/WebM pour vidéos

# 3. Générer thumbnails côté serveur
# Utiliser Cloud Functions (voir exemple plus haut)

# 4. Mettre en cache avec CDN
# Firebase Storage utilise automatiquement CDN Google


# === SÉCURITÉ ===

# 1. JAMAIS faire confiance au client
# [OK] Valider données dans Security Rules
# [OK] Valider dans Cloud Functions
# [X] Ne jamais valider seulement côté client

# 2. Principe du moindre privilège
# Donner accès minimal nécessaire dans Rules

# 3. Ne pas exposer données sensibles
# [X] Ne jamais stocker: mots de passe, numéros carte bancaire, SSN
# [OK] Utiliser: Firebase Auth, services paiement tiers (Stripe)

# 4. Vérifier auth.uid dans Rules
# Toujours vérifier que request.auth.uid correspond

# 5. Utiliser HTTPS seulement
# Firebase force HTTPS automatiquement

# 6. Activer App Check
# Console > Build > App Check
# Protège contre abus et trafic non autorisé

# 7. Ne pas committer credentials
# [OK] Utiliser .env pour API keys
# [OK] Ajouter .env au .gitignore

# 8. Monitorer usage
# Console > Usage and billing
# Détecter activité anormale

# 9. Définir budget
# Éviter factures surprises


# === PERFORMANCE ===

# 1. Utiliser Firebase SDK modulaire (v9+)
# Plus léger, meilleur tree-shaking

# 2. Lazy loading
# Charger services Firebase uniquement quand nécessaires

// Bon
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';

// Mauvais - importe tout
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

# 3. Offline persistence
# Active automatiquement cache local

import { enableIndexedDbPersistence } from 'firebase/firestore';

try {
  await enableIndexedDbPersistence(db);
} catch (err) {
  if (err.code == 'failed-precondition') {
    // Plusieurs tabs ouverts
  } else if (err.code == 'unimplemented') {
    // Navigateur non supporté
  }
}

# 4. Limiter listeners temps réel
# Unsubscribe quand composant unmount
# Ne pas créer listeners inutiles

# 5. Utiliser pagination
# Ne jamais charger tous les documents d'un coup


[OK] DÉPANNAGE


# === ERREUR: Firebase App not initialized ===
# Cause: App Firebase pas initialisé
# Solution: Vérifier initializeApp() appelé avant utilisation

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const app = initializeApp(firebaseConfig);
const auth = getAuth(app); // Passer app en paramètre


# === ERREUR: Missing or insufficient permissions ===
# Cause: Security Rules bloquent accès
# Solution: Vérifier rules dans Console Firebase

# Tester rules dans Simulateur:
# Console > Firestore/Storage > Rules > Simulator


# === ERREUR: CORS policy ===
# Cause: Requêtes bloquées par CORS (Functions HTTP)
# Solution: Activer CORS dans fonction

const cors = require('cors')({ origin: true });

exports.myFunction = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    res.json({ message: 'Success' });
  });
});


# === ERREUR: Quota exceeded ===
# Cause: Dépassement quotas gratuits
# Solution: 
# 1. Vérifier usage dans Console
# 2. Optimiser requêtes
# 3. Passer au plan Blaze si nécessaire


# === ERREUR: Function timeout ===
# Cause: Fonction prend plus de 60s (défaut)
# Solution: Augmenter timeout

exports.longFunction = functions
  .runWith({ timeoutSeconds: 300 })
  .https.onRequest((req, res) => {
    // ...
  });


# === ERREUR: Auth state not persisting ===
# Cause: Persistence non configurée
# Solution: Configurer persistence

import { setPersistence, browserLocalPersistence } from 'firebase/auth';

await setPersistence(auth, browserLocalPersistence);


# === ERREUR: Storage upload fails ===
# Cause: Fichier trop grand ou rules restrictives
# Solution:
# 1. Vérifier taille fichier
# 2. Vérifier Storage Rules
# 3. Compresser fichier


# === DEBUGGER FIRESTORE ===

# Activer logs détaillés
import { enableLogging } from 'firebase/firestore';
enableLogging(true);

# Dans Console navigateur: onglet Network
# Filtrer par "google.firestore" pour voir requêtes


# === PROBLÈME: Listeners multiples ===
# Cause: Oublier unsubscribe
# Solution: Toujours cleanup

useEffect(() => {
  const unsubscribe = onSnapshot(query, (snapshot) => {
    // ...
  });
  
  return () => unsubscribe(); // Important!
}, []);


[OK] RESSOURCES & DOCUMENTATION


# Documentation officielle
https://firebase.google.com/docs

# Guides spécifiques:
# Authentication:
https://firebase.google.com/docs/auth

# Firestore:
https://firebase.google.com/docs/firestore

# Storage:
https://firebase.google.com/docs/storage

# Functions:
https://firebase.google.com/docs/functions

# Hosting:
https://firebase.google.com/docs/hosting

# SDK References:
# JavaScript:
https://firebase.google.com/docs/reference/js

# Python Admin SDK:
https://firebase.google.com/docs/reference/admin/python

# Console Firebase:
https://console.firebase.google.com

# Stack Overflow:
https://stackoverflow.com/questions/tagged/firebase

# GitHub:
https://github.com/firebase/

# Exemples de code:
https://github.com/firebase/quickstart-js

# Vidéos YouTube:
# Chaîne Firebase:
https://www.youtube.com/firebase

# Cours gratuits:
# Google Codelabs:
https://codelabs.developers.google.com/?cat=Firebase


[OK] CHECKLIST DÉPLOIEMENT PRODUCTION


# === AVANT MISE EN PRODUCTION ===

[ ] Security Rules testées et validées
  - Firestore Rules restrictives
  - Storage Rules restrictives
  - Pas d'accès public non intentionnel

[ ] Authentication configurée
  - Méthodes auth activées
  - Email verification activée si nécessaire
  - Password reset configuré

[ ] Variables d'environnement
  - API keys dans .env
  - .env dans .gitignore
  - Credentials pas dans code

[ ] Monitoring activé
  - Analytics activé
  - Budget défini (plan Blaze)
  - Alertes configurées

[ ] Performance
  - Images compressées
  - Lazy loading
  - Pagination implémentée
  - Cache activé

[ ] Backup
  - Export régulier Firestore (si plan Blaze)
  - Stratégie de récupération

[ ] Tests
  - Tests fonctionnels
  - Tests Security Rules
  - Tests Functions
  - Tests charge (si attendu fort trafic)

[ ] Documentation
  - Structure Firestore documentée
  - API Functions documentée
  - Guide maintenance

[ ] Domaine personnalisé (optionnel)
  - Domaine configuré
  - SSL activé (automatique Firebase)

[ ] App Check (recommandé)
  - App Check activé
  - Protection anti-abus


# === POST-DÉPLOIEMENT ===

[ ] Monitoring usage
  - Vérifier quotas régulièrement
  - Surveiller erreurs dans Console

[ ] Performance
  - Vérifier temps de réponse
  - Optimiser requêtes lentes

[ ] Sécurité
  - Audit Security Rules périodique
  - Vérifier logs inhabituels

[ ] Mises à jour
  - Tenir SDK Firebase à jour
  - Suivre breaking changes


[OK] AIDE-MÉMOIRE COMMANDES


# Firebase CLI
firebase login
firebase init
firebase deploy
firebase deploy --only hosting
firebase deploy --only functions
firebase deploy --only firestore:rules
firebase serve
firebase emulators:start
firebase functions:log
firebase projects:list

# NPM packages
npm install firebase
npm install firebase-admin
npm install -g firebase-tools

# Fire