8.8 KiB
8.8 KiB
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 (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
# 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 :
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
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
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_KEYvé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), fallbackX-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: nosniffX-Frame-Options: DENYContent-Security-PolicystrictReferrer-Policy: strict-origin-when-cross-originPermissions-Policyrestrictive
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.