229 lines
8.8 KiB
Markdown
229 lines
8.8 KiB
Markdown
# Cellar — Collection de vins, bières et spiritueux
|
|
|
|
Application web fullstack pour gérer une collection personnelle de boissons. Interface dark theme moderne, authentification JWT, upload de photos.
|
|
|
|
## Stack technique
|
|
|
|
| Composant | Technologie |
|
|
|-----------|-------------|
|
|
| Frontend | React 18, TypeScript, Vite, Tailwind CSS |
|
|
| Backend | Python 3.12, FastAPI, SQLAlchemy, SQLite |
|
|
| Auth | JWT (python-jose), bcrypt, token versioning |
|
|
| Infra | Podman/Docker, nginx, multi-stage builds |
|
|
|
|
## Prérequis
|
|
|
|
- [Podman](https://podman.io/) (ou Docker) + podman-compose (ou docker-compose)
|
|
|
|
Node.js et Python ne sont pas nécessaires — tout est géré dans les containers.
|
|
|
|
## Démarrage rapide
|
|
|
|
```bash
|
|
# Cloner et configurer
|
|
cp .env.example .env
|
|
# Éditer .env avec un SECRET_KEY aléatoire et un ADMIN_PASSWORD fort
|
|
|
|
# Build et lancement
|
|
podman-compose up --build
|
|
```
|
|
|
|
L'app est accessible sur `http://localhost:8080`.
|
|
|
|
## Configuration (.env)
|
|
|
|
| Variable | Description | Défaut |
|
|
|----------|-------------|--------|
|
|
| `SECRET_KEY` | Clé secrète pour signer les JWT (64+ caractères) | **Requis** — le backend refuse de démarrer sans |
|
|
| `ADMIN_PASSWORD` | Mot de passe du compte admin | Généré aléatoirement si non défini |
|
|
| `CORS_ORIGINS` | Origines autorisées pour CORS | `http://localhost:5173,http://localhost:3000` |
|
|
| `BUILDAH_FORMAT` | Format OCI pour Buildah/Podman | `docker` |
|
|
|
|
**Important :** Générez un `SECRET_KEY` aléatoire :
|
|
```bash
|
|
python3 -c "import secrets; print(secrets.token_hex(32))"
|
|
```
|
|
|
|
## Compte admin
|
|
|
|
Au premier démarrage, un compte `admin` est créé automatiquement.
|
|
Le mot de passe est soit `ADMIN_PASSWORD` (depuis `.env`), soit un mot de passe aléatoire généré automatiquement.
|
|
|
|
## Structure du projet
|
|
|
|
```
|
|
wine/
|
|
├── backend/
|
|
│ ├── main.py # Point d'entrée FastAPI, startup, CORS
|
|
│ ├── auth.py # JWT, hashage mot de passe, get_current_user
|
|
│ ├── database.py # Engine SQLAlchemy, migration, session
|
|
│ ├── models.py # Modèles: User, Drink, Invitation
|
|
│ ├── schemas.py # Schémas Pydantic (validation)
|
|
│ ├── ratelimit.py # Rate limiter in-memory
|
|
│ ├── requirements.txt # Dépendances Python (pinnées)
|
|
│ ├── requirements-dev.txt # Dépendances dev (pytest, httpx)
|
|
│ ├── tests/ # Tests pytest
|
|
│ ├── Dockerfile # Build multi-stage (Python 3.12-slim)
|
|
│ └── routers/
|
|
│ ├── auth.py # Login, register, logout, invitations
|
|
│ ├── drinks.py # CRUD boissons + upload images
|
|
│ └── admin.py # Gestion utilisateurs (admin only)
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── api/ # Client API, types TypeScript
|
|
│ │ ├── auth/ # Contexte auth, ProtectedRoute
|
|
│ │ ├── components/ # DrinkCard, RatingStars, Layout
|
|
│ │ └── pages/ # Home, Login, Register, Admin, etc.
|
|
│ ├── nginx.conf # Config nginx (rate limiting, CSP, proxy)
|
|
│ ├── nginx-main.conf # Override nginx.conf (user root pour podman)
|
|
│ ├── Dockerfile # Build multi-stage (Node → nginx)
|
|
│ └── package.json # Dépendances JS
|
|
├── docker-compose.yml # Orchestration backend + frontend
|
|
├── .env # Variables d'environnement
|
|
└── .env.example # Template pour .env
|
|
```
|
|
|
|
## Dépendances backend (Python)
|
|
|
|
| Package | Version | Usage |
|
|
|---------|---------|-------|
|
|
| fastapi | * | Framework web |
|
|
| uvicorn[standard] | * | Serveur ASGI |
|
|
| sqlalchemy | * | ORM, accès SQLite |
|
|
| pydantic | * | Validation des données |
|
|
| python-multipart | * | Upload de fichiers |
|
|
| Pillow | * | Vérification images uploadées |
|
|
| aiofiles | * | Async file I/O |
|
|
| python-jose[cryptography] | * | JWT encoding/decoding |
|
|
| bcrypt | * | Hashage des mots de passe |
|
|
|
|
## Dépendances frontend (Node)
|
|
|
|
| Package | Version | Usage |
|
|
|---------|---------|-------|
|
|
| react | ^18.3.1 | UI framework |
|
|
| react-dom | ^18.3.1 | React DOM |
|
|
| react-router-dom | ^7.1.1 | Routing SPA |
|
|
| typescript | ^5.6.3 | Typage statique |
|
|
| vite | ^6.0.3 | Bundler |
|
|
| tailwindcss | ^3.4.16 | CSS utility-first |
|
|
| @vitejs/plugin-react | ^4.3.4 | Plugin React pour Vite |
|
|
|
|
## Dev local (sans Docker)
|
|
|
|
Si tu veux développer sans container, tu auras besoin de Python 3.12+ et Node.js 20+.
|
|
|
|
### Backend
|
|
```bash
|
|
cd backend
|
|
python -m venv venv && source venv/bin/activate
|
|
pip install -r requirements.txt
|
|
# Créer un .env ou exporter les variables :
|
|
export SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
|
export ADMIN_PASSWORD=monpassword
|
|
uvicorn main:app --reload --port 8000
|
|
```
|
|
|
|
### Frontend
|
|
```bash
|
|
cd frontend
|
|
npm install
|
|
npm run dev
|
|
# Accessible sur http://localhost:5173
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### Auth
|
|
| Méthode | Route | Description | Auth |
|
|
|---------|-------|-------------|------|
|
|
| POST | `/api/auth/login` | Connexion | Non |
|
|
| POST | `/api/auth/register` | Inscription (invitation requise) | Non |
|
|
| GET | `/api/auth/me` | Profil utilisateur | Oui |
|
|
| POST | `/api/auth/change-password` | Changer mot de passe | Oui |
|
|
| POST | `/api/auth/logout` | Déconnexion (invalide le token) | Oui |
|
|
| POST | `/api/auth/invitations` | Créer une invitation | Admin |
|
|
| GET | `/api/auth/invitations` | Lister les invitations | Admin |
|
|
|
|
### Drinks
|
|
| Méthode | Route | Description | Auth |
|
|
|---------|-------|-------------|------|
|
|
| GET | `/api/drinks` | Lister les boissons | Oui |
|
|
| POST | `/api/drinks` | Ajouter une boisson | Oui |
|
|
| GET | `/api/drinks/{id}` | Détail d'une boisson | Oui |
|
|
| PUT | `/api/drinks/{id}` | Modifier une boisson | Oui |
|
|
| DELETE | `/api/drinks/{id}` | Supprimer une boisson | Oui |
|
|
| POST | `/api/drinks/{id}/upload-image` | Upload d'image | Oui |
|
|
| GET | `/api/drinks/{id}/image?token=...` | Voir l'image | Oui (via query) |
|
|
|
|
### Admin
|
|
| Méthode | Route | Description | Auth |
|
|
|---------|-------|-------------|------|
|
|
| GET | `/api/admin/users` | Lister les utilisateurs | Admin |
|
|
| DELETE | `/api/admin/users/{id}` | Supprimer un utilisateur (+ données) | Admin |
|
|
| POST | `/api/admin/users/{id}/reset-password` | Reset mot de passe d'un user | Admin |
|
|
| POST | `/api/admin/users/{id}/toggle-admin` | Promouvoir/rétrograder admin | Admin |
|
|
| GET | `/api/admin/stats` | Statistiques | Admin |
|
|
|
|
## Sécurité
|
|
|
|
### Authentification
|
|
- JWT avec expiration 24h
|
|
- Token versioning : le logout, le changement de mot de passe et le reset admin invalident tous les tokens existants
|
|
- Tokens stockés en sessionStorage (pas de localStorage)
|
|
- Mot de passe : bcrypt avec cost factor par défaut
|
|
- `SECRET_KEY` vérifié au démarrage — le backend refuse de démarrer si manquant ou placeholder
|
|
- Messages d'erreur génériques (pas d'énumération username/email)
|
|
|
|
### Rate Limiting
|
|
- Login : 5 requêtes/min par IP
|
|
- Register : 3 requêtes/5min par IP
|
|
- Change-password : 5 requêtes/min par IP
|
|
- Basé sur `X-Real-IP` (défini par nginx, non-spoofable), fallback `X-Forwarded-For`
|
|
- Compteur incrémenté avant l'exécution (les échecs comptent aussi)
|
|
- Protection nginx en supplément
|
|
|
|
### Uploads
|
|
- Vérification MIME (magic bytes via Pillow + vérification du format réel)
|
|
- Taille max 10 MB
|
|
- Filenames UUID (pas de path traversal)
|
|
- Endpoint image protégé par authentification (JWT via query param `?token=...` pour compatibilité `<img>`)
|
|
- Vérification que le répertoire de destination est bien dans `uploads/`
|
|
|
|
### Gestion admin des utilisateurs
|
|
- L'admin peut reset le mot de passe de n'importe quel user (invalide ses tokens, min 8 caractères)
|
|
- L'admin peut promouvoir/rétrograder un user en admin
|
|
- L'admin ne peut pas se modifier lui-même (protection auto-destructrice)
|
|
- Suppression d'un user : boissons + images + invitations associées
|
|
|
|
### Headers sécurité (nginx)
|
|
- `X-Content-Type-Options: nosniff`
|
|
- `X-Frame-Options: DENY`
|
|
- `Content-Security-Policy` strict
|
|
- `Referrer-Policy: strict-origin-when-cross-origin`
|
|
- `Permissions-Policy` restrictive
|
|
|
|
### Docker
|
|
- Container non-root (`appuser`)
|
|
- `cap_drop: ALL` + `no-new-privileges`
|
|
- 1 seul worker uvicorn (cohérence SQLite + rate limiter)
|
|
- Limites ressources (512MB backend, 128MB frontend)
|
|
- Logs avec rotation
|
|
- Healthchecks pour orchestration
|
|
|
|
## Fonctionnalités
|
|
|
|
- Gestion de vins, bières et spiritueux avec champs spécifiques par catégorie
|
|
- Notation par étoiles (0-5)
|
|
- Recherche full-text (nom, région, producteur, etc.)
|
|
- Filtres par catégorie et note minimale
|
|
- Upload de photos avec validation
|
|
- Édition des boissons existantes (tous les champs modifiables)
|
|
- Interface responsive dark theme
|
|
- Panel admin : liste, suppression, reset password, toggle admin, statistiques
|
|
- Système d'invitation pour l'inscription (lien copiable avec clipboard fallback)
|
|
|
|
## Licence
|
|
|
|
Projet personnel.
|