julien f7480eafe7 Style
2026-03-28 20:03:09 +01:00
2026-03-28 17:55:37 +01:00
2026-03-27 14:43:08 +01:00
2026-03-28 17:37:22 +01:00
2026-03-27 14:43:08 +01:00
2026-03-28 20:03:09 +01:00
2026-03-27 14:43:08 +01:00
2026-03-27 22:30:10 +01:00
2026-03-27 22:50:42 +01:00
Doc
2026-03-27 22:40:44 +01:00
2026-03-27 20:14:11 +01:00
2026-03-28 17:55:37 +01:00
2026-03-27 22:56:24 +01:00
2026-03-28 17:55:37 +01:00
2026-03-28 17:37:22 +01:00
Doc
2026-03-28 17:54:04 +01:00

F3 Simple Blog

Blog simple avec Fat-Free Framework et SQLite.

Structure

project/
├── config.local.ini         # Surcharges locales (gitignored)
├── app/
│   ├── config.ini           # Configuration F3 (globals + routes)
│   ├── bootstrap.php        # Initialisation (config, DB, session, erreurs)
│   ├── Controllers/
│   ├── Helpers/             # Fonctions utilitaires
│   ├── Models/              # DB\SQL\Mapper (Post, Media, User)
│   ├── Services/            # MarkdownService
│   └── Views/
├── db/
│   └── app.sqlite           # Base SQLite persistante
├── logs/
│   └── php-error.log        # Log PHP configuré au runtime
├── public/
│   ├── assets/              # Sources CSS/JS servies via /min/@file
│   └── uploads/
│       └── media/           # Images publiées (JPG conservé, PNG/WebP normalisés en PNG)
├── scripts/
│   ├── bootstrap.php        # Autoload + bootstrap partagé par les scripts CLI
│   ├── install.php          # Initialisation idempotente de la base
│   └── create-admin.php     # Création d'un compte admin en CLI
└── tmp/                     # Runtime temporaire, recréable sans perte métier
    ├── cache/               # Cache F3 + assets minifiés
    └── uploads/             # Transit Web::receive(), nettoyé après chaque upload

Fonctionnalités F3 utilisées

  • Routage nomméconfig.ini [routes], filtre alias dans les templates, reroute('@route') dans les contrôleurs.
  • Cache HTTP — TTL par contrôleur via expire(), forcé à 0 quand un utilisateur est connecté.
  • Assets minifiésWeb::minify() via AssetController (GET /min/@file).
  • UploadWeb::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).
  • MarkdownMarkdown::instance()->convert() + strip_tags et résolution des images média.
  • SlugsWeb::instance()->slug().
  • Session / CSRF$f3->set('JAR', …), hooks beforeRoute() sur les contrôleurs protégés, jeton @CSRF recopié en session au rendu et vérifié au POST suivant.
  • ORMDB\SQL\Mapper : paginate(), copyfrom(), cast(), find().
  • Erreurs — gestion personnalisée en production via ONERROR + fallback HTML minimal sur erreur fatale.

Prérequis

Développement local

  • PHP 8.3+
  • Composer
  • Extensions PHP : pdo_sqlite, gd, mbstring, intl

Déploiement Docker

  • Docker
  • Docker Compose

Configuration

Les paramètres par défaut sont dans app/config.ini. Pour surcharger localement ou en production :

cp config.local.ini.example config.local.ini

Réglages minimums conseillés en production :

[globals]
app.env=prod
app.timezone=Europe/Paris

Développement local

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 :

php scripts/create-admin.php admin
# mot de passe : 10 caractères minimum

scripts/install.php peut être relancé sans danger : il crée les tables si elles n'existent pas.

Déploiement avec Docker

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 éphémère.

Le fichier config.local.ini est monté en lecture seule. Si le fichier hôte n'existe pas, Docker peut créer un répertoire à la place ; l'entrypoint le supprime et l'application retombe sur les valeurs par défaut de app/config.ini.

Le service écoute sur http://127.0.0.1:8888.

Créer un compte admin :

docker compose exec app php scripts/create-admin.php admin
# mot de passe : 10 caractères minimum

Cache public

Les pages publiques sont cacheables pour un visiteur anonyme :

  • / : TTL de 300 s.
  • /posts/@slug : TTL de 3600 s.
  • /min/app.css et /min/app.js : TTL de 86400 s.

Quand un utilisateur est connecté, le rendu est forcé en non-cacheable avec expire(0) pour ne pas servir du contenu admin via un cache intermédiaire.

Le projet ne fait pas d'invalidation explicite du cache lors des mutations d'articles : la fraîcheur dépend des TTL ci-dessus.

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, 40 mégapixels.
  • Sortie publiée : JPG pour les sources JPEG, PNG pour les sources PNG/WebP.
  • Texte alternatif initial dérivé du nom de fichier d'origine.

La médiathèque admin est paginée et le picker dans l'éditeur charge seulement les 60 images les plus récentes.

Reverse proxy Caddy

Caddy sur le même hôte

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 :

blog.example.com {
    encode zstd gzip
    reverse_proxy app:80
}

Le fichier Caddyfile.example fournit en plus un jeu d'en-têtes de sécurité minimal.

Données à sauvegarder

  • db/ — base SQLite.
  • public/uploads/media/ — images.
  • logs/ — optionnel, utile pour diagnostic.

Mise à jour

docker compose up -d --build

Logs

  • PHP : logs/php-error.log.
  • Apache / conteneur : docker compose logs -f app.

Limitations connues

  • CSRF et multi-onglets — F3 expose un jeton CSRF unique via @CSRF. Le projet le recopie en session au rendu et le vérifie au POST suivant. Si l'admin ouvre deux onglets, le token du premier est écrasé par celui du second. La soumission du premier formulaire échouera avec « Jeton CSRF invalide ». Solution de contournement : travailler dans un seul onglet à la fois. F3 ne fournit pas de mécanisme de pool de tokens.

Notes

  • Les dates sont stockées en UTC (gmdate) puis formatées côté affichage avec le fuseau configuré.
Description
No description provided
Readme 301 KiB
Languages
PHP 48.4%
HTML 20.6%
CSS 19.7%
JavaScript 7.1%
Dockerfile 2.6%
Other 1.6%