22 KiB
Revue Critique Complète - HabitForge
Date: 2025-12-30
Type: Revue Architecture & Qualité Logicielle
Statut Actuel: Fonctionnel / Prototype
1. Architecture Globale
1.1 Structure du Projet
État Actuel:
HabitForge/
├── backend/ # Monolithique, tout dans un dossier
├── frontend/ # Fichiers statiques (HTML/JS/CSS)
├── data/ # SQLite en local
├── alembic/ # Migrations
└── docker-compose.yml
Problèmes Identifiés:
| # | Problème | Priorité | Impact |
|---|---|---|---|
| 1 | Monolithisme sans séparation claire - Tout le code backend est dans un seul dossier backend/ sans sous-modules (services, repositories, schemas, routes) |
Important | Maintenabilité, Évolutivité |
| 2 | Absence de couche de services - La logique métier est directement dans les routes de main.py |
Important | Testabilité, Réutilisation |
| 3 | Frontend non structuré - app.js contient 2286 lignes, mélange UI, API calls, state management |
Critique | Maintenabilité, DX |
| 4 | Pas de séparation des préoccupations - Models et Pydantic schemas dans le même fichier models.py |
Important | Clean Architecture |
| 5 | Configuration hardcodée - Secrets et URLs en dur dans le code | Critique | Sécurité, Déploiement |
Solutions Recommandées:
backend/
├── api/ # Routes/endpoints
│ ├── v1/
│ │ ├── auth.py
│ │ ├── challenges.py
│ │ ├── health.py
│ │ └── __init__.py
├── core/ # Configuration, sécurité
│ ├── config.py
│ ├── security.py
│ └── deps.py
├── models/ # SQLAlchemy models
├── schemas/ # Pydantic schemas
├── services/ # Business logic
│ ├── auth_service.py
│ ├── challenge_service.py
│ └── health_service.py
├── repositories/ # Data access layer
├── integrations/ # External APIs (Google Fit, Zepp)
└── main.py # Application entry point only
2. Qualité du Code
2.1 Backend
Problèmes:
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | Auto-creation des tables au démarrage (main.py:15) - Contourne Alembic |
Important | Supprimer create_all, utiliser uniquement Alembic |
| 2 | CORS permissif (main.py:25) - allow_origins=["*"] |
Critique | Configurer les origines autorisées via env |
| 3 | Pas de validation des entrées - Les endpoints acceptent tout sans validation stricte | Important | Renforcer les Pydantic models |
| 4 | Pas de gestion d'erreurs centralisée - Try/catch dispersés | Important | Créer un middleware de gestion d'exceptions |
| 5 | Code dupliqué - Logique d'agrégation répétée dans main.py:278-343 |
Nice-to-have | Extraire dans des fonctions utilitaires |
| 6 | Imports cycliques potentiels - import_zepp.py importe depuis backend |
Important | Restructurer les imports |
Exemple de code problématique:
# main.py:15 - À SUPPRIMER
models.Base.metadata.create_all(bind=database.engine)
2.2 Frontend
Problèmes:
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | Fichier monolithique - app.js = 2286 lignes |
Critique | Modulariser (modules ES6 ou framework) |
| 2 | Pas de gestion d'état structurée - State global basique | Important | Adopter un pattern (Redux, Zustand, ou Context API) |
| 3 | Fetch wrapper monkey-patch (app.js:10) - Fragile |
Important | Créer un service API dédié |
| 4 | Pas de TypeScript - Erreurs de type possibles | Important | Migrer vers TypeScript |
| 5 | Code inline dans HTML - Scripts et styles mélangés | Nice-to-have | Séparer complètement |
| 6 | Pas de tests frontend | Important | Ajouter Vitest/Jest + Testing Library |
3. Sécurité
3.1 Problèmes Critiques
| # | Problème | Priorité | Impact | Solution |
|---|---|---|---|---|
| 1 | SECRET_KEY hardcodé (auth.py:7) - "supersecretkey_change_me_in_production" |
Critique | Compromission JWT | Utiliser python-secrets + env var |
| 2 | CORS ouvert (main.py:25) - allow_origins=["*"] |
Critique | CSRF, XSS | Configurer via env |
| 3 | Pas de rate limiting | Critique | DoS, brute force | Ajouter slowapi ou limiter |
| 4 | Pas de validation des mots de passe | Important | Sécurité utilisateur | Ajouter zxcvbn ou similaire |
| 5 | Token JWT sans refresh | Important | UX, Sécurité | Implémenter refresh tokens |
| 6 | Pas de HTTPS enforcement | Critique | MITM | Configurer HTTPS en prod |
| 7 | Client secrets Google exposés (backend/) |
Critique | Compromission OAuth | Exclure du git, utiliser env |
| 8 | Pas de sanitization des inputs | Important | Injection | Valider tous les inputs |
3.2 Solutions Immédiates
Créer backend/core/config.py:
from pydantic_settings import BaseSettings
from secrets import token_urlsafe
class Settings(BaseSettings):
SECRET_KEY: str = token_urlsafe(32)
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
DATABASE_URL: str = "sqlite:///./data/habitforge.db"
CORS_ORIGINS: list[str] = ["http://localhost:8000"]
class Config:
env_file = ".env"
settings = Settings()
Créer .env.example:
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost/habitforge
CORS_ORIGINS=http://localhost:8000,https://yourdomain.com
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
4. Choix Technologiques
4.1 Backend
| Choix | Évaluation | Alternatives | Recommandation |
|---|---|---|---|
| FastAPI | ✅ Excellent - Moderne, rapide, auto-doc | Flask, Django | Garder |
| SQLite | ⚠️ OK pour dev, pas pour prod | PostgreSQL, MySQL | Migrer vers PostgreSQL |
| SQLAlchemy | ✅ Bon choix | Tortoise ORM, SQLModel | Garder |
| Alembic | ✅ Bon choix | - | Garder |
| Argon2 | ✅ Excellent | bcrypt | Garder |
| OAuth2 Password Flow | ⚠️ Déprécié pour SPA | PKCE, OpenID Connect | Migrer vers PKCE |
4.2 Frontend
| Choix | Évaluation | Alternatives | Recommandation |
|---|---|---|---|
| Vanilla JS | ⚠️ Simple mais limite l'évolutivité | React, Vue, Svelte | Migrer vers React/Vue |
| Tailwind CDN | ⚠️ Pas optimisé pour prod | Tailwind build, CSS-in-JS | Build step avec PostCSS |
| Chart.js | ✅ OK pour simple charts | Recharts, ApexCharts | Garder ou migrer |
| SortableJS | ✅ Bon choix | - | Garder |
5. Performance et Scalabilité
5.1 Problèmes Identifiés
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | N+1 queries - read_challenges charge tous les trackings |
Important | Eager loading avec selectinload |
| 2 | Pas de pagination - Tous les challenges chargés | Important | Ajouter pagination |
| 3 | Pas de cache - Chaque requête hit la DB | Important | Ajouter Redis ou cache HTTP |
| 4 | Pas d'indexation - Index manquants sur les colonnes fréquentes | Important | Ajouter des indexes |
| 5 | Frontend sans lazy loading - Tout chargé au démarrage | Nice-to-have | Code splitting |
| 6 | Google Fit sync synchrone - Bloque la réponse | Important | Background task (Celery) |
5.2 Optimisations Recommandées
Indexation de la DB:
# models.py - Ajouter des indexes
class Challenge(Base):
__tablename__ = "challenges"
# ... existing fields ...
__table_args__ = (
Index('idx_user_display_order', 'user_id', 'display_order'),
Index('idx_user_period', 'user_id', 'period_type'),
)
class Tracking(Base):
__tablename__ = "tracking"
# ... existing fields ...
__table_args__ = (
Index('idx_challenge_date', 'challenge_id', 'date'),
Index('idx_date', 'date'),
)
Eager Loading:
from sqlalchemy.orm import selectinload
@app.get("/api/challenges")
def read_challenges(db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user)):
challenges = db.query(models.Challenge)\
.options(selectinload(models.Challenge.trackings))\
.filter(models.Challenge.user_id == current_user.id)\
.order_by(models.Challenge.display_order.asc())\
.all()
return challenges
6. Configuration et Gestion des Environnements
6.1 État Actuel
| Aspect | État | Problème |
|---|---|---|
| Dev | ✅ Fonctionnel | - |
| Staging | ❌ Inexistant | Pas d'environnement intermédiaire |
| Prod | ❌ Non configuré | Pas de config prod séparée |
| Env vars | ❌ Absentes | Tout hardcodé |
| Secrets management | ❌ Aucun | Secrets dans le code |
6.2 Solutions
Structure des environnements:
.env.example # Template
.env.development # Dev (gitignored)
.env.staging # Staging (gitignored)
.env.production # Prod (gitignored)
docker-compose.dev.yml
docker-compose.prod.yml
Docker Compose multi-environnement:
# docker-compose.prod.yml
version: "3.8"
services:
web:
build: .
env_file:
- .env.production
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env.production
volumes:
postgres_data:
7. Observabilité
7.1 État Actuel
| Aspect | État | Problème |
|---|---|---|
| Logs | ⚠️ print() statements |
Pas de logging structuré |
| Métriques | ❌ Aucune | Impossible de monitorer |
| Tracing | ❌ Aucun | Pas de distributed tracing |
| Health checks | ❌ Aucun | Pas de endpoint de santé |
| Error tracking | ❌ Aucun | Erreurs silencieuses |
7.2 Solutions
Logging structuré:
# backend/core/logging.py
import logging
import sys
from pythonjsonlogger import jsonlogger
def setup_logging():
logger = logging.getLogger()
handler = logging.StreamHandler(sys.stdout)
formatter = jsonlogger.JsonFormatter(
'%(asctime)s %(name)s %(levelname)s %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
Health check endpoint:
@app.get("/api/health")
async def health_check(db: Session = Depends(get_db)):
try:
db.execute("SELECT 1")
return {"status": "healthy", "database": "connected"}
except Exception as e:
raise HTTPException(status_code=503, detail="Unhealthy")
Métriques avec Prometheus:
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app, endpoint="/metrics")
8. Tests et Validation
8.1 État Actuel
| Type | État | Couverture |
|---|---|---|
| Tests unitaires | ❌ Aucun | 0% |
| Tests d'intégration | ❌ Aucun | 0% |
| Tests E2E | ❌ Aucun | 0% |
| Tests API | ❌ Aucun | 0% |
| Type checking | ❌ Aucun | N/A |
8.2 Solutions
Structure de tests:
tests/
├── unit/
│ ├── test_auth_service.py
│ ├── test_challenge_service.py
│ └── test_health_service.py
├── integration/
│ ├── test_api_auth.py
│ ├── test_api_challenges.py
│ └── test_api_health.py
├── e2e/
│ └── test_user_flow.py
└── conftest.py
Exemple de test:
# tests/integration/test_api_challenges.py
import pytest
from fastapi.testclient import TestClient
from backend.main import app
client = TestClient(app)
def test_create_challenge(auth_headers):
response = client.post(
"/api/challenges",
json={
"name": "Push-ups",
"period_type": "daily",
"daily_target": 50,
"start_date": "2025-01-01"
},
headers=auth_headers
)
assert response.status_code == 201
assert response.json()["name"] == "Push-ups"
9. Expérience Développeur (DX)
9.1 Problèmes
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | Pas de pre-commit hooks | Important | Ajouter pre-commit (black, ruff, mypy) |
| 2 | Pas de linter/formatter | Important | Configurer Ruff + Black |
| 3 | Pas de Makefile/scripts | Nice-to-have | Créer des scripts de dev |
| 4 | Documentation minimale | Important | Ajouter docstrings + README détaillé |
| 5 | Pas de CI/CD | Critique | Configurer GitHub Actions |
| 6 | Pas de .gitignore complet | Important | Ajouter patterns Python/Node |
9.2 Solutions
.pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [pydantic]
Makefile:
.PHONY: dev test lint format migrate
dev:
uvicorn backend.main:app --reload
test:
pytest tests/ -v --cov=backend
lint:
ruff check backend/
mypy backend/
format:
ruff format backend/
ruff check --fix backend/
migrate:
alembic upgrade head
10. Expérience Utilisateur (UX/UI)
10.1 Problèmes
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | Pas de feedback visuel sur les actions | Important | Ajouter toasts/notifications |
| 2 | Pas de loading states | Important | Spinners pendant les appels API |
| 3 | Pas de gestion d'erreurs utilisateur | Important | Messages d'erreur clairs |
| 4 | Pas de responsive testing | Nice-to-have | Tests sur différents viewports |
| 5 | Accessibilité limitée | Important | ARIA labels, keyboard nav |
10.2 Solutions
Ajouter un système de notifications:
// frontend/utils/notifications.js
export function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
11. Pratiques Professionnelles
11.1 CI/CD
État: ❌ Aucun pipeline CI/CD
Solution - GitHub Actions:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- run: pip install -r requirements.txt
- run: pip install pytest pytest-cov
- run: pytest tests/ --cov=backend --cov-report=xml
- uses: codecov/codecov-action@v3
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install ruff mypy
- run: ruff check backend/
- run: mypy backend/
11.2 Versionnement
État: ⚠️ Pas de versioning sémantique
Solution:
- Adopter Semantic Versioning (v1.0.0)
- Utiliser
bumpversionousemantic-release - Changelog automatique
11.3 Documentation
État: ⚠️ README basique
Solution:
- Ajouter
docs/avec MkDocs ou Sphinx - API docs avec FastAPI auto-docs
- Architecture Decision Records (ADRs)
12. Base de Données
12.1 Problèmes
| # | Problème | Priorité | Solution |
|---|---|---|---|
| 1 | SQLite en prod | Critique | Migrer vers PostgreSQL |
| 2 | Pas de backups | Critique | Script de backup automatique |
| 3 | Pas de migrations rollback | Important | Documenter les rollbacks |
| 4 | Pas de seed data | Nice-to-have | Fixtures pour dev |
12.2 Migration PostgreSQL
Avantages:
- Concurrency supérieure
- Meilleure scalabilité
- Full-text search
- Extensions (PostGIS, pgcrypto)
- Meilleure intégration CI/CD
13. Déploiement
13.1 État Actuel
| Aspect | État | Problème |
|---|---|---|
| Docker | ✅ Présent | Image non optimisée |
| Orchestration | ⚠️ Docker Compose | Pas de K8s pour scale |
| Reverse proxy | ❌ Aucun | Pas de Nginx/Traefik |
| SSL/TLS | ❌ Aucun | Pas de HTTPS |
| Monitoring | ❌ Aucun | Pas de supervision |
13.2 Solutions
Architecture de déploiement recommandée:
┌─────────────┐
│ Nginx │ ← SSL termination, static files
└──────┬──────┘
│
┌──────▼──────┐
│ Gunicorn │ ← WSGI server (multi-worker)
└──────┬──────┘
│
┌──────▼──────┐
│ FastAPI │
└──────┬──────┘
│
┌──────▼──────┐
│ PostgreSQL │
└─────────────┘
14. Résumé des Priorités
Critique (Bloquant pour prod)
- Sécurité: SECRET_KEY hardcodé, CORS ouvert, secrets exposés
- Base de données: SQLite en prod, pas de backups
- CI/CD: Aucun pipeline de tests/déploiement
- HTTPS: Pas de SSL/TLS
- Rate limiting: Protection contre DoS
Important (Qualité professionnelle)
- Architecture: Séparation des responsabilités, couche de services
- Tests: Couverture de tests minimale
- Observabilité: Logging structuré, métriques, health checks
- Configuration: Environment variables, multi-env
- Performance: Pagination, eager loading, cache
- DX: Linters, formatters, pre-commit hooks
Nice-to-have (Améliorations)
- Frontend framework: Migration vers React/Vue
- TypeScript: Typage statique
- Background tasks: Celery pour tâches async
- Monitoring avancé: Sentry, Datadog
- Documentation: MkDocs/Sphinx
15. Feuille de Route Priorisée
Phase 1: Sécurité & Stabilité (1-2 semaines)
gantt
title Phase 1: Sécurité & Stabilité
dateFormat YYYY-MM-DD
section Sécurité
Configurer env vars :a1, 2025-01-01, 2d
Supprimer secrets hardcodés :a2, after a1, 1d
Configurer CORS :a3, after a1, 1d
Ajouter rate limiting :a4, after a1, 2d
section Base de données
Migrer vers PostgreSQL :b1, 2025-01-01, 3d
Configurer backups :b2, after b1, 2d
section Déploiement
Configurer HTTPS :c1, 2025-01-03, 2d
Ajouter Nginx reverse proxy :c2, after c1, 1d
Phase 2: Architecture & Qualité (2-3 semaines)
gantt
title Phase 2: Architecture & Qualité
dateFormat YYYY-MM-DD
section Refactoring
Restructurer backend :a1, 2025-01-08, 4d
Créer couche de services :a2, after a1, 3d
Séparer models/schemas :a3, after a1, 2d
section Frontend
Modulariser app.js :b1, 2025-01-08, 5d
Créer service API :b2, after b1, 2d
section DX
Configurer pre-commit :c1, 2025-01-10, 1d
Ajouter linters/formatters :c2, after c1, 1d
Créer Makefile :c3, after c1, 1d
Phase 3: Tests & CI/CD (2 semaines)
gantt
title Phase 3: Tests & CI/CD
dateFormat YYYY-MM-DD
section Tests
Tests unitaires backend :a1, 2025-01-15, 4d
Tests d'intégration API :a2, after a1, 3d
Tests E2E :a3, after a2, 3d
section CI/CD
Configurer GitHub Actions :b1, 2025-01-17, 2d
Pipeline de tests :b2, after b1, 1d
Pipeline de déploiement :b3, after b2, 2d
Phase 4: Observabilité & Performance (1-2 semaines)
gantt
title Phase 4: Observabilité & Performance
dateFormat YYYY-MM-DD
section Observabilité
Logging structuré :a1, 2025-01-22, 2d
Health checks :a2, after a1, 1d
Métriques Prometheus :a3, after a1, 2d
section Performance
Pagination API :b1, 2025-01-22, 2d
Eager loading :b2, after b1, 1d
Indexation DB :b3, after b1, 1d
Phase 5: UX & Documentation (1-2 semaines)
gantt
title Phase 5: UX & Documentation
dateFormat YYYY-MM-DD
section UX
Notifications/toasts :a1, 2025-01-29, 2d
Loading states :a2, after a1, 1d
Gestion d'erreurs :a3, after a1, 2d
section Documentation
Docstrings API :b1, 2025-01-29, 3d
README amélioré :b2, after b1, 1d
Architecture docs :b3, after b1, 2d
16. Conclusion
HabitForge est un prototype fonctionnel avec une base technique solide (FastAPI, SQLAlchemy, Alembic). Cependant, le projet nécessite des améliorations significatives pour atteindre un niveau professionnel et production-ready.
Points Forts
- ✅ Stack moderne et performant
- ✅ Auth JWT avec Argon2
- ✅ Migrations de base de données
- ✅ Dockerisation
- ✅ UI responsive avec Tailwind
Points Faibles Critiques
- ❌ Sécurité (secrets, CORS, rate limiting)
- ❌ Architecture monolithique sans séparation
- ❌ Absence de tests
- ❌ Pas de CI/CD
- ❌ SQLite en production
- ❌ Frontend non modulaire
Recommandation Finale
Ne PAS déployer en production sans au minimum:
- Corriger les problèmes de sécurité critiques
- Migrer vers PostgreSQL
- Ajouter des tests de base
- Configurer HTTPS
- Mettre en place un pipeline CI/CD
Avec ces améliorations, HabitForge pourrait devenir une application professionnelle robuste et crédible.