222 lines
8.2 KiB
Markdown
222 lines
8.2 KiB
Markdown
# F3 Simple Blog
|
||
|
||
Blog simple avec Fat-Free Framework et SQLite.
|
||
|
||
## Structure
|
||
|
||
```text
|
||
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 ciblées
|
||
│ ├── 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/ # Assets statiques servis directement
|
||
│ └── 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
|
||
└── 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 statiques** — servis directement depuis `public/assets/`, laissés au serveur HTTP / reverse proxy.
|
||
- **Upload** — `Web::receive()` avec contrôle de taille, puis validation MIME/dimensions côté modèle.
|
||
- **Images** — normalisation des médias via `Image`, lecture/écriture via `Base::read()` / `Base::write()`.
|
||
- **Markdown** — `Markdown::instance()->convert()` suivi d'un assainissement DOM strict (liste blanche de balises, attributs et protocoles) et de la résolution locale des images média.
|
||
- **Slugs** — `Web::instance()->slug()`.
|
||
- **Session / CSRF** — `$f3->set('JAR', …)`, `new Session(null, 'CSRF')` pour s'appuyer sur la génération native F3, puis persistance d'un jeton unique en session pour qu'il reste valide entre le GET du formulaire et le POST suivant ; validation centralisée via `verifyCsrf()`.
|
||
- **ORM** — `DB\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`, `dom`
|
||
|
||
### 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
|
||
```
|
||
|
||
### Proxies de confiance
|
||
|
||
L'application ne délègue pas la sécurité des cookies à Apache : elle détermine le schéma de la requête pour marquer la session en `Secure`.
|
||
|
||
La valeur par défaut couvre les cas les plus courants :
|
||
|
||
- `127.0.0.1,::1` — Caddy sur le même hôte ;
|
||
- `172.16.0.0/12` — réseaux Docker IPv4 standard, y compris un Caddy conteneurisé sur le même réseau.
|
||
|
||
Si ton proxy tourne sur un autre sous-réseau, surcharge `app.trusted_proxies` dans `config.local.ini`.
|
||
|
||
```ini
|
||
[globals]
|
||
app.trusted_proxies=127.0.0.1,::1,172.16.0.0/12
|
||
# ou, avec une liste F3 : app.trusted_proxies[]=10.0.0.42
|
||
```
|
||
|
||
Seuls ces proxies sont autorisés à influencer `X-Forwarded-Proto` / `Forwarded`.
|
||
|
||
### À propos du CSRF F3
|
||
|
||
F3 sait générer un jeton via `new Session(null, 'CSRF')`, mais ce jeton est produit à l'instanciation de la requête. Le projet s'en sert donc comme source native F3, puis le persiste explicitement en session pour garder un jeton stable entre l'affichage d'un formulaire et sa soumission.
|
||
|
||
## 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
|
||
```
|
||
|
||
`scripts/install.php` peut être relancé sans danger : il crée les tables si elles n'existent pas.
|
||
|
||
## Déploiement avec Docker
|
||
|
||
```bash
|
||
cp config.local.ini.example config.local.ini
|
||
# édite config.local.ini (app.env=prod, app.timezone, trusted proxies si besoin)
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Le déploiement Docker est cohérent avec le projet :
|
||
|
||
- l'image embarque les extensions réellement utilisées (`pdo_sqlite`, `gd`, `mbstring`, `intl`, `dom`) ;
|
||
- `scripts/install.php` s'exécute au démarrage pour initialiser SQLite de façon idempotente ;
|
||
- seuls les répertoires métier/persistants sont montés (`db/`, `logs/`, `public/uploads/media/`) ;
|
||
- `tmp/` reste dans le conteneur et sert uniquement au runtime F3 (transit d'upload, fichiers temporaires).
|
||
|
||
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 :
|
||
|
||
```bash
|
||
docker compose exec app php scripts/create-admin.php admin
|
||
# mot de passe : 10 caractères minimum
|
||
```
|
||
|
||
## Cache public
|
||
|
||
Les pages publiques restent cacheables pour un visiteur anonyme :
|
||
|
||
- `/` : TTL de 300 s ;
|
||
- `/posts/@slug` : TTL de 3600 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.
|
||
|
||
Les assets statiques (`public/assets/`) sont servis directement ; leur éventuel cache HTTP relève du serveur web ou du reverse proxy.
|
||
|
||
## 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
|
||
|
||
```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
|
||
}
|
||
```
|
||
|
||
Dans la plupart des cas, la valeur par défaut de `app.trusted_proxies` suffit aussi pour ce mode ; surcharge-la seulement si ton réseau Docker utilise un autre sous-réseau.
|
||
|
||
Comme F3 lit aussi `X-Forwarded-For` pour déterminer `IP`, le conteneur applicatif ne doit pas être exposé directement à Internet : Caddy doit rester l'unique point d'entrée public et réécrire les en-têtes `X-Forwarded-*`.
|
||
|
||
Le fichier `Caddyfile.example` fournit en plus un jeu d'en-têtes de sécurité minimal.
|
||
|
||
## Répartition des responsabilités avec Caddy
|
||
|
||
Le projet n'essaie pas de dupliquer ce que le reverse proxy gère naturellement :
|
||
|
||
- **Caddy** — TLS, compression, en-têtes de sécurité, terminaison HTTPS, reverse proxy.
|
||
- **Application / PHP-F3** — sessions, CSRF, cookies, validation métier, rendu HTML et cache HTTP des pages dynamiques.
|
||
|
||
## Données à sauvegarder
|
||
|
||
- `db/` — base SQLite.
|
||
- `public/uploads/media/` — images.
|
||
- `logs/` — optionnel, utile pour diagnostic.
|
||
|
||
## Mise à jour
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
## Logs
|
||
|
||
- PHP : `logs/php-error.log`.
|
||
- Apache / conteneur : `docker compose logs -f app`.
|
||
|
||
## Notes
|
||
|
||
- Les dates sont stockées en UTC (`gmdate`) puis formatées côté affichage avec le fuseau configuré.
|
||
- Le pipeline Markdown n'autorise que les éléments utiles au rendu éditorial et remappe systématiquement les images vers la médiathèque locale.
|