F3 Simple Blog
Blog simple construit avec Fat-Free Framework et SQLite.
Le projet vise un blog léger, lisible et facile à déployer, avec un petit back-office d’administration, une médiathèque locale et un rendu Markdown sécurisé.
Fonctionnalités
- listing public des articles avec pagination ;
- page article ;
- authentification admin ;
- création, modification et suppression d’articles ;
- médiathèque locale avec upload, texte alternatif, copie de la syntaxe Markdown et suppression ;
- vignette de carte dérivée de la première image du contenu ;
- rendu Markdown avec images locales
media:...; - stockage des images en JPG/PNG bruts sans transformation.
Structure
project/
├── app/
│ ├── bootstrap.php
│ ├── config.ini
│ ├── helpers.php
│ ├── Controllers/
│ ├── Models/
│ ├── Services/
│ └── Views/
├── db/
├── logs/
├── public/
│ ├── assets/
│ └── uploads/media/
├── docker/
├── scripts/
│ ├── bootstrap.php
│ ├── install.php
│ └── create-admin.php
└── tmp/uploads/
Architecture
Le backend s’appuie sur un petit noyau :
SiteController: pages publiques ;AuthController: connexion / déconnexion ;AdminController: back-office articles et médiathèque ;Controller: rendu, session courante, flash, CSRF ;Post,Media,User: modèlesDB\SQL\Mapper;MarkdownService: compilation et sanitation du contenu Markdown.
Intégration F3
Le projet utilise directement les briques natives du framework :
- routes et aliases dans
app/config.ini; DB\SQL\Mapperpour les tables principales ;Authpour la connexion ;Sessionpour la session ;Templatepour le rendu ;Web::receive()pour la réception des uploads ;Markdownpour le parsing Markdown ;Web::slug()pour les slugs.
Contenu et médiathèque
Les articles contiennent leur texte en Markdown. Les images sont insérées dans le corps du contenu, et la première image rendue dans body_html sert de vignette dans les cartes d’article.
Les images du contenu utilisent la syntaxe :

La médiathèque :
- accepte uniquement JPG et PNG ;
- vérifie que le fichier reçu est bien une image ;
- conserve le fichier tel quel, sans réencodage ;
- stocke en base le nom du fichier, le texte alternatif, la largeur, la hauteur et la date de création.
Contrat Markdown
Le rendu Markdown suit les règles suivantes :
- le HTML brut saisi par l’auteur n’est pas rendu ;
- les liens sont filtrés avant rendu ;
- seules les images locales en
media:...sont rendues ; - les images externes ne sont pas affichées ;
- avec le parseur Markdown de F3, il faut laisser une ligne vide entre deux blocs (titre, liste, citation, image, code) pour un rendu fiable.
Sécurité
Le projet inclut :
- session d’administration ;
- jeton CSRF sur les formulaires ;
- rotation du jeton CSRF après connexion et déconnexion ;
- sanitation du HTML produit à partir du Markdown ;
- validation des uploads image ;
- cookies
httponlyetsamesite=Lax.
Pré-requis
Développement local
- PHP 8.3+
- Composer
- extensions PHP :
pdo_sqlite,mbstring,intl,dom
Déploiement Docker
- Docker
- Docker Compose
Démarrage local
composer install
cp config.local.ini.example config.local.ini
php scripts/install.php
php -S 127.0.0.1:8080 -t public
Puis ouvrir http://127.0.0.1:8080.
Créer un compte admin :
php scripts/create-admin.php admin
Configuration
Le fichier app/config.ini contient les valeurs par défaut. Tu peux les surcharger dans config.local.ini.
Exemple minimal en production :
[globals]
app.env=prod
app.timezone=Europe/Paris
Le paramètre app.env doit être défini à prod sur un déploiement réel.
Déploiement Docker derrière Caddy
Déploiement Docker recommandé :
- Apache sert
public/dans le conteneur ; compose.yamlexpose l’application sur127.0.0.1:8888par défaut ;- Caddy termine TLS et reverse-proxy vers cette cible ;
- SQLite, les logs et les uploads sont montés sur des volumes persistants.
Exemple :
cp config.local.ini.example config.local.ini
# édite config.local.ini : app.env=prod
docker compose up -d --build
Créer un compte admin dans le conteneur :
docker compose exec app php scripts/create-admin.php admin
La commande demande ensuite le mot de passe du compte.
Le Caddyfile.example fournit une base de reverse proxy. En production, il faut exposer publiquement Caddy uniquement. L’application Apache/PHP ne doit pas être accessible directement depuis Internet.
Sauvegarde
Pour sauvegarder le blog, il faut au minimum conserver :
db/app.sqlitepublic/uploads/media/
Les logs peuvent aussi être conservés si tu veux garder l’historique d’erreurs.
Limites assumées
- base SQLite ;
- instance applicative unique ;
- pas de système de migrations automatique ;
- en cas de changement de schéma, une migration manuelle ou une base recréée sera nécessaire.