# Slim Blog ![PHP](https://img.shields.io/badge/PHP-8.4.1%2B-777BB4?logo=php&logoColor=white) ![Slim](https://img.shields.io/badge/Slim-4-74a045) ![PHPStan](https://img.shields.io/badge/PHPStan-niveau%208-blue) ![Tests](https://img.shields.io/badge/tests-442%20passing-brightgreen) ![Licence](https://img.shields.io/badge/licence-MIT-green) Blog multi-utilisateurs modulaire développé avec Slim 4. Les domaines `Auth`, `Category`, `Media`, `User` et `Shared` portent une architecture DDD légère, lisible et réutilisable pour d'autres projets (boutique, portfolio…). ## Fonctionnalités - **Articles** — création, édition, suppression avec éditeur WYSIWYG, slugs stables - **Catégories** — filtrage sur la page d'accueil et dans l'interface admin - **Médias** — upload WebP avec déduplication SHA-256 par utilisateur - **Recherche** — full-text FTS5 cumulable avec le filtre catégorie - **Comptes** — trois rôles (`user`, `editor`, `admin`), réinitialisation de mot de passe par email - **RSS** — flux 2.0 des 20 derniers articles (`/rss.xml`) - **Protection brute-force** — verrouillage par IP après trop de tentatives échouées sur la connexion et la réinitialisation de mot de passe ## Stack | Rôle | Librairie | |-------------------|------------------------------------------------------------| | Framework HTTP | [Slim 4](https://www.slimframework.com) | | Base de données | [SQLite](https://sqlite.org) via PDO natif | | Templates | [Twig](https://twig.symfony.com) | | CSS | [Sass](https://sass-lang.com) (7-1, BEM) | | Éditeur WYSIWYG | [Trumbowyg](https://alex-d.github.io/Trumbowyg/) | | Emails | [PHPMailer](https://github.com/PHPMailer/PHPMailer) | | Logging | [Monolog](https://github.com/Seldaek/monolog) | | Sanitisation HTML | [HTMLPurifier](http://htmlpurifier.org) | | Protection CSRF | [Slim CSRF](https://github.com/slimphp/Slim-Csrf) | | Injection de dépendances | [PHP-DI](https://php-di.org) (autowiring) | | Analyse statique | [PHPStan](https://phpstan.org) (niveau 8) | ## Développement **Prérequis :** PHP 8.4.1+ avec `pdo_sqlite`, `intl`, `fileinfo`, `gd` (WebP), `xml` (dom), Composer, Node.js 18+ ```bash git clone https://git.netig.net/netig/slim-blog cd slim-blog composer install npm install && npm run build cp .env.example .env php bin/provision.php php -S localhost:8080 -t public ``` Le projet est encore en développement : l'historique des migrations a été simplifié en une baseline courte. Si vous aviez une ancienne base locale, supprimez `database/app.sqlite` puis reprovisionnez. Pour surveiller les modifications SCSS et recompiler automatiquement en développement : ```bash npm run watch ``` > `npm run watch` est réservé au développement local. En production, le CSS est compilé une fois lors du build Docker. Pour tester l'upload de fichiers, augmenter les limites PHP : ```bash php -S localhost:8080 -t public -d upload_max_filesize=6M -d post_max_size=8M ``` > Si `upload_max_filesize` est trop basse, PHP abandonne l'intégralité du corps POST — fichier > **et** tokens CSRF — ce qui provoque une erreur générique sans message explicite. ## Production **Prérequis :** Docker, Docker Compose ```bash git clone https://git.netig.net/netig/slim-blog cd slim-blog cp .env.example .env # Définir APP_ENV=production, APP_URL, ADMIN_PASSWORD et la configuration SMTP docker compose up -d --build docker compose exec app php bin/provision.php ``` > Le démarrage en production avec `ADMIN_PASSWORD=changeme123` est bloqué intentionnellement. > `--build` est superflu sur une machine vierge mais garantit une image à jour dans tous les cas. **Note sur le `.env` :** `docker-compose.yml` monte le fichier `.env` physiquement dans le conteneur (`- ./.env:/var/www/app/.env:ro`). Ce mount est nécessaire car `phpdotenv` lit un fichier sur le disque via `file_get_contents` — `env_file` seul ne suffit pas. Au premier démarrage, l'entrypoint initialise automatiquement `./data/` sur l'hôte : ``` data/ ├── public/ # assets compilés, index.php — servis par Nginx │ └── media/ # uploads utilisateurs — persistés entre redéploiements ├── database/ # migrations + app.sqlite └── var/ # cache Twig/HTMLPurifier, logs ``` Pour mettre à jour le site après un changement de code : ```bash docker compose build docker compose up -d ``` ### Logs Docker Les erreurs PHP remontent dans `docker compose logs` grâce à `error_log = /dev/stderr` dans `docker/php/php.ini`. Sans ce réglage, les erreurs des workers FPM ne sont pas transmises au process principal et n'apparaissent pas dans les logs Docker. ### Durcissement HTTP `docker/php/php.ini` désactive l'en-tête `X-Powered-By` (`expose_php = Off`) et renomme le cookie de session de `PHPSESSID` en `sid` pour ne pas exposer la stack technique. `docker/nginx/default.conf` ajoute quatre en-têtes de sécurité sur toutes les réponses : | En-tête | Valeur | Protection | |---------|--------|------------| | `X-Frame-Options` | `SAMEORIGIN` | Clickjacking | | `X-Content-Type-Options` | `nosniff` | Sniffing MIME | | `Referrer-Policy` | `strict-origin-when-cross-origin` | Fuite d'URL | | `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` | APIs navigateur | ### Derrière un reverse proxy (Caddy, Nginx…) Nginx écoute sur `127.0.0.1:8888` (câblé dans `docker-compose.yml`). Configurer aussi `TRUSTED_PROXIES` si vous déployez l'application derrière un autre proxy que le Nginx Docker fourni. En stack Docker par défaut, `docker-compose.yml` force `TRUSTED_PROXIES=*` côté conteneur PHP-FPM pour faire confiance au Nginx interne. Exemple de configuration Caddy : ```caddy https://blog.exemple.com { header Strict-Transport-Security max-age=31536000; reverse_proxy localhost:8888 } ``` ## Variables d'environnement | Variable | Description | Exemple | |-------------------|--------------------------------------------------------------------------|----------------------------| | `APP_ENV` | `development` ou `production` | `production` | | `APP_URL` | URL de base (liens emails, flux RSS) — inclure le port en développement | `http://localhost:8080` | | `APP_NAME` | Nom du blog (flux RSS, emails) | `Slim Blog` | | `TIMEZONE` | Fuseau horaire PHP | `Europe/Paris` | | `TRUSTED_PROXIES` | Proxies autorisés à fournir `X-Forwarded-For` / `X-Forwarded-Proto` | `127.0.0.1,::1` ou `*` | | `ADMIN_USERNAME` | Nom d'utilisateur du compte admin | `admin` | | `ADMIN_EMAIL` | Email du compte admin | `admin@example.com` | | `ADMIN_PASSWORD` | Mot de passe admin (obligatoire en production) | *(à changer)* | | `MAIL_HOST` | Serveur SMTP | `smtp.example.com` | | `MAIL_PORT` | Port SMTP (`587` TLS, `465` SSL) | `587` | | `MAIL_USERNAME` | Identifiant SMTP | `noreply@example.com` | | `MAIL_PASSWORD` | Mot de passe SMTP | *(à renseigner)* | | `MAIL_ENCRYPTION` | `tls` ou `ssl` | `tls` | | `MAIL_FROM` | Adresse expéditeur | `noreply@example.com` | | `MAIL_FROM_NAME` | Nom expéditeur | `Slim Blog` | | `UPLOAD_MAX_SIZE` | Taille max upload en octets | `5242880` | ## Documentation - [Guide technique](docs/GUIDE.md) — bases PHP, architecture en domaines, faire évoluer le projet - [Architecture](docs/ARCHITECTURE.md) — référence concise : domaines, schéma BDD, routes, arborescence - [Installation — développement](#développement) — prérequis, lancement local, upload, SCSS - [Installation — production](#production) — Docker, reverse proxy, logs - [Variables d'environnement](#variables-denvironnement) - [Contribuer](CONTRIBUTING.md) — tests, conventions ## Licence Le code source est distribué sous licence [MIT](LICENSE). Le contenu du blog (articles publiés) est soumis à [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.fr). ## Provisioning Le provisionnement (migrations + seed admin) s'exécute explicitement via `php bin/provision.php`. - Développement local : exécuter `php bin/provision.php` apres `cp .env.example .env` - Docker / production : exécuter `docker compose exec app php bin/provision.php` apres le demarrage du conteneur Le runtime HTTP ne provisionne plus automatiquement la base. Si le schéma n'est pas présent, l'application echoue avec un message explicite demandant d'exécuter la commande de provisionnement. Pour repartir d'un schéma frais en développement apres un nettoyage de l'historique des migrations, supprimez d'abord la base SQLite locale puis relancez le provisionnement : `rm -f database/app.sqlite` (ou votre fichier SQLite configure), puis `php bin/provision.php`.