Structure des dossiers

Cartographie complète du projet Le Labo du Yeti : du code source backend/frontend aux données utilisateur (DB, médias, extensions), en passant par les templates et les artefacts de build. Cette page sert de référence pour comprendre où vit chaque chose et, surtout, ce qu'il ne faut jamais modifier à la main.

Vue d'ensemble. Le projet sépare strictement code (versionné), configuration runtime (env Plesk), données utilisateur (jamais commit) et artefacts générés (regénérables via build). Cette séparation est la condition d'un déploiement SaaS hybride sain — un client = une instance, un domaine, un .env.

Vue d'ensemble de l'arborescence

À la racine du projet, on retrouve trois grandes familles : le code applicatif (backend/, frontend/), les données runtime (content/, uploads/, public/, brouillons/, backups/) et les ressources partagées (templates/, extensions/, documentation/).

cms-ia/
├── backend/                  # API Node 22 + Express (ESM)
│   ├── src/
│   │   ├── routes/           # Définition Express : /api/*
│   │   ├── controllers/      # HTTP handlers (validation, réponses)
│   │   ├── services/         # Logique métier (auth, blog, cocon, ai, etc.)
│   │   ├── middleware/       # requireAuth, requireRole, requireCapability, rate-limit
│   │   ├── utils/            # db-store.js, datetime, paths, logger, etc.
│   │   └── config/           # Chargement .env, paths absolus, constantes
│   ├── scripts/              # Migrations one-shot, seeders, tools CLI
│   └── .env.example          # Modèle de configuration (à committer)
│
├── frontend/                 # SPA React 18 + Vite + Tailwind
│   ├── src/
│   │   ├── pages/            # Routes React (Dashboard, Pages, Blog, Cocon…)
│   │   ├── components/       # UI réutilisables (Sidebar, Topbar, MediaPicker…)
│   │   ├── services/         # Clients HTTP (api.js, ai.js, blog.js…)
│   │   ├── stores/           # Zustand (auth, ui, branding, capabilities)
│   │   ├── lib/              # Helpers (datetime, slugify, validators)
│   │   └── hooks/            # useCapability, useDebounce, etc.
│   └── dist/                 # Build Vite (committé : voir Déploiement)
│
├── content/                  # Données runtime
│   └── cms.db                # SQLite (WAL) — auto-créée au boot
│
├── public/                   # Site statique servi par Plesk (Document Root)
│   ├── index.html            # Sortie du build (page d'accueil)
│   ├── blog/                 # Articles compilés
│   ├── sitemap.xml
│   ├── robots.txt
│   └── 404.html
│
├── uploads/                  # Médiathèque (images, vidéos, PDF)
│
├── brouillons/               # Previews cocon non publiés
│
├── extensions/
│   └── installed/            # Un dossier par extension (manifest + code)
│       ├── hello-world/
│       ├── webp-auto-converter/
│       └── surefeedback-connector/
│
├── templates/                # Bibliothèques HTML système
│   ├── library/              # Templates pages prédéfinis (versionnés)
│   ├── layouts/              # Layouts globaux (header/footer/structure)
│   ├── pages/                # Templates pages utilisateur (par UUID)
│   ├── articles/             # Templates articles blog (par UUID)
│   └── cocon/                # Templates content groups (par UUID)
│
├── backups/                  # Snapshots ZIP (export/restore, safety backups)
│
└── documentation/            # Cette doc HTML (rendue statique)
    ├── tech/
    ├── user/
    └── assets/

Backend (backend/)

Le backend est une application Node 22 LTS en ESM qui expose une API REST sous /api/* et sert également les fichiers statiques en développement. Toute la logique métier vit ici.

Organisation backend/src/

DossierResponsabilitéExemples
routes/Déclaration des endpoints Express, chaînage des middlewaresblog.routes.js, cocon.routes.js, auth.routes.js
controllers/HTTP handlers — validation entrée, appel service, sérialisation réponsepages.controller.js, ai.controller.js
services/Logique métier pure, accès DB, orchestrationblog-service.js, cocon-service.js, ai-service.js
middleware/Auth (JWT + tokens API), RBAC, capabilities, rate-limit, uploads multerrequireAuth, requireRole, requireCapability
utils/Helpers transverses sans état métierdb-store.js (~50 helpers SQLite), datetime.js, paths.js
config/Chargement environnement, constantes, paths absolusenv.js, paths.js

Scripts (backend/scripts/)

Scripts CLI one-shot pour les migrations, l'amorçage et la maintenance. Exécutés manuellement (node backend/scripts/<name>.js), jamais au démarrage normal. Les migrations idempotentes intégrées au boot (ex. migrateAdminsToUsers, migrateLegacyRoles) vivent à l'intérieur de db-store.js.

Configuration : .env.example

Le fichier backend/.env.example documente toutes les variables d'environnement nécessaires (port, URLs, secrets, fournisseurs IA, SMTP). Il est commité. Le .env réel, lui, n'est jamais versionné — en production Plesk, les vars sont injectées via l'interface Node.js du panel, pas via un fichier.

JWT_SECRET unique par client. Chaque instance Plesk doit avoir son propre JWT_SECRET. Ne jamais réutiliser une valeur entre clients : c'est une faille d'identité multi-tenant.

Frontend (frontend/)

Single-Page Application React 18 + Vite + Tailwind CSS. État global via Zustand, communication HTTP via fetch wrappés dans services/api.js.

DossierContenu
pages/Une route React = un fichier. Dashboard.jsx, Pages.jsx, BlogArticles.jsx, CoconGroups.jsx, Settings/*
components/UI réutilisable : Sidebar, Topbar, MediaPicker, BuilderIframe, Modal, Toast
services/Clients HTTP typés par domaine : auth.js, blog.js, cocon.js, ai.js, media.js
stores/Zustand stores : authStore, uiStore, brandingStore, capabilitiesStore.
lib/Pur utilitaire : datetime.js (formats FR centralisés), slugify.js, validators.js.
hooks/Hooks React custom : useCapability, useDebounce, useLocalStorage.

Build (frontend/dist/)

Sortie de vite buildcommittée dans le repo. C'est volontaire : on évite de builder sur Plesk (problèmes RAM connus). À terme, le build sera produit par GitHub Actions sur la branche release ; en attendant, le développeur commit dist/ après chaque modification du frontend.

Données runtime : content/

Contient un unique fichier : cms.db, la base SQLite (mode WAL) qui stocke toutes les données du CMS — utilisateurs, pages, articles, cocon, logs, tokens, extensions, KV stores, usage IA, etc. Voir Base de données pour la liste détaillée des 8 tables et leurs schémas.

Ne JAMAIS modifier content/cms.db à la main. Le fichier est en mode WAL : trois fichiers cohabitent (cms.db, cms.db-wal, cms.db-shm) et un patch manuel via SQLite Browser pendant que l'app tourne corrompra la base. Pour toute modification : passer par l'API, ou arrêter l'app puis exécuter un script backend/scripts/*.js.

Le fichier est auto-créé au boot s'il n'existe pas (zéro-config). Les seeds par défaut (stores auth, global, blog, cocon, permissions…) sont injectés automatiquement par seedMissingStores().

Sortie de build : public/

Le Document Root pointé par Plesk. Ne contient que des fichiers statiques (HTML, CSS, JS, images compilées). C'est la photo du site à un instant T, produite par le service buildSite().

Fichier / dossierRôle
index.htmlPage d'accueil compilée (page racine du site)
<slug>/index.htmlChaque page CMS génère un dossier + index.html pour URLs propres
blog/<slug>/Articles de blog compilés
<parent>/<enfant>/Pages cocon avec slugs composites parent/enfant
sitemap.xmlSitemap regénéré par POST /api/build/sitemap
robots.txtDirectives crawlers
404.htmlPage d'erreur personnalisable (Réglages → Page 404)
uploads/Lien ou copie vers la médiathèque pour exposition publique
Tout dans public/ est régénérable. Lancer un build (POST /api/build) le reconstruit intégralement à partir des données SQLite + templates. Ne jamais éditer un fichier public/*.html à la main : la prochaine génération l'écrase.

Médiathèque : uploads/

Stockage des médias uploadés par les utilisateurs : images (JPG, PNG, WebP, SVG), vidéos, PDF, etc. Une seule arborescence à plat (pas de sous-dossiers par utilisateur) — la table SQLite référence les fichiers par leur nom.

Cette racine est exposée publiquement par le serveur (route statique /uploads/*) et également accessible via l'API /api/media. L'extension webp-auto-converter peut intercepter les uploads pour les convertir en WebP via sharp (hook filter media.before_save).

Ne jamais modifier uploads/* à la main sans aussi mettre à jour la table SQLite associée — sinon les références côté pages/blog/cocon pointeront dans le vide.

Previews cocon : brouillons/

Dossier de travail temporaire pour les previews iframe de pages cocon non encore publiées. Le frontend (Cocon) y dépose le HTML rendu pour le visualiser avant validation. C'est un cache : tout peut être vidé sans perte de données métier (le contenu source vit en SQLite).

Extensions : extensions/installed/

Une extension = un sous-dossier dont le nom est l'ID unique de l'extension. Chaque dossier contient au minimum :

Trois extensions sont livrées de base :

IDRôleVersionnée ?
hello-worldDémo interne : ping endpoint + compteur de buildsOui (référence)
webp-auto-converterConvertit auto les uploads en WebP via sharpNon
surefeedback-connectorInjecte un snippet HTML (SureFeedback, BugHerd…) avant </body>Non

Seul hello-world est commit (rôle de référence pour développeurs). Toutes les autres extensions installées sont ignorées via .gitignore : extensions/installed/* sauf !extensions/installed/hello-world/.

Templates : templates/

Bibliothèques HTML utilisées comme modèles pour générer pages, articles et cocon. Cinq sous-dossiers, deux logiques de versionnement.

DossierContenuCommit ?
templates/library/Templates pages prédéfinis distribués avec le CMS (catalogue d'exemples réutilisables)Oui
templates/layouts/Layouts globaux (header + footer + structure) sélectionnables par l'adminOui
templates/pages/<uuid>/Templates HTML uploadés par utilisateur, un dossier par page (UUID)Non
templates/articles/<uuid>/Templates HTML d'articles blog, un dossier par articleNon
templates/cocon/<uuid>/Templates de content groups (cocon SEO), un dossier par groupeNon

Sauvegardes : backups/

Stockage local des snapshots ZIP générés par :

Listable via GET /api/backup/list. Accès réservé au rôle owner + capability settings_security.

Documentation : documentation/

La doc HTML statique que vous lisez actuellement. Deux espaces :

Fichiers sensibles à ne JAMAIS modifier manuellement

Risque de perte de données. Ces fichiers sont gérés exclusivement par l'application. Toute édition manuelle peut corrompre l'état du CMS.

Matrice : à committer vs à ignorer

Le .gitignore trace une frontière nette entre code source (versionné, partagé entre clients) et données runtime (propres à chaque instance).

CheminStatut GitRaison
backend/src/**CommittéCode source
backend/.env.exampleCommittéModèle de config, sans secrets
frontend/src/**CommittéCode source
frontend/dist/**CommittéBuild CI temporairement absent, on évite de builder sur Plesk
templates/library/**CommittéCatalogue commun à tous les clients
templates/layouts/**CommittéLayouts globaux partagés
extensions/installed/hello-world/**CommittéExtension de référence (démo + tests)
documentation/**CommittéDocumentation du produit
backend/.envIgnoréSecrets propres à l'instance
content/cms.db*IgnoréDonnées utilisateur
uploads/**IgnoréMédias utilisateur
public/**IgnoréSortie de build regénérable
brouillons/**IgnoréCache previews
backups/**IgnoréSnapshots locaux, volumineux
templates/pages|articles|cocon/**IgnoréTemplates utilisateur (par UUID)
extensions/installed/**Ignoré(sauf hello-world) installées via UI
node_modules/**IgnoréRecompilation native obligatoire par OS (better-sqlite3)
Conséquence pratique : un git pull sur Plesk ne touche jamais aux données du client (DB, uploads, templates utilisateur, extensions installées). C'est ce qui rend les mises à jour par tag Git sûres dans le modèle SaaS hybride.

Paths runtime : centralisation

Tous les chemins disque sont centralisés dans backend/src/config/paths.js (ou backend/src/utils/paths.js). Aucune route ou service ne doit utiliser de chemin en dur — passer toujours par les helpers :

import { paths } from '../config/paths.js';

paths.content     // → <root>/content
paths.uploads     // → <root>/uploads
paths.public      // → <root>/public
paths.brouillons  // → <root>/brouillons
paths.templates   // → <root>/templates
paths.extensions  // → <root>/extensions/installed
paths.backups     // → <root>/backups

Ceci évite les bugs de chemin relatif entre dev (Windows) et prod (Linux Plesk), et permet à terme de relocaliser ces dossiers (par ex. uploads/ sur un volume monté) sans refactor.

Résumé

Pour aller plus loin : consultez Architecture technique pour comprendre comment ces dossiers s'articulent au runtime, ou Déploiement pour les implications sur Plesk.