Apparence
Architecture
Comment Klapy est assemblé : du main.ts du frontend jusqu'aux Cloud Functions, en passant par Firestore, l'Auth, le Storage et les intégrations externes.
TL;DR
- Frontend : SPA Vue 3 (Composition API +
<script setup>), build Vite, état Pinia, bindings Firestore réactifs via VueFire. - Backend : 100 % Firebase, région unique
europe-west1. Pas de serveur Node maison. - Persistance : Firestore en lecture/écriture directe depuis le client (sécurisé par
firestore.rules). Les opérations sensibles passent par des Cloud FunctionsonCall. - Externe : Stripe (abonnements, prévu) et Resend (emails transactionnels, prévu) sont appelés uniquement depuis les Cloud Functions, jamais depuis le client.
- Deux environnements Firebase :
klapy-stagingetklapy-718ae(prod,app.klapy.fr).
Vue d'ensemble
Le client n'a jamais de clé secrète. Tout ce qui touche à Stripe, Resend, aux custom claims ou à la suppression de compte passe par une Cloud Function.
Frontend
Point d'entrée
klapy-crm/src/main.ts initialise l'app dans cet ordre :
createApp(App)- Pinia + Pinia Colada (cache async pour les appels Cloud Functions et Stripe)
- VueFire avec le module
VueFireAuth(synchroniseauth.currentUserréactivement) - Vue Router
- Mount sur
#app
ts
// klapy-crm/src/main.ts
const app = createApp(App);
app.use(createPinia());
app.use(PiniaColada);
app.use(VueFire, { firebaseApp, modules: [VueFireAuth()] });
app.use(router);
app.mount('#app');Initialisation Firebase
klapy-crm/src/firebase/config.ts exporte les handles SDK partagés par toute l'app :
| Export | SDK | Usage |
|---|---|---|
firebaseApp | firebase/app | Passé à VueFire et aux autres getX(firebaseApp) |
auth | firebase/auth | Auth state, signIn/signOut, token |
db | firebase/firestore | Toutes les lectures/écritures Firestore |
storage | firebase/storage | Upload logo, fichiers utilisateur |
functions | firebase/functions | httpsCallable(functions, '...'), région europe-west1 |
Région
La région europe-west1 est imposée à la création du handle functions. Tout httpsCallable qui ne passe pas par cet export risque d'appeler la région par défaut (us-central1) et d'échouer en CORS. Toujours importer depuis @/firebase/config.
Routing & layouts
klapy-crm/src/router/index.ts définit trois familles de routes :
| Famille | Layout | requiresAuth | Routes |
|---|---|---|---|
| Auth | AuthLayout | false | /login, /signup, /reset-password |
| Onboarding | aucun | true | /onboarding |
| App | AppLayout | true | /app, /app/contacts, /pipeline, /finance, /tasks, /calendar, /settings |
| Légal | aucun | public | /privacy, /terms, /mentions-legales |
Le garde global authGuard (src/router/guards.ts) est branché via router.beforeEach et redirige vers /login si requiresAuth: true et pas de session. Un router.afterEach réinitialise les erreurs d'auth à chaque navigation pour ne pas afficher un message obsolète.
Stores Pinia
Un store par domaine métier, tous suivent le même pattern (state réactif + getters computed + actions async qui parlent à Firestore via les composables useFirestoreList / useFirestoreCrud) :
| Store | Fichier | Responsabilité |
|---|---|---|
auth | src/stores/auth.ts | Session, profil, custom claim admin, login/signup/reset |
contacts | src/stores/contacts.ts | Contacts, sociétés, log d'activité |
pipeline | src/stores/pipeline.ts | Opportunités, regroupement Kanban |
finance | src/stores/finance.ts | Devis, factures, calcul CA, limites du plan Starter |
tasks | src/stores/tasks.ts | Tâches (priorité, statut, échéance) |
calendar | src/stores/calendar.ts | Évènements (tournage, rdv, livraison) |
dashboard | src/stores/dashboard.ts | Agrégations / KPIs affichés sur le dashboard |
Pinia Colada
Pinia Colada est utilisé pour cacher les résultats des Cloud Functions (RGPD export, opérations Stripe à venir) afin d'éviter les double-clics et les appels redondants. Ne pas l'utiliser pour les listes Firestore : VueFire (useCollection) gère déjà le cache réactif.
Flux de données
Lecture / écriture Firestore
Le client n'écrit jamais sans passer par les rules. Toute lecture/écriture d'un client compromis est bloquée par firestore.rules (voir Règles de sécurité).
Opération sensible (Cloud Function)
Tout ce qui ne peut pas être délégué au client passe par une Cloud Function onCall :
- Mutation des champs protégés (
plan,subscription_status,stripe_customer_id) - Pose / retrait d'un custom claim admin
- Export RGPD (lecture cross-collections + zip)
- Suppression complète d'un compte (Firestore + Storage + Auth)
- Appels Stripe et Resend (clés secrètes côté serveur uniquement)
Voir Cloud Functions pour la liste exhaustive.
Backend (Firebase)
| Service | Rôle | Région |
|---|---|---|
| Authentication | Identité, token JWT, custom claims, blocking functions | europe-west1 |
| Firestore | Base de données principale, isolation par utilisateur | europe-west1 |
| Cloud Functions | Logique sensible, intégrations externes, RGPD | europe-west1 |
| Cloud Storage | Logos, exports utilisateur | europe-west1 |
| Hosting | Sert le build Vite (dist/) | CDN global |
Région unique
Tout doit rester en europe-west1 (RGPD, latence FR). Avant d'ajouter un service Firebase, vérifier qu'il est disponible dans cette région et le configurer explicitement.
Environnements
| Environnement | Projet Firebase | URL | Usage |
|---|---|---|---|
| Staging | klapy-staging | (interne) | Tests, QA, prévalidation |
| Production | klapy-718ae | app.klapy.fr | Live |
Le switch se fait via firebase use <alias>. Les variables VITE_FIREBASE_* côté client viennent de .env.staging / .env.production (jamais commités).
Pièges & gotchas
À ne pas faire
- Importer
getFirestore()ougetFunctions()ailleurs que danssrc/firebase/config.ts. Tu casses la région et le partage de l'instance. - Écrire un champ protégé depuis le client (
plan,subscription_status,stripe_customer_id). Les rules le rejettent et le store affichera une erreur opaque. - Mettre une clé Stripe ou Resend dans un
import.meta.env.VITE_*. Tout ce qui commence parVITE_est exposé dans le bundle. - Oublier de détacher un listener Firestore au démontage d'une vue. Préférer systématiquement
useCollection/useDocumentde VueFire qui le font automatiquement. - Faire plus de 25 docs par requête. Convention interne pour rester pagination-friendly et économiser les reads.
Voir aussi
- Modèles de données : interfaces TypeScript de toutes les entités
- Base de données : arbre des collections Firestore
- Règles de sécurité : isolation utilisateur et champs protégés
- Cloud Functions : liste, signatures, déclencheurs
- Auth & Custom Claims : gestion des admins
- Code source :
klapy-crm/src/main.tsklapy-crm/src/firebase/config.tsklapy-crm/src/router/index.tsklapy-crm/firestore.rulesklapy-crm/functions/src/index.ts