Skip to content

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 Functions onCall.
  • 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-staging et klapy-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 :

  1. createApp(App)
  2. Pinia + Pinia Colada (cache async pour les appels Cloud Functions et Stripe)
  3. VueFire avec le module VueFireAuth (synchronise auth.currentUser réactivement)
  4. Vue Router
  5. 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 :

ExportSDKUsage
firebaseAppfirebase/appPassé à VueFire et aux autres getX(firebaseApp)
authfirebase/authAuth state, signIn/signOut, token
dbfirebase/firestoreToutes les lectures/écritures Firestore
storagefirebase/storageUpload logo, fichiers utilisateur
functionsfirebase/functionshttpsCallable(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 :

FamilleLayoutrequiresAuthRoutes
AuthAuthLayoutfalse/login, /signup, /reset-password
Onboardingaucuntrue/onboarding
AppAppLayouttrue/app, /app/contacts, /pipeline, /finance, /tasks, /calendar, /settings
Légalaucunpublic/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) :

StoreFichierResponsabilité
authsrc/stores/auth.tsSession, profil, custom claim admin, login/signup/reset
contactssrc/stores/contacts.tsContacts, sociétés, log d'activité
pipelinesrc/stores/pipeline.tsOpportunités, regroupement Kanban
financesrc/stores/finance.tsDevis, factures, calcul CA, limites du plan Starter
taskssrc/stores/tasks.tsTâches (priorité, statut, échéance)
calendarsrc/stores/calendar.tsÉvènements (tournage, rdv, livraison)
dashboardsrc/stores/dashboard.tsAgré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)

ServiceRôleRégion
AuthenticationIdentité, token JWT, custom claims, blocking functionseurope-west1
FirestoreBase de données principale, isolation par utilisateureurope-west1
Cloud FunctionsLogique sensible, intégrations externes, RGPDeurope-west1
Cloud StorageLogos, exports utilisateureurope-west1
HostingSert 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

EnvironnementProjet FirebaseURLUsage
Stagingklapy-staging(interne)Tests, QA, prévalidation
Productionklapy-718aeapp.klapy.frLive

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() ou getFunctions() ailleurs que dans src/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 par VITE_ est exposé dans le bundle.
  • Oublier de détacher un listener Firestore au démontage d'une vue. Préférer systématiquement useCollection / useDocument de 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