Add initial project
This commit is contained in:
@@ -0,0 +1,515 @@
|
||||
# Plan de Refonte : Collecte des Données Brutes Health Connect
|
||||
|
||||
## Objectif
|
||||
|
||||
Refactoriser l'ensemble du système HabitForge pour :
|
||||
1. **Collecter toutes les données brutes** de Health Connect (chaque record individuel avec son timestamp précis)
|
||||
2. **Stocker les données brutes** dans la base de données
|
||||
3. **Interpréter les données côté web** (backend/frontend) selon les besoins d'affichage
|
||||
|
||||
## Problème Actuel
|
||||
|
||||
### Architecture Actuelle (Problématique)
|
||||
```
|
||||
Health Connect API
|
||||
↓
|
||||
Android App (readDailyMetrics) ──→ AGRÈGE par jour ──→ DailyMetrics payload
|
||||
↓
|
||||
Backend /api/health-connect/sync ──→ Stocke dans health_connect_daily_metrics (1 row/jour)
|
||||
↓
|
||||
Frontend ──→ Affiche données agrégées
|
||||
```
|
||||
|
||||
### Symptômes Observés
|
||||
- La tuile "Steps" affiche 0 pour aujourd'hui (l'agrégation ne capture pas les nouveaux records)
|
||||
- L'historique des steps (Nov 29 - Jan 8) montre des bonnes valeurs (agrégation passée)
|
||||
- Les données brutes affichées s'arrêtent au 29 décembre (décalage avec l'agrégation)
|
||||
- La base de données ne contient que des agrégations, pas les records individuels
|
||||
|
||||
## Nouvelle Architecture Proposée
|
||||
|
||||
```
|
||||
Health Connect API
|
||||
↓
|
||||
Android App (readRawRecords) ──→ Collecte TOUS les records bruts avec timestamps
|
||||
↓
|
||||
Backend /api/health-connect/sync-raw ──→ Stocke dans NOUVELLES tables de données brutes
|
||||
↓
|
||||
Backend (Aggregation Service) ──→ Calcule les agrégations à la demande ou en cache
|
||||
↓
|
||||
Frontend ──→ Affiche données selon la granularité demandée
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 : Nouveaux Modèles de Base de Données
|
||||
|
||||
### 1.1 Nouvelles Tables pour Données Brutes
|
||||
|
||||
```python
|
||||
# backend/models/health_connect_raw.py
|
||||
|
||||
class HealthConnectStepsRecord(Base):
|
||||
"""Individual step records from Health Connect"""
|
||||
__tablename__ = "hc_raw_steps"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
# Record identifiers
|
||||
record_id = Column(String(100), unique=True) # Health Connect UID
|
||||
data_source = Column(String(200)) # Package name
|
||||
data_source_name = Column(String(100))
|
||||
|
||||
# Timestamps
|
||||
start_time = Column(DateTime, index=True)
|
||||
end_time = Column(DateTime)
|
||||
recorded_at = Column(DateTime) # When it was recorded on device
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Data
|
||||
count = Column(Integer)
|
||||
|
||||
# Metadata
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectHeartRateRecord(Base):
|
||||
"""Individual heart rate records from Health Connect"""
|
||||
__tablename__ = "hc_raw_heart_rate"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
start_time = Column(DateTime, index=True)
|
||||
end_time = Column(DateTime)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectHeartRateSample(Base):
|
||||
"""Individual heart rate samples (part of a HeartRateRecord)"""
|
||||
__tablename__ = "hc_raw_heart_rate_samples"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
record_id = Column(Integer, ForeignKey("hc_raw_heart_rate.id"), index=True)
|
||||
|
||||
time = Column(DateTime)
|
||||
bpm = Column(Integer)
|
||||
|
||||
|
||||
class HealthConnectSleepSession(Base):
|
||||
"""Sleep sessions from Health Connect"""
|
||||
__tablename__ = "hc_raw_sleep_sessions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
start_time = Column(DateTime, index=True)
|
||||
end_time = Column(DateTime)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
title = Column(String(200), nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectSleepStage(Base):
|
||||
"""Sleep stages within a session"""
|
||||
__tablename__ = "hc_raw_sleep_stages"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
session_id = Column(Integer, ForeignKey("hc_raw_sleep_sessions.id"), index=True)
|
||||
|
||||
start_time = Column(DateTime)
|
||||
end_time = Column(DateTime)
|
||||
stage_type = Column(Integer) # AWAKE, LIGHT, DEEP, REM, etc.
|
||||
stage_name = Column(String(50))
|
||||
|
||||
|
||||
class HealthConnectDistanceRecord(Base):
|
||||
"""Distance records from Health Connect"""
|
||||
__tablename__ = "hc_raw_distance"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
start_time = Column(DateTime, index=True)
|
||||
end_time = Column(DateTime)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
distance_meters = Column(Float)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectCaloriesRecord(Base):
|
||||
"""Calories burned records from Health Connect"""
|
||||
__tablename__ = "hc_raw_calories"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
start_time = Column(DateTime, index=True)
|
||||
end_time = Column(DateTime)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
calories = Column(Float)
|
||||
record_type = Column(String(50)) # "active" or "total"
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectOxygenSaturationRecord(Base):
|
||||
"""SpO2 records from Health Connect"""
|
||||
__tablename__ = "hc_raw_spo2"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
time = Column(DateTime, index=True)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
percentage = Column(Float)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectWeightRecord(Base):
|
||||
"""Weight records from Health Connect"""
|
||||
__tablename__ = "hc_raw_weight"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
time = Column(DateTime, index=True)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
weight_kg = Column(Float)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
|
||||
|
||||
class HealthConnectRestingHeartRateRecord(Base):
|
||||
"""Resting heart rate records"""
|
||||
__tablename__ = "hc_raw_resting_hr"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), index=True)
|
||||
|
||||
record_id = Column(String(100), unique=True)
|
||||
data_source = Column(String(200))
|
||||
|
||||
time = Column(DateTime, index=True)
|
||||
synced_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
bpm = Column(Integer)
|
||||
|
||||
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 : Modifications Android
|
||||
|
||||
### 2.1 Nouvelles Méthodes de Lecture des Données Brutes
|
||||
|
||||
```kotlin
|
||||
// HealthConnectManager.kt - Nouvelles méthodes
|
||||
|
||||
/**
|
||||
* Read all raw step records with full metadata (not aggregated)
|
||||
*/
|
||||
suspend fun readRawStepsRecords(startDate: LocalDate, endDate: LocalDate): List<RawStepsRecord>
|
||||
|
||||
/**
|
||||
* Read all raw heart rate records with samples
|
||||
*/
|
||||
suspend fun readRawHeartRateRecords(startDate: LocalDate, endDate: LocalDate): List<RawHeartRateRecord>
|
||||
|
||||
/**
|
||||
* Read all raw sleep sessions with stages
|
||||
*/
|
||||
suspend fun readRawSleepSessions(startDate: LocalDate, endDate: LocalDate): List<RawSleepSession>
|
||||
|
||||
// ... etc pour chaque type de donnée
|
||||
```
|
||||
|
||||
### 2.2 Nouvelles Classes de Données
|
||||
|
||||
```kotlin
|
||||
// RawDataModels.kt
|
||||
|
||||
data class RawStepsRecord(
|
||||
val recordId: String, // metadata.id
|
||||
val dataSource: String, // metadata.dataOrigin.packageName
|
||||
val startTime: String, // ISO8601
|
||||
val endTime: String,
|
||||
val count: Long
|
||||
)
|
||||
|
||||
data class RawHeartRateRecord(
|
||||
val recordId: String,
|
||||
val dataSource: String,
|
||||
val startTime: String,
|
||||
val endTime: String,
|
||||
val samples: List<HeartRateSample>
|
||||
)
|
||||
|
||||
data class HeartRateSample(
|
||||
val time: String,
|
||||
val bpm: Long
|
||||
)
|
||||
|
||||
data class RawSleepSession(
|
||||
val recordId: String,
|
||||
val dataSource: String,
|
||||
val startTime: String,
|
||||
val endTime: String,
|
||||
val title: String?,
|
||||
val notes: String?,
|
||||
val stages: List<SleepStage>
|
||||
)
|
||||
|
||||
data class SleepStage(
|
||||
val startTime: String,
|
||||
val endTime: String,
|
||||
val stageType: Int,
|
||||
val stageName: String
|
||||
)
|
||||
|
||||
// ... etc
|
||||
```
|
||||
|
||||
### 2.3 Nouveau Payload de Synchronisation
|
||||
|
||||
```kotlin
|
||||
// ApiModels.kt
|
||||
|
||||
data class RawDataSyncRequest(
|
||||
val device_id: String,
|
||||
val sync_timestamp: String,
|
||||
val steps_records: List<RawStepsPayload>,
|
||||
val heart_rate_records: List<RawHeartRatePayload>,
|
||||
val sleep_sessions: List<RawSleepPayload>,
|
||||
val distance_records: List<RawDistancePayload>,
|
||||
val calories_records: List<RawCaloriesPayload>,
|
||||
val spo2_records: List<RawSpo2Payload>,
|
||||
val weight_records: List<RawWeightPayload>,
|
||||
val resting_hr_records: List<RawRestingHRPayload>,
|
||||
val exercise_sessions: List<ExerciseSessionPayload>
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 : Nouveau Endpoint Backend
|
||||
|
||||
### 3.1 Endpoint /api/health-connect/sync-raw
|
||||
|
||||
```python
|
||||
# backend/api/v1/health_connect_raw.py
|
||||
|
||||
@router.post("/sync-raw", response_model=RawSyncResponse)
|
||||
async def receive_raw_health_data(
|
||||
request: RawDataSyncRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(auth.get_current_user)
|
||||
):
|
||||
"""
|
||||
Receive raw health records from Android companion app.
|
||||
Each record is stored individually with its original timestamp.
|
||||
"""
|
||||
# Upsert each record type using record_id as unique key
|
||||
# ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 : Service d'Agrégation Backend
|
||||
|
||||
### 4.1 AggregationService
|
||||
|
||||
```python
|
||||
# backend/services/aggregation_service.py
|
||||
|
||||
class AggregationService:
|
||||
"""
|
||||
Computes aggregated metrics from raw data on-demand.
|
||||
Can optionally cache results in the existing daily_health_metrics tables.
|
||||
"""
|
||||
|
||||
def get_daily_steps(self, user_id: int, date: date) -> int:
|
||||
"""Sum all step records for a given day"""
|
||||
|
||||
def get_daily_heart_rate_stats(self, user_id: int, date: date) -> HeartRateStats:
|
||||
"""Calculate min, max, avg HR for a day from all samples"""
|
||||
|
||||
def get_weekly_stats(self, user_id: int, ref_date: date) -> WeeklyStats:
|
||||
"""Aggregate stats for a week"""
|
||||
|
||||
def get_monthly_stats(self, user_id: int, ref_date: date) -> MonthlyStats:
|
||||
"""Aggregate stats for a month"""
|
||||
|
||||
def get_metric_history(
|
||||
self,
|
||||
user_id: int,
|
||||
metric_type: str,
|
||||
start_date: date,
|
||||
end_date: date,
|
||||
granularity: str = "day" # "raw", "hour", "day", "week", "month"
|
||||
) -> List[MetricDataPoint]:
|
||||
"""Get historical data at specified granularity"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 : Modifications Frontend
|
||||
|
||||
### 5.1 API Client Updates
|
||||
|
||||
```javascript
|
||||
// frontend/js/api.js
|
||||
|
||||
async function fetchRawMetricData(metricType, startDate, endDate, granularity = 'day') {
|
||||
// Appelle le nouveau endpoint avec paramètres de granularité
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Composants de Visualisation
|
||||
|
||||
Les composants existants peuvent continuer à fonctionner car le backend retournera des données agrégées par défaut.
|
||||
L'option de voir les données brutes sera ajoutée pour les vues détaillées.
|
||||
|
||||
---
|
||||
|
||||
## Plan d'Implémentation - **ÉTAT D'AVANCEMENT**
|
||||
|
||||
### ✅ Étape 1 : Database (Backend) - COMPLÉTÉ
|
||||
- [x] Créer le fichier `backend/models/health_connect_raw.py` (15 tables brutes)
|
||||
- [x] Ajouter migration Alembic `f5g6h7i8j9k0_add_health_connect_raw_tables.py`
|
||||
- [x] Mettre à jour `backend/models/__init__.py` pour inclure les nouveaux modèles
|
||||
|
||||
### ✅ Étape 2 : Backend API - COMPLÉTÉ
|
||||
- [x] Créer `backend/api/v1/health_connect_raw.py` avec endpoint `/sync-raw`
|
||||
- [x] Créer les schémas Pydantic pour les 15 types de payloads bruts
|
||||
- [x] Ajouter endpoints de lecture des données brutes (`/raw/steps`, `/raw/heart-rate`, `/raw/stats`)
|
||||
- [x] Ajouter la route au router principal `backend/main.py`
|
||||
|
||||
### ✅ Étape 3 : Backend Aggregation Service - COMPLÉTÉ
|
||||
- [x] Créer `backend/services/aggregation_service.py`
|
||||
- [x] Implémenter les méthodes d'agrégation quotidiennes
|
||||
- [x] Implémenter `get_weekly_summary` et `get_monthly_summary`
|
||||
- [x] Implémenter `get_metric_history` pour les graphiques
|
||||
|
||||
### ✅ Étape 4 : Android - Reading Raw Data - COMPLÉTÉ
|
||||
- [x] Créer `RawDataModels.kt` avec toutes les classes de données brutes
|
||||
- [x] Créer `HealthConnectRawReader.kt` avec les méthodes `readRaw*` pour 13 types de données
|
||||
- [x] Implémentation avec pagination complète
|
||||
|
||||
### ✅ Étape 5 : Android - Sync Service - COMPLÉTÉ
|
||||
- [x] Mettre à jour `HabitForgeApi.kt` avec le nouvel endpoint `/sync-raw`
|
||||
- [x] Créer les 15 classes de `*Payload` pour le format API
|
||||
- [x] Créer `RawDataSyncService.kt` - nouveau service de synchronisation
|
||||
|
||||
### ⏳ Étape 6 : Testing & Validation - À FAIRE
|
||||
- [ ] Appliquer la migration Alembic sur la base de données
|
||||
- [ ] Recompiler l'application Android
|
||||
- [ ] Tester la synchronisation complète
|
||||
- [ ] Valider que les agrégations sont correctes
|
||||
- [ ] Vérifier la cohérence entre les vues
|
||||
|
||||
---
|
||||
|
||||
## Fichiers Créés/Modifiés
|
||||
|
||||
### Backend (Python)
|
||||
| Fichier | Action | Description |
|
||||
|---------|--------|-------------|
|
||||
| `backend/models/health_connect_raw.py` | ✅ Créé | 15 modèles SQLAlchemy pour données brutes |
|
||||
| `backend/models/__init__.py` | ✅ Modifié | Imports des nouveaux modèles |
|
||||
| `backend/api/v1/health_connect_raw.py` | ✅ Créé | API endpoint /sync-raw + lecture |
|
||||
| `backend/services/aggregation_service.py` | ✅ Créé | Service d'agrégation on-demand |
|
||||
| `backend/main.py` | ✅ Modifié | Ajout du router |
|
||||
| `alembic/env.py` | ✅ Modifié | Import de tous les modèles |
|
||||
| `alembic/versions/f5g6h7i8j9k0_...py` | ✅ Créé | Migration pour 15 nouvelles tables |
|
||||
|
||||
### Android (Kotlin)
|
||||
| Fichier | Action | Description |
|
||||
|---------|--------|-------------|
|
||||
| `RawDataModels.kt` | ✅ Créé | Classes de données brutes |
|
||||
| `HealthConnectRawReader.kt` | ✅ Créé | Lecteur de données brutes HC |
|
||||
| `RawDataSyncService.kt` | ✅ Créé | Service de synchronisation raw |
|
||||
| `HabitForgeApi.kt` | ✅ Modifié | Payloads + endpoint raw |
|
||||
|
||||
---
|
||||
|
||||
## Prochaines Étapes pour Compléter l'Implémentation
|
||||
|
||||
1. **Appliquer la migration de base de données**:
|
||||
```bash
|
||||
# Dans le container Docker ou en local
|
||||
docker-compose exec web alembic upgrade head
|
||||
```
|
||||
|
||||
2. **Recompiler l'application Android**:
|
||||
```bash
|
||||
cd android-companion
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
3. **Tester la synchronisation**:
|
||||
- Installer l'APK sur le téléphone
|
||||
- Se connecter au backend
|
||||
- Déclencher une synchronisation manuelle
|
||||
- Vérifier les logs du backend pour les records reçus
|
||||
|
||||
4. **Intégrer le service d'agrégation avec le frontend** (optionnel):
|
||||
- Modifier les endpoints `/api/health/stats` existants pour utiliser `AggregationService`
|
||||
- Le frontend continuera de fonctionner tel quel
|
||||
|
||||
---
|
||||
|
||||
## Migration des Données Existantes
|
||||
|
||||
Les données déjà agrégées dans `health_connect_daily_metrics` seront conservées.
|
||||
Le nouveau système fonctionnera en parallèle.
|
||||
Une option de re-synchronisation complète permettra de repeupler les nouvelles tables avec les données brutes.
|
||||
|
||||
---
|
||||
|
||||
## Questions Résolues
|
||||
|
||||
1. **Volume de données** : Sync limité à 30 jours par défaut pour le service raw.
|
||||
|
||||
2. **Compatibilité** : L'ancien endpoint `/sync` est conservé pour backward compatibility.
|
||||
|
||||
3. **Performance** : Le service d'agrégation calcule à la demande. Un cache peut être ajouté ultérieurement.
|
||||
|
||||
---
|
||||
|
||||
## Estimation Mise à Jour
|
||||
|
||||
**Temps effectif : ~4 heures** (implémentation core)
|
||||
**Restant : ~1-2 heures** (testing et validation)
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
# Plan de Correction - HabitForge
|
||||
|
||||
**Date:** 2025-12-30
|
||||
**Basé sur:** `revue_critique_habitforge.md`
|
||||
**Objectif:** Transformer HabitForge en une application professionnelle et production-ready
|
||||
|
||||
---
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce plan organise les corrections nécessaires en 5 phases prioritaires, des problèmes critiques de sécurité jusqu'aux améliorations de l'expérience utilisateur.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Phase 1: Sécurité & Stabilité] --> B[Phase 2: Architecture & Qualité]
|
||||
B --> C[Phase 3: Tests & CI/CD]
|
||||
C --> D[Phase 4: Observabilité & Performance]
|
||||
D --> E[Phase 5: UX & Documentation]
|
||||
|
||||
A --> A1[Configuration env vars]
|
||||
A --> A2[Secrets management]
|
||||
A --> A3[CORS & Rate limiting]
|
||||
A --> A4[PostgreSQL migration]
|
||||
A --> A5[HTTPS & Nginx]
|
||||
|
||||
B --> B1[Restructuration backend]
|
||||
B --> B2[Couche de services]
|
||||
B --> B3[Modularisation frontend]
|
||||
B --> B4[DX tools]
|
||||
|
||||
C --> C1[Tests unitaires]
|
||||
C --> C2[Tests intégration]
|
||||
C --> C3[GitHub Actions CI/CD]
|
||||
|
||||
D --> D1[Logging structuré]
|
||||
D --> D2[Health checks]
|
||||
D --> D3[Performance DB]
|
||||
|
||||
E --> E1[Notifications UI]
|
||||
E --> E2[Documentation]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Sécurité & Stabilité (Critique)
|
||||
|
||||
### 1.1 Configuration et Environment Variables
|
||||
|
||||
**Objectif:** Éliminer toute configuration hardcodée et sécuriser les secrets.
|
||||
|
||||
#### Étape 1.1.1: Créer le module de configuration
|
||||
- [x] Créer `backend/core/config.py` avec `pydantic_settings`
|
||||
- [x] Définir la classe `Settings` avec tous les paramètres
|
||||
- [x] Configurer le chargement depuis `.env`
|
||||
- [x] Générer `SECRET_KEY` avec `token_urlsafe(32)` par défaut
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `backend/core/config.py` (nouveau)
|
||||
- `backend/core/__init__.py` (nouveau)
|
||||
|
||||
#### Étape 1.1.2: Créer les fichiers d'environnement
|
||||
- [x] Créer `.env.example` avec tous les paramètres documentés
|
||||
- [x] Créer `.env.development` (gitignored)
|
||||
- [ ] Créer `.env.staging` (gitignored)
|
||||
- [ ] Créer `.env.production` (gitignored)
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `.env.example`
|
||||
- `.env.development`
|
||||
- `.env.staging`
|
||||
- `.env.production`
|
||||
|
||||
#### Étape 1.1.3: Mettre à jour .gitignore
|
||||
- [ ] Ajouter `.env*` (sauf `.env.example`)
|
||||
- [ ] Ajouter `backend/client_secret*.json`
|
||||
- [ ] Ajouter `data/*.db`
|
||||
- [ ] Ajouter `__pycache__/`, `*.pyc`, `.pytest_cache/`
|
||||
- [ ] Ajouter `.coverage`, `htmlcov/`
|
||||
- [ ] Ajouter `node_modules/`, `.DS_Store`
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `.gitignore`
|
||||
|
||||
#### Étape 1.1.4: Migrer le code vers la configuration centralisée
|
||||
- [x] Modifier `backend/auth.py` pour utiliser `settings.SECRET_KEY`
|
||||
- [x] Modifier `backend/main.py` pour utiliser `settings.CORS_ORIGINS`
|
||||
- [x] Modifier `backend/database.py` pour utiliser `settings.DATABASE_URL`
|
||||
- [x] Modifier `backend/google_fit.py` pour utiliser les env vars Google
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/auth.py`
|
||||
- `backend/main.py`
|
||||
- `backend/database.py`
|
||||
- `backend/google_fit.py`
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Sécurité des Secrets
|
||||
|
||||
**Objectif:** Supprimer tous les secrets hardcodés du code.
|
||||
|
||||
#### Étape 1.2.1: Nettoyer les secrets Google
|
||||
- [x] Supprimer `backend/client_secret_947199050300-boi4r9r8eekmv61h748lrvjmvp1f2v38.apps.googleusercontent.com.json`
|
||||
- [x] Supprimer `backend/client_secret.json` du git
|
||||
- [ ] Documenter la configuration des credentials Google dans `.env.example`
|
||||
|
||||
**Fichiers à supprimer:**
|
||||
- `backend/client_secret_947199050300-boi4r9r8eekmv61h748lrvjmvp1f2v38.apps.googleusercontent.com.json`
|
||||
- `backend/client_secret.json`
|
||||
|
||||
#### Étape 1.2.2: Supprimer le SECRET_KEY hardcodé
|
||||
- [x] Remplacer `"supersecretkey_change_me_in_production"` dans `backend/auth.py`
|
||||
- [x] Utiliser `settings.SECRET_KEY` à la place
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/auth.py`
|
||||
|
||||
---
|
||||
|
||||
### 1.3 CORS et Rate Limiting
|
||||
|
||||
**Objectif:** Sécuriser les accès API et prévenir les attaques DoS.
|
||||
|
||||
#### Étape 1.3.1: Configurer CORS sécurisé
|
||||
- [x] Modifier `backend/main.py` pour utiliser `settings.CORS_ORIGINS`
|
||||
- [x] Supprimer `allow_origins=["*"]`
|
||||
- [x] Configurer `allow_credentials=True`
|
||||
- [x] Configurer `allow_methods` et `allow_headers` explicites
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/main.py`
|
||||
|
||||
#### Étape 1.3.2: Ajouter rate limiting
|
||||
- [x] Installer `slowapi`
|
||||
- [x] Configurer `Limiter` dans `backend/main.py`
|
||||
- [ ] Ajouter rate limit sur les endpoints d'authentification
|
||||
- [ ] Ajouter rate limit sur les endpoints de création/modification
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `requirements.txt`
|
||||
- `backend/main.py`
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Migration PostgreSQL
|
||||
|
||||
**Objectif:** Remplacer SQLite par PostgreSQL pour la production.
|
||||
|
||||
#### Étape 1.4.1: Préparer la migration
|
||||
- [ ] Installer `psycopg2-binary` dans `requirements.txt`
|
||||
- [ ] Créer un script de migration des données SQLite vers PostgreSQL
|
||||
- [ ] Documenter le processus de migration
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `scripts/migrate_to_postgres.py` (nouveau)
|
||||
|
||||
#### Étape 1.4.2: Configurer PostgreSQL
|
||||
- [ ] Mettre à jour `docker-compose.yml` pour PostgreSQL
|
||||
- [ ] Créer `docker-compose.dev.yml` pour développement
|
||||
- [ ] Créer `docker-compose.prod.yml` pour production
|
||||
- [ ] Configurer les volumes persistants
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `docker-compose.yml`
|
||||
- `docker-compose.dev.yml` (nouveau)
|
||||
- `docker-compose.prod.yml` (nouveau)
|
||||
|
||||
#### Étape 1.4.3: Exécuter la migration
|
||||
- [ ] Sauvegarder les données SQLite existantes
|
||||
- [ ] Exécuter le script de migration
|
||||
- [ ] Vérifier l'intégrité des données
|
||||
- [ ] Mettre à jour les migrations Alembic si nécessaire
|
||||
|
||||
---
|
||||
|
||||
### 1.5 HTTPS et Reverse Proxy
|
||||
|
||||
**Objectif:** Sécuriser les communications en production.
|
||||
|
||||
#### Étape 1.5.1: Configurer Nginx
|
||||
- [ ] Créer `nginx/nginx.conf`
|
||||
- [ ] Configurer SSL/TLS avec Let's Encrypt
|
||||
- [ ] Configurer le reverse proxy vers FastAPI
|
||||
- [ ] Servir les fichiers statiques via Nginx
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `nginx/nginx.conf` (nouveau)
|
||||
- `nginx/Dockerfile` (nouveau)
|
||||
|
||||
#### Étape 1.5.2: Configurer Gunicorn
|
||||
- [ ] Installer `gunicorn` dans `requirements.txt`
|
||||
- [ ] Créer un script de démarrage avec Gunicorn
|
||||
- [ ] Configurer le nombre de workers
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `scripts/start_prod.sh` (nouveau)
|
||||
|
||||
#### Étape 1.5.3: Mettre à jour Docker Compose
|
||||
- [ ] Ajouter le service Nginx dans `docker-compose.prod.yml`
|
||||
- [ ] Configurer les réseaux Docker
|
||||
- [ ] Configurer les health checks
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `docker-compose.prod.yml`
|
||||
|
||||
---
|
||||
|
||||
### 1.6 Backups de Base de Données
|
||||
|
||||
**Objectif:** Automatiser les sauvegardes de la base de données.
|
||||
|
||||
#### Étape 1.6.1: Créer le script de backup
|
||||
- [ ] Créer `scripts/backup_db.sh`
|
||||
- [ ] Configurer la rotation des backups
|
||||
- [ ] Compresser les backups
|
||||
- [ ] Envoyer les backups vers un stockage externe (optionnel)
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `scripts/backup_db.sh` (nouveau)
|
||||
|
||||
#### Étape 1.6.2: Automatiser les backups
|
||||
- [ ] Configurer un cron job dans Docker
|
||||
- [ ] Documenter la procédure de restauration
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `docker-compose.prod.yml`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Architecture & Qualité
|
||||
|
||||
### 2.1 Restructuration du Backend
|
||||
|
||||
**Objectif:** Organiser le code backend selon les principes de Clean Architecture.
|
||||
|
||||
#### Étape 2.1.1: Créer la nouvelle structure de dossiers
|
||||
- [x] Créer `backend/api/v1/` pour les routes
|
||||
- [x] Créer `backend/core/` pour la configuration et sécurité
|
||||
- [x] Créer `backend/models/` pour les SQLAlchemy models
|
||||
- [x] Créer `backend/schemas/` pour les Pydantic schemas
|
||||
- [x] Créer `backend/services/` pour la logique métier
|
||||
- [x] Créer `backend/repositories/` pour l'accès aux données
|
||||
- [x] Créer `backend/integrations/` pour les APIs externes
|
||||
|
||||
**Dossiers à créer:**
|
||||
- `backend/api/v1/`
|
||||
- `backend/core/`
|
||||
- `backend/models/`
|
||||
- `backend/schemas/`
|
||||
- `backend/services/`
|
||||
- `backend/repositories/`
|
||||
- `backend/integrations/`
|
||||
|
||||
#### Étape 2.1.2: Extraire les routes
|
||||
- [x] Créer `backend/api/v1/auth.py` avec les endpoints d'auth
|
||||
- [x] Créer `backend/api/v1/challenges.py` avec les endpoints challenges
|
||||
- [x] Créer `backend/api/v1/health.py` avec le health check
|
||||
- [x] Créer `backend/api/v1/__init__.py` pour l'agrégation
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `backend/api/v1/auth.py`
|
||||
- `backend/api/v1/challenges.py`
|
||||
- `backend/api/v1/health.py`
|
||||
- `backend/api/v1/__init__.py`
|
||||
|
||||
#### Étape 2.1.3: Séparer models et schemas
|
||||
- [x] Déplacer les SQLAlchemy models vers `backend/models/`
|
||||
- [x] Créer `backend/models/__init__.py`
|
||||
- [x] Extraire les Pydantic schemas vers `backend/schemas/`
|
||||
- [x] Créer `backend/schemas/__init__.py`
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `backend/models/user.py`
|
||||
- `backend/models/challenge.py`
|
||||
- `backend/models/tracking.py`
|
||||
- `backend/models/__init__.py`
|
||||
- `backend/schemas/user.py`
|
||||
- `backend/schemas/challenge.py`
|
||||
- `backend/schemas/tracking.py`
|
||||
- `backend/schemas/__init__.py`
|
||||
|
||||
#### Étape 2.1.4: Créer la couche de services
|
||||
- [ ] Créer `backend/services/auth_service.py`
|
||||
- [ ] Créer `backend/services/challenge_service.py`
|
||||
- [ ] Créer `backend/services/health_service.py`
|
||||
- [ ] Créer `backend/services/__init__.py`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `backend/services/auth_service.py`
|
||||
- `backend/services/challenge_service.py`
|
||||
- `backend/services/health_service.py`
|
||||
- `backend/services/__init__.py`
|
||||
|
||||
#### Étape 2.1.5: Créer la couche de repositories
|
||||
- [ ] Créer `backend/repositories/base.py` avec le repository de base
|
||||
- [ ] Créer `backend/repositories/user_repository.py`
|
||||
- [ ] Créer `backend/repositories/challenge_repository.py`
|
||||
- [ ] Créer `backend/repositories/__init__.py`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `backend/repositories/base.py`
|
||||
- `backend/repositories/user_repository.py`
|
||||
- `backend/repositories/challenge_repository.py`
|
||||
- `backend/repositories/__init__.py`
|
||||
|
||||
#### Étape 2.1.6: Déplacer les intégrations
|
||||
- [x] Déplacer `backend/google_fit.py` vers `backend/integrations/`
|
||||
- [x] Déplacer `backend/import_zepp.py` vers `backend/integrations/`
|
||||
- [x] Créer `backend/integrations/__init__.py`
|
||||
|
||||
**Fichiers à déplacer/créer:**
|
||||
- `backend/integrations/google_fit.py`
|
||||
- `backend/integrations/import_zepp.py`
|
||||
- `backend/integrations/__init__.py`
|
||||
|
||||
#### Étape 2.1.7: Créer les modules core
|
||||
- [ ] Créer `backend/core/security.py` avec les fonctions de sécurité
|
||||
- [ ] Créer `backend/core/deps.py` avec les dépendances FastAPI
|
||||
- [x] Créer `backend/core/__init__.py`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `backend/core/security.py`
|
||||
- `backend/core/deps.py`
|
||||
- `backend/core/__init__.py`
|
||||
|
||||
#### Étape 2.1.8: Simplifier main.py
|
||||
- [ ] Nettoyer `backend/main.py` pour ne contenir que l'initialisation
|
||||
- [ ] Supprimer `models.Base.metadata.create_all(bind=database.engine)`
|
||||
- [ ] Enregistrer les routers depuis `api/v1/`
|
||||
- [ ] Configurer le middleware de gestion d'erreurs
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/main.py`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Modularisation du Frontend
|
||||
|
||||
**Objectif:** Découper le fichier monolithique `app.js` en modules cohérents.
|
||||
|
||||
#### Étape 2.2.1: Créer la structure de modules
|
||||
- [ ] Créer `frontend/api/` pour les appels API
|
||||
- [ ] Créer `frontend/components/` pour les composants UI
|
||||
- [ ] Créer `frontend/utils/` pour les utilitaires
|
||||
- [ ] Créer `frontend/state/` pour la gestion d'état
|
||||
|
||||
**Dossiers à créer:**
|
||||
- `frontend/api/`
|
||||
- `frontend/components/`
|
||||
- `frontend/utils/`
|
||||
- `frontend/state/`
|
||||
|
||||
#### Étape 2.2.2: Créer le service API
|
||||
- [ ] Créer `frontend/api/client.js` avec le client HTTP
|
||||
- [ ] Créer `frontend/api/auth.js` pour les endpoints d'auth
|
||||
- [ ] Créer `frontend/api/challenges.js` pour les endpoints challenges
|
||||
- [ ] Supprimer le fetch wrapper monkey-patch
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `frontend/api/client.js`
|
||||
- `frontend/api/auth.js`
|
||||
- `frontend/api/challenges.js`
|
||||
|
||||
#### Étape 2.2.3: Extraire les composants UI
|
||||
- [ ] Créer `frontend/components/ChallengeList.js`
|
||||
- [ ] Créer `frontend/components/ChallengeForm.js`
|
||||
- [ ] Créer `frontend/components/TrackingForm.js`
|
||||
- [ ] Créer `frontend/components/Charts.js`
|
||||
- [ ] Créer `frontend/components/Navigation.js`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `frontend/components/ChallengeList.js`
|
||||
- `frontend/components/ChallengeForm.js`
|
||||
- `frontend/components/TrackingForm.js`
|
||||
- `frontend/components/Charts.js`
|
||||
- `frontend/components/Navigation.js`
|
||||
|
||||
#### Étape 2.2.4: Créer les utilitaires
|
||||
- [ ] Créer `frontend/utils/notifications.js` pour les toasts
|
||||
- [ ] Créer `frontend/utils/storage.js` pour localStorage
|
||||
- [ ] Créer `frontend/utils/date.js` pour les formats de date
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `frontend/utils/notifications.js`
|
||||
- `frontend/utils/storage.js`
|
||||
- `frontend/utils/date.js`
|
||||
|
||||
#### Étape 2.2.5: Créer la gestion d'état
|
||||
- [ ] Créer `frontend/state/store.js` avec un store simple
|
||||
- [ ] Créer `frontend/state/actions.js` pour les actions
|
||||
- [ ] Créer `frontend/state/reducers.js` pour les reducers
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `frontend/state/store.js`
|
||||
- `frontend/state/actions.js`
|
||||
- `frontend/state/reducers.js`
|
||||
|
||||
#### Étape 2.2.6: Simplifier app.js
|
||||
- [ ] Réécrire `app.js` pour utiliser les modules
|
||||
- [ ] Initialiser l'application
|
||||
- [ ] Gérer le routing basique
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `frontend/app.js`
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Expérience Développeur (DX)
|
||||
|
||||
**Objectif:** Améliorer les outils de développement.
|
||||
|
||||
#### Étape 2.3.1: Configurer Ruff (linter/formatter)
|
||||
- [ ] Installer `ruff` dans `requirements.txt`
|
||||
- [ ] Créer `ruff.toml` avec la configuration
|
||||
- [ ] Configurer les règles de linting
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `ruff.toml` (nouveau)
|
||||
|
||||
#### Étape 2.3.2: Configurer MyPy (type checking)
|
||||
- [ ] Installer `mypy` dans `requirements.txt`
|
||||
- [ ] Créer `mypy.ini` avec la configuration
|
||||
- [ ] Ajouter les stubs nécessaires
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `mypy.ini` (nouveau)
|
||||
|
||||
#### Étape 2.3.3: Configurer pre-commit hooks
|
||||
- [ ] Créer `.pre-commit-config.yaml`
|
||||
- [ ] Configurer les hooks Ruff et MyPy
|
||||
- [ ] Documenter l'installation
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `.pre-commit-config.yaml`
|
||||
|
||||
#### Étape 2.3.4: Créer un Makefile
|
||||
- [ ] Créer `Makefile` avec les commandes de dev
|
||||
- [ ] Ajouter les cibles: dev, test, lint, format, migrate
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `Makefile`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Tests & CI/CD
|
||||
|
||||
### 3.1 Tests Backend
|
||||
|
||||
**Objectif:** Mettre en place une couverture de tests minimale.
|
||||
|
||||
#### Étape 3.1.1: Configurer pytest
|
||||
- [ ] Installer `pytest`, `pytest-cov`, `pytest-asyncio` dans `requirements.txt`
|
||||
- [ ] Créer `pytest.ini` avec la configuration
|
||||
- [ ] Créer `tests/conftest.py` avec les fixtures
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `pytest.ini` (nouveau)
|
||||
- `tests/conftest.py` (nouveau)
|
||||
|
||||
#### Étape 3.1.2: Tests unitaires des services
|
||||
- [ ] Créer `tests/unit/test_auth_service.py`
|
||||
- [ ] Créer `tests/unit/test_challenge_service.py`
|
||||
- [ ] Créer `tests/unit/test_health_service.py`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `tests/unit/test_auth_service.py`
|
||||
- `tests/unit/test_challenge_service.py`
|
||||
- `tests/unit/test_health_service.py`
|
||||
|
||||
#### Étape 3.1.3: Tests d'intégration API
|
||||
- [ ] Créer `tests/integration/test_api_auth.py`
|
||||
- [ ] Créer `tests/integration/test_api_challenges.py`
|
||||
- [ ] Créer `tests/integration/test_api_health.py`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `tests/integration/test_api_auth.py`
|
||||
- `tests/integration/test_api_challenges.py`
|
||||
- `tests/integration/test_api_health.py`
|
||||
|
||||
#### Étape 3.1.4: Tests E2E
|
||||
- [ ] Créer `tests/e2e/test_user_flow.py`
|
||||
- [ ] Tester le flux complet: inscription → login → création challenge → tracking
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `tests/e2e/test_user_flow.py`
|
||||
|
||||
---
|
||||
|
||||
### 3.2 CI/CD avec GitHub Actions
|
||||
|
||||
**Objectif:** Automatiser les tests et le déploiement.
|
||||
|
||||
#### Étape 3.2.1: Créer le workflow CI
|
||||
- [ ] Créer `.github/workflows/ci.yml`
|
||||
- [ ] Configurer le job de tests
|
||||
- [ ] Configurer le job de linting
|
||||
- [ ] Configurer le reporting de couverture
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `.github/workflows/ci.yml`
|
||||
|
||||
#### Étape 3.2.2: Créer le workflow CD
|
||||
- [ ] Créer `.github/workflows/cd.yml`
|
||||
- [ ] Configurer le déploiement vers staging
|
||||
- [ ] Configurer le déploiement vers production
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `.github/workflows/cd.yml`
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Observabilité & Performance
|
||||
|
||||
### 4.1 Observabilité
|
||||
|
||||
**Objectif:** Mettre en place le monitoring et le logging.
|
||||
|
||||
#### Étape 4.1.1: Logging structuré
|
||||
- [ ] Installer `python-json-logger` dans `requirements.txt`
|
||||
- [ ] Créer `backend/core/logging.py`
|
||||
- [ ] Configurer le logging JSON
|
||||
- [ ] Remplacer les `print()` par des `logger.info()`
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `backend/core/logging.py`
|
||||
|
||||
#### Étape 4.1.2: Health check endpoint
|
||||
- [ ] Créer le endpoint `/api/health`
|
||||
- [ ] Vérifier la connexion à la base de données
|
||||
- [ ] Retourner le statut de l'application
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/api/v1/health.py`
|
||||
|
||||
#### Étape 4.1.3: Métriques Prometheus
|
||||
- [ ] Installer `prometheus-fastapi-instrumentator` dans `requirements.txt`
|
||||
- [ ] Configurer l'instrumentation dans `main.py`
|
||||
- [ ] Exposer le endpoint `/metrics`
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `requirements.txt`
|
||||
- `backend/main.py`
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Performance Base de Données
|
||||
|
||||
**Objectif:** Optimiser les requêtes et l'indexation.
|
||||
|
||||
#### Étape 4.2.1: Ajouter des indexes
|
||||
- [ ] Créer une migration Alembic pour les indexes
|
||||
- [ ] Ajouter index sur `challenges(user_id, display_order)`
|
||||
- [ ] Ajouter index sur `challenges(user_id, period_type)`
|
||||
- [ ] Ajouter index sur `tracking(challenge_id, date)`
|
||||
- [ ] Ajouter index sur `tracking(date)`
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `alembic/versions/xxxx_add_indexes.py`
|
||||
|
||||
#### Étape 4.2.2: Eager loading
|
||||
- [ ] Modifier les repositories pour utiliser `selectinload`
|
||||
- [ ] Éliminer les N+1 queries
|
||||
- [ ] Optimiser les requêtes d'agrégation
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `backend/repositories/challenge_repository.py`
|
||||
|
||||
#### Étape 4.2.3: Pagination
|
||||
- [ ] Ajouter la pagination aux endpoints de liste
|
||||
- [ ] Créer les schemas de pagination
|
||||
- [ ] Documenter les paramètres de pagination
|
||||
|
||||
**Fichiers à créer/modifier:**
|
||||
- `backend/schemas/pagination.py`
|
||||
- `backend/api/v1/challenges.py`
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: UX & Documentation
|
||||
|
||||
### 5.1 Améliorations UX
|
||||
|
||||
**Objectif:** Améliorer l'expérience utilisateur.
|
||||
|
||||
#### Étape 5.1.1: Notifications/Toasts
|
||||
- [ ] Créer le système de notifications dans `frontend/utils/notifications.js`
|
||||
- [ ] Ajouter des toasts pour les actions réussies
|
||||
- [ ] Ajouter des toasts pour les erreurs
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `frontend/utils/notifications.js`
|
||||
|
||||
#### Étape 5.1.2: Loading states
|
||||
- [ ] Ajouter des spinners pendant les appels API
|
||||
- [ ] Désactiver les boutons pendant le chargement
|
||||
- [ ] Ajouter des indicateurs de progression
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `frontend/components/ChallengeForm.js`
|
||||
- `frontend/components/TrackingForm.js`
|
||||
|
||||
#### Étape 5.1.3: Gestion d'erreurs utilisateur
|
||||
- [ ] Afficher des messages d'erreur clairs
|
||||
- [ ] Proposer des solutions aux erreurs courantes
|
||||
- [ ] Logger les erreurs côté client
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `frontend/api/client.js`
|
||||
- `frontend/app.js`
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Documentation
|
||||
|
||||
**Objectif:** Documenter le projet pour les développeurs.
|
||||
|
||||
#### Étape 5.2.1: Docstrings API
|
||||
- [ ] Ajouter des docstrings à toutes les fonctions
|
||||
- [ ] Documenter les paramètres et les retours
|
||||
- [ ] Utiliser le format Google ou NumPy
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- Tous les fichiers Python dans `backend/`
|
||||
|
||||
#### Étape 5.2.2: README amélioré
|
||||
- [ ] Mettre à jour `README.md`
|
||||
- [ ] Ajouter la section Installation
|
||||
- [ ] Ajouter la section Configuration
|
||||
- [ ] Ajouter la section Développement
|
||||
- [ ] Ajouter la section Déploiement
|
||||
|
||||
**Fichiers à modifier:**
|
||||
- `README.md`
|
||||
|
||||
#### Étape 5.2.3: Architecture Decision Records (ADRs)
|
||||
- [ ] Créer `docs/adr/`
|
||||
- [ ] Documenter les décisions d'architecture majeures
|
||||
- [ ] Utiliser le template ADR standard
|
||||
|
||||
**Fichiers à créer:**
|
||||
- `docs/adr/001-architecture-backend.md`
|
||||
- `docs/adr/002-choix-postgresql.md`
|
||||
- `docs/adr/003-securite-jwt.md`
|
||||
|
||||
---
|
||||
|
||||
## Résumé des Fichiers à Créer
|
||||
|
||||
### Configuration
|
||||
- `.env.example`
|
||||
- `.env.development`
|
||||
- `.env.staging`
|
||||
- `.env.production`
|
||||
- `ruff.toml`
|
||||
- `mypy.ini`
|
||||
- `pytest.ini`
|
||||
- `.pre-commit-config.yaml`
|
||||
- `Makefile`
|
||||
|
||||
### Backend - Core
|
||||
- `backend/core/config.py`
|
||||
- `backend/core/security.py`
|
||||
- `backend/core/deps.py`
|
||||
- `backend/core/logging.py`
|
||||
- `backend/core/__init__.py`
|
||||
|
||||
### Backend - API
|
||||
- `backend/api/v1/auth.py`
|
||||
- `backend/api/v1/challenges.py`
|
||||
- `backend/api/v1/health.py`
|
||||
- `backend/api/v1/__init__.py`
|
||||
|
||||
### Backend - Models
|
||||
- `backend/models/user.py`
|
||||
- `backend/models/challenge.py`
|
||||
- `backend/models/tracking.py`
|
||||
- `backend/models/__init__.py`
|
||||
|
||||
### Backend - Schemas
|
||||
- `backend/schemas/user.py`
|
||||
- `backend/schemas/challenge.py`
|
||||
- `backend/schemas/tracking.py`
|
||||
- `backend/schemas/pagination.py`
|
||||
- `backend/schemas/__init__.py`
|
||||
|
||||
### Backend - Services
|
||||
- `backend/services/auth_service.py`
|
||||
- `backend/services/challenge_service.py`
|
||||
- `backend/services/health_service.py`
|
||||
- `backend/services/__init__.py`
|
||||
|
||||
### Backend - Repositories
|
||||
- `backend/repositories/base.py`
|
||||
- `backend/repositories/user_repository.py`
|
||||
- `backend/repositories/challenge_repository.py`
|
||||
- `backend/repositories/__init__.py`
|
||||
|
||||
### Backend - Integrations
|
||||
- `backend/integrations/google_fit.py`
|
||||
- `backend/integrations/import_zepp.py`
|
||||
- `backend/integrations/__init__.py`
|
||||
|
||||
### Frontend - API
|
||||
- `frontend/api/client.js`
|
||||
- `frontend/api/auth.js`
|
||||
- `frontend/api/challenges.js`
|
||||
|
||||
### Frontend - Components
|
||||
- `frontend/components/ChallengeList.js`
|
||||
- `frontend/components/ChallengeForm.js`
|
||||
- `frontend/components/TrackingForm.js`
|
||||
- `frontend/components/Charts.js`
|
||||
- `frontend/components/Navigation.js`
|
||||
|
||||
### Frontend - Utils
|
||||
- `frontend/utils/notifications.js`
|
||||
- `frontend/utils/storage.js`
|
||||
- `frontend/utils/date.js`
|
||||
|
||||
### Frontend - State
|
||||
- `frontend/state/store.js`
|
||||
- `frontend/state/actions.js`
|
||||
- `frontend/state/reducers.js`
|
||||
|
||||
### Tests
|
||||
- `tests/conftest.py`
|
||||
- `tests/unit/test_auth_service.py`
|
||||
- `tests/unit/test_challenge_service.py`
|
||||
- `tests/unit/test_health_service.py`
|
||||
- `tests/integration/test_api_auth.py`
|
||||
- `tests/integration/test_api_challenges.py`
|
||||
- `tests/integration/test_api_health.py`
|
||||
- `tests/e2e/test_user_flow.py`
|
||||
|
||||
### CI/CD
|
||||
- `.github/workflows/ci.yml`
|
||||
- `.github/workflows/cd.yml`
|
||||
|
||||
### Docker & Nginx
|
||||
- `docker-compose.dev.yml`
|
||||
- `docker-compose.prod.yml`
|
||||
- `nginx/nginx.conf`
|
||||
- `nginx/Dockerfile`
|
||||
|
||||
### Scripts
|
||||
- `scripts/migrate_to_postgres.py`
|
||||
- `scripts/backup_db.sh`
|
||||
- `scripts/start_prod.sh`
|
||||
|
||||
### Documentation
|
||||
- `docs/adr/001-architecture-backend.md`
|
||||
- `docs/adr/002-choix-postgresql.md`
|
||||
- `docs/adr/003-securite-jwt.md`
|
||||
|
||||
---
|
||||
|
||||
## Checklist de Validation
|
||||
|
||||
Avant de considérer le projet comme "production-ready", vérifier:
|
||||
|
||||
### Sécurité
|
||||
- [ ] Aucun secret hardcodé dans le code
|
||||
- [ ] CORS configuré avec des origines spécifiques
|
||||
- [ ] Rate limiting activé
|
||||
- [ ] HTTPS configuré en production
|
||||
- [ ] Secrets Google exclus du git
|
||||
|
||||
### Base de Données
|
||||
- [ ] PostgreSQL utilisé en production
|
||||
- [ ] Backups automatisés en place
|
||||
- [ ] Indexes créés sur les colonnes fréquentes
|
||||
- [ ] N+1 queries éliminées
|
||||
|
||||
### Architecture
|
||||
- [ ] Backend restructuré avec séparation des responsabilités
|
||||
- [ ] Couche de services créée
|
||||
- [ ] Frontend modularisé
|
||||
- [ ] Configuration centralisée
|
||||
|
||||
### Tests
|
||||
- [ ] Tests unitaires backend créés
|
||||
- [ ] Tests d'intégration API créés
|
||||
- [ ] Tests E2E créés
|
||||
- [ ] Couverture minimale de 70%
|
||||
|
||||
### CI/CD
|
||||
- [ ] Pipeline CI configuré
|
||||
- [ ] Pipeline CD configuré
|
||||
- [ ] Tests exécutés automatiquement
|
||||
|
||||
### Observabilité
|
||||
- [ ] Logging structuré en place
|
||||
- [ ] Health check endpoint créé
|
||||
- [ ] Métriques Prometheus exposées
|
||||
|
||||
### DX
|
||||
- [ ] Linter configuré (Ruff)
|
||||
- [ ] Type checker configuré (MyPy)
|
||||
- [ ] Pre-commit hooks configurés
|
||||
- [ ] Makefile créé
|
||||
|
||||
### Documentation
|
||||
- [ ] README à jour
|
||||
- [ ] Docstrings ajoutées
|
||||
- [ ] ADRs créés
|
||||
|
||||
---
|
||||
|
||||
## Notes Importantes
|
||||
|
||||
1. **Ordre d'exécution:** Les phases doivent être exécutées dans l'ordre indiqué. La Phase 1 (Sécurité) est critique et doit être complétée avant tout déploiement en production.
|
||||
|
||||
2. **Tests continus:** À chaque étape, exécuter les tests existants pour s'assurer que rien n'est cassé.
|
||||
|
||||
3. **Documentation:** Mettre à jour la documentation à mesure que des changements sont apportés.
|
||||
|
||||
4. **Commits:** Faire des commits fréquents et descriptifs pour faciliter le rollback si nécessaire.
|
||||
|
||||
5. **Review:** Faire une revue de code après chaque phase majeure.
|
||||
|
||||
---
|
||||
|
||||
**Fin du Plan de Correction**
|
||||
@@ -0,0 +1,720 @@
|
||||
# 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`](backend/main.py:1) | Important | Testabilité, Réutilisation |
|
||||
| 3 | **Frontend non structuré** - [`app.js`](frontend/app.js:1) 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`](backend/models.py:1) | 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`](backend/main.py:15)) - Contourne Alembic | Important | Supprimer `create_all`, utiliser uniquement Alembic |
|
||||
| 2 | **CORS permissif** ([`main.py:25`](backend/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`](backend/main.py:278) | Nice-to-have | Extraire dans des fonctions utilitaires |
|
||||
| 6 | **Imports cycliques potentiels** - [`import_zepp.py`](backend/import_zepp.py:8) importe depuis backend | Important | Restructurer les imports |
|
||||
|
||||
**Exemple de code problématique:**
|
||||
```python
|
||||
# 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`](frontend/app.js:1) = 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`](frontend/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`](backend/auth.py:7)) - `"supersecretkey_change_me_in_production"` | Critique | Compromission JWT | Utiliser `python-secrets` + env var |
|
||||
| 2 | **CORS ouvert** ([`main.py:25`](backend/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/`](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`:**
|
||||
```python
|
||||
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`:**
|
||||
```env
|
||||
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`](backend/main.py:116) 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:**
|
||||
```python
|
||||
# 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:**
|
||||
```python
|
||||
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:**
|
||||
```yaml
|
||||
# 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é:**
|
||||
```python
|
||||
# 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:**
|
||||
```python
|
||||
@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:**
|
||||
```python
|
||||
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:**
|
||||
```python
|
||||
# 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`:**
|
||||
```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`:**
|
||||
```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:**
|
||||
```javascript
|
||||
// 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:**
|
||||
```yaml
|
||||
# .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 `bumpversion` ou `semantic-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)
|
||||
|
||||
1. **Sécurité:** SECRET_KEY hardcodé, CORS ouvert, secrets exposés
|
||||
2. **Base de données:** SQLite en prod, pas de backups
|
||||
3. **CI/CD:** Aucun pipeline de tests/déploiement
|
||||
4. **HTTPS:** Pas de SSL/TLS
|
||||
5. **Rate limiting:** Protection contre DoS
|
||||
|
||||
### Important (Qualité professionnelle)
|
||||
|
||||
1. **Architecture:** Séparation des responsabilités, couche de services
|
||||
2. **Tests:** Couverture de tests minimale
|
||||
2. **Observabilité:** Logging structuré, métriques, health checks
|
||||
3. **Configuration:** Environment variables, multi-env
|
||||
4. **Performance:** Pagination, eager loading, cache
|
||||
5. **DX:** Linters, formatters, pre-commit hooks
|
||||
|
||||
### Nice-to-have (Améliorations)
|
||||
|
||||
1. **Frontend framework:** Migration vers React/Vue
|
||||
2. **TypeScript:** Typage statique
|
||||
3. **Background tasks:** Celery pour tâches async
|
||||
4. **Monitoring avancé:** Sentry, Datadog
|
||||
5. **Documentation:** MkDocs/Sphinx
|
||||
|
||||
---
|
||||
|
||||
## 15. Feuille de Route Priorisée
|
||||
|
||||
### Phase 1: Sécurité & Stabilité (1-2 semaines)
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```mermaid
|
||||
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:
|
||||
1. Corriger les problèmes de sécurité critiques
|
||||
2. Migrer vers PostgreSQL
|
||||
3. Ajouter des tests de base
|
||||
4. Configurer HTTPS
|
||||
5. Mettre en place un pipeline CI/CD
|
||||
|
||||
Avec ces améliorations, HabitForge pourrait devenir une application professionnelle robuste et crédible.
|
||||
Reference in New Issue
Block a user