# F3 Simple Blog Blog simple avec Fat-Free Framework. ## Structure ```text project/ ├── config.local.ini # Surcharges locales (gitignored) ├── app/ │ ├── config.ini # Routes et variables F3 │ ├── bootstrap.php # Initialisation (DB, session, cache, erreurs) │ ├── Controllers/ │ ├── Helpers/ # Fonctions utilitaires (App.php, Error.php) │ ├── Models/ # DB\SQL\Mapper (Post, Media, User) │ ├── Services/ # MarkdownService │ └── Views/ ├── db/ │ └── app.sqlite ├── logs/ │ ├── app.log │ └── php-error.log ├── public/ │ ├── assets/ # Sources CSS/JS (servis minifiés via /min/@file) │ └── uploads/ │ └── media/ # Images publiées (JPG conservé, PNG/WebP normalisés en PNG) └── tmp/ ├── cache/ # Cache F3 (pages publiques + assets minifiés) └── uploads/ # Transit Web::receive() — nettoyé après chaque upload ``` ## Philosophie des dossiers runtime Le projet sépare les données persistantes du runtime jetable : - `tmp/` = runtime temporaire, recréable - `db/` = base SQLite persistante - `logs/` = logs persistants - `public/uploads/media/` = médias publiés et persistants Autrement dit, `tmp/` peut être vidé sans perte métier. Les données à sauvegarder restent hors de `tmp/`. ## Fonctionnalités F3 utilisées - **Routage nommé** — `config.ini [routes]`, filtre `alias` dans les templates, `reroute('@route')` dans les contrôleurs - **Cache HTTP + serveur** — TTL déclarés directement dans `[routes]`, `Cache::reset('.url')` à la mutation - **Assets minifiés** — `Web::minify()` via `AssetController` (`GET /min/@file`) - **Upload** — `Web::receive()` avec contrôle de taille, puis validation MIME/dimensions côté modèle - **Images** — normalisation des médias via GD (`JPG` conservé, `PNG/WebP` convertis en `PNG` pour préserver la transparence) - **Markdown** — `Markdown::instance()->convert()` + reconstruction DOM en liste blanche - **Slugs** — `Web::instance()->slug()` - **Session** — `$f3->set('JAR', …)`, hooks `beforeRoute()` sur les contrôleurs protégés, token CSRF créé seulement sur les vues qui contiennent des formulaires - **Logging** — `Log` de F3 avec fallback `file_put_contents` - **ORM** — `DB\SQL\Mapper` : `paginate()`, `copyfrom()`, `cast()`, `find()` ## Prérequis ### Développement local - PHP 8.3+ - Composer - Extensions PHP : `pdo_sqlite`, `dom`, `gd`, `mbstring` ### Déploiement Docker - Docker - Docker Compose ## Configuration Les paramètres par défaut sont dans `app/config.ini`. Pour surcharger localement ou en production : ```bash cp config.local.ini.example config.local.ini ``` Réglages minimums conseillés en production : ```ini [globals] app.env=prod app.timezone=Europe/Paris ``` Le fichier `config.local.ini` sert uniquement aux surcharges d'environnement. Les chemins runtime restent les mêmes partout : - `tmp/cache/` pour le cache F3 et les assets minifiés - `tmp/uploads/` pour les fichiers temporaires d'upload ## Développement local ```bash composer install cp config.local.ini.example config.local.ini php scripts/install.php php -S 127.0.0.1:8080 -t public ``` Ouvre ensuite `http://127.0.0.1:8080`. Créer un compte admin : ```bash php scripts/create-admin.php admin # mot de passe : 10 caractères minimum ``` ## Déploiement avec Docker ```bash cp config.local.ini.example config.local.ini # édite config.local.ini (app.env=prod, app.timezone, etc.) docker compose up -d --build ``` Docker ne monte que les dossiers persistants (`db/`, `logs/`, `public/uploads/media/`) et laisse `tmp/` dans le conteneur pour qu'il reste réellement éphémère. Si `config.local.ini` n'existe pas, le conteneur démarre avec les valeurs par défaut de `app/config.ini`. Le service écoute sur `http://127.0.0.1:8888`. Créer un compte admin : ```bash docker compose exec app php scripts/create-admin.php admin # mot de passe : 10 caractères minimum ``` ## Cache public et navigation Les pages publiques (`/` et `/posts/@slug`) restent cacheables parce que leur rendu n'accède ni à la session, ni au token CSRF. La navigation publique affiche donc un lien statique vers la connexion / l'administration, tandis que les vues d'administration restent session-aware. ## Médias et limites d'upload - Formats acceptés à l'entrée : `JPG`, `PNG`, `WebP` - Taille max du fichier reçu : `10 Mo` - Dimensions max : `8000 × 8000 px` - Limite de surface : `40 mégapixels` - Sortie publiée : `JPG` pour les sources JPEG, `PNG` pour les sources PNG/WebP La médiathèque admin est paginée et le picker dans l'éditeur charge seulement les images les plus récentes pour éviter de charger toute la bibliothèque en mémoire à chaque formulaire. ## Reverse proxy Caddy ### Caddy sur le même hôte ```caddy blog.example.com { encode zstd gzip reverse_proxy 127.0.0.1:8888 } ``` ### Caddy dans Docker Si Caddy tourne aussi dans Docker, place-le sur le même réseau que `app` et cible directement le service : ```caddy blog.example.com { encode zstd gzip reverse_proxy app:80 } ``` ## Données à sauvegarder - `db/` — base SQLite - `public/uploads/media/` — images - `logs/` — optionnel - `tmp/` — non persistant, recréable ## Mise à jour ```bash docker compose up -d --build ``` ## Logs - Applicatifs : `logs/app.log` - PHP : `logs/php-error.log` - Apache (conteneur) : `docker compose logs -f app`