This commit is contained in:
julien
2026-03-30 00:04:18 +02:00
parent fac7f60190
commit db86a7ee3b
2 changed files with 77 additions and 61 deletions

137
README.md
View File

@@ -1,18 +1,19 @@
# F3 Simple Blog
Blog simple construit autour de deux objectifs :
Blog simple construit avec Fat-Free Framework et SQLite.
- **simplicité conceptuelle** ;
- **fidélité à Fat-Free Framework**.
Le projet vise un blog léger, lisible et facile à déployer, avec un petit back-office dadministration, une médiathèque locale et un rendu Markdown sécurisé.
Le rendu visuel est volontairement soigné, mais le backend reste petit et lisible :
## Fonctionnalités
- **3 contrôleurs** : `SiteController`, `AuthController`, `AdminController`
- **1 contrôleur de base** : `Controller`
- **3 modèles** : `Post`, `Media`, `User`
- **1 service dédié** : `MarkdownService`
- **1 bootstrap F3**
- **2 scripts CLI** : installation et création d'admin
- listing public des articles avec pagination ;
- page article ;
- authentification admin ;
- création, modification et suppression darticles ;
- 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
@@ -39,35 +40,67 @@ project/
└── tmp/uploads/
```
## Ce qui est utilisé côté F3
## Architecture
- routes dans `config.ini`
- `DB\SQL\Mapper` pour les trois tables
- `Auth` pour la connexion
- `Session` pour la session et le jeton CSRF de base
- `Template` pour le rendu et le filtre `date_fr`
- `Web::receive()` pour lupload
- `Markdown` pour le rendu des articles
- `Web::slug()` pour les slugs
Le backend sappuie sur un petit noyau :
## Choix de simplicité
- `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èles `DB\SQL\Mapper` ;
- `MarkdownService` : compilation et sanitation du contenu Markdown.
- pas de namespaces ;
- pas de couche repository ;
- pas de système de migrations ;
- pas de container DI ;
- pas de logique de proxy avancée ;
- un seul contrôleur admin pour les articles et la médiathèque ;
- pas dimage de couverture dédiée : la première image du corps sert de vignette de carte.
## Intégration F3
## Choix éditorial simplifié
Le projet utilise directement les briques natives du framework :
Les articles n'ont plus de champ “image de couverture”. Les images vivent uniquement dans le Markdown, et la première image rendue dans `body_html` est réutilisée comme vignette dans les cartes darticle. Cela réduit le nombre de champs, supprime un cas métier entier, et garde un rendu visuel cohérent.
- routes et aliases dans `app/config.ini` ;
- `DB\SQL\Mapper` pour les tables principales ;
- `Auth` pour la connexion ;
- `Session` pour la session ;
- `Template` pour le rendu ;
- `Web::receive()` pour la réception des uploads ;
- `Markdown` pour le parsing Markdown ;
- `Web::slug()` pour les slugs.
## Simplifications supplémentaires
## Contenu et médiathèque
- **zéro HTML brut** : le HTML saisi dans le Markdown est neutralisé avant le passage dans le parseur Markdown ; seuls les éléments générés par Markdown puis validés par le sanitizer sont rendus ;
- **images stockées brutes** : lupload accepte uniquement JPG et PNG, vérifie que le fichier est bien une image, puis le stocke tel quel sans réencodage ni transformation ; la dépendance à GD est supprimée.
Les articles ne possèdent pas de champ “image de couverture”. Les images vivent dans le corps Markdown, et la première image rendue dans `body_html` sert de vignette dans les cartes darticle.
Les images du contenu utilisent la syntaxe :
```md
![Texte alternatif](media:nom-de-fichier.jpg)
```
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 lauteur nest 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 dadministration ;
- 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 `httponly` et `samesite=Lax`.
## Pré-requis
@@ -75,7 +108,7 @@ Les articles n'ont plus de champ “image de couverture”. Les images vivent un
- PHP 8.3+
- Composer
- extensions : `pdo_sqlite`, `mbstring`, `intl`, `dom`
- extensions PHP : `pdo_sqlite`, `mbstring`, `intl`, `dom`
### Déploiement Docker
@@ -91,7 +124,7 @@ php scripts/install.php
php -S 127.0.0.1:8080 -t public
```
Ouvre ensuite `http://127.0.0.1:8080`.
Puis ouvrir `http://127.0.0.1:8080`.
Créer un compte admin :
@@ -111,18 +144,16 @@ app.env=prod
app.timezone=Europe/Paris
```
### Important sur le mode prod
Par défaut, `app/config.ini` laisse `app.env=dev`. Pour un vrai déploiement, il faut donc fournir un `config.local.ini` avec `app.env=prod`, sinon lapplication gardera un comportement de développement.
Le paramètre `app.env` doit être forcé à `prod` sur un déploiement réel.
## Déploiement Docker derrière Caddy
Le projet est cohérent pour un déploiement simple :
Le projet est prévu pour un déploiement simple :
- Apache sert `public/` dans le conteneur ;
- `compose.yaml` expose lapplication sur `127.0.0.1:8888` par défaut ;
- Caddy termine TLS et reverse-proxy vers cette cible ;
- SQLite, les logs et les uploads sont montés sur des volumes hôtes.
- SQLite, les logs et les uploads sont montés sur des volumes persistants.
Exemple :
@@ -132,9 +163,9 @@ cp config.local.ini.example config.local.ini
docker compose up -d --build
```
Le `Caddyfile.example` donne une base de reverse proxy. Le point important est de **nexposer publiquement que Caddy**. Lapplication Apache/PHP ne doit pas être accessible directement depuis Internet.
Le `Caddyfile.example` fournit une base de reverse proxy. En production, il faut exposer publiquement **Caddy uniquement**. Lapplication Apache/PHP ne doit pas être accessible directement depuis Internet.
### Volumes persistants à sauvegarder
## Sauvegarde
Pour sauvegarder le blog, il faut au minimum conserver :
@@ -143,23 +174,9 @@ Pour sauvegarder le blog, il faut au minimum conserver :
Les logs peuvent aussi être conservés si tu veux garder lhistorique derreurs.
## Contrat Markdown
## Limites assumées
Le projet assume un Markdown simple :
- titres, listes, citations, code, liens et images Markdown ;
- pas de HTML brut auteur ;
- les images du contenu utilisent la syntaxe `media:nom-de-fichier.ext` ;
- les images externes ne sont pas rendues ;
- avec le parseur Markdown de F3, laisse une ligne vide entre deux blocs pour un rendu fiable.
## Limite volontaire : pas de migrations
`scripts/install.php` crée les tables si elles nexistent pas. En revanche, il ne gère pas les évolutions de schéma. Si le modèle de données change dans le futur, il faudra soit repartir dune base propre, soit appliquer une migration manuelle.
## Nettoyage de confort appliqué
- suppression d'une méthode utilitaire inutilisée dans `Media` ;
- suppression d'un paramètre mort dans `Post::usesMedia()` ;
- simplification de l'upload temporaire côté admin ;
- détection `secure` des cookies un peu plus tolérante (`HTTPS`, `REQUEST_SCHEME`, `X-Forwarded-Proto`) sans réintroduire une grosse logique proxy.
- 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.