Merge pull request #105 from R0m1k3/claude/fix-time-offset-bug-01FzrcjKh9o5emsU6hRMMLRL

Add: Système de logging complet pour débuguer le décalage horaire
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-16 18:27:16 +01:00
commit b1c34970e6
3 files changed
+329 -6

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
# 🔍 SYSTÈME DE LOGGING POUR DÉBUGUER LE TIMEZONE
Ce document explique comment utiliser le système de logging complet pour identifier le problème de décalage horaire.
## 📋 Logs ajoutés
### 1. **Parser PostgreSQL** (`config/database.js`)
Logs chaque conversion TIMESTAMPTZ → ISO string
### 2. **Synchronisation Google Calendar** (`routes/calendar.routes.js`)
Logs ce que Google renvoie et ce qui est stocké
### 3. **Récupération des événements** (`routes/calendar.routes.js`)
Logs ce qui est lu de la DB et envoyé au frontend
### 4. **Endpoint de diagnostic** (`GET /api/calendar/debug`)
Affiche un rapport complet du traitement
---
## 🚀 PROCÉDURE COMPLÈTE
### **ÉTAPE 1 : Rebuild avec les logs**
```bash
docker-compose down
docker-compose build --no-cache notes-app
docker-compose up -d
```
### **ÉTAPE 2 : Supprimer les anciens événements**
```bash
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 \
-c "DELETE FROM calendar_events"
```
### **ÉTAPE 3 : Activer les logs debug**
Suivre les logs en temps réel :
```bash
docker-compose logs -f notes-app
```
### **ÉTAPE 4 : Synchroniser avec Google Calendar**
1. Ouvrez http://localhost:2222
2. Allez dans Admin → Google Calendar
3. Cliquez sur "🔄 Synchroniser"
**Observez les logs** dans votre terminal :
```
[SYNC CALENDAR] Événement: "Dr Julian Wlodarczak"
[SYNC CALENDAR] - Google startTime brut: {"dateTime":"2024-11-17T10:20:00+01:00"}
[SYNC CALENDAR] - startTime extrait: "2024-11-17T10:20:00+01:00"
[SYNC CALENDAR] - Type: string
[SYNC CALENDAR] - new Date(startTime): 2024-11-17T09:20:00.000Z
[SYNC CALENDAR] - Affichage Paris: 17/11/2024 10:20:00
```
**Puis le parser PostgreSQL** :
```
[PARSER TIMESTAMPTZ] Input avec TZ: "2024-11-17 09:20:00+00" → Output: "2024-11-17T09:20:00.000Z"
```
**OU**
```
[PARSER TIMESTAMPTZ] Input sans TZ: "2024-11-17 09:20:00" → ISO+Z: "2024-11-17T09:20:00Z" → Output: "2024-11-17T09:20:00.000Z"
```
### **ÉTAPE 5 : Récupérer les événements**
Rechargez la page NoteFlow et observez les logs :
```
[GET /events] "Dr Julian Wlodarczak"
[GET /events] - start_time de la DB: "2024-11-17T09:20:00.000Z" (type: string)
[GET /events] - new Date(): 2024-11-17T09:20:00.000Z
[GET /events] - Affichage Paris: 10:20
```
### **ÉTAPE 6 : Utiliser l'endpoint de diagnostic**
Appelez l'endpoint de diagnostic pour voir un rapport complet :
```bash
curl -H "Authorization: Bearer VOTRE_TOKEN" \
http://localhost:2222/api/calendar/debug | jq
```
**OU** avec le navigateur :
1. Ouvrez http://localhost:2222
2. Ouvrez la console développeur (F12)
3. Exécutez :
```javascript
fetch('/api/calendar/debug', {
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
})
.then(r => r.json())
.then(console.log)
```
**Résultat attendu** :
```json
{
"timestamp": "2024-11-17T...",
"serverTimezone": "UTC",
"postgresTimezone": "UTC",
"sampleEvent": {
"title": "Dr Julian Wlodarczak",
"type_col": "timestamp with time zone",
"start_time_raw": "2024-11-17T09:20:00.000Z",
"start_time_type": "string",
"start_text": "2024-11-17 09:20:00+00",
"start_utc_formatted": "2024-11-17T09:20:00Z",
"start_paris_formatted": "2024-11-17 10:20:00"
},
"parsing": {
"input": "2024-11-17T09:20:00.000Z",
"inputType": "string",
"dateObject": "2024-11-17T09:20:00.000Z",
"displayUTC": "Sun, 17 Nov 2024 09:20:00 GMT",
"displayParis": "17/11/2024 10:20:00",
"displayParisTime": "10:20"
}
}
```
---
## 🔍 ANALYSE DES LOGS
### **Si tout est correct** :
1. Google renvoie : `"2024-11-17T10:20:00+01:00"` (10:20 Paris)
2. Conversion en UTC : `2024-11-17T09:20:00.000Z` (09:20 UTC)
3. PostgreSQL stocke : `2024-11-17 09:20:00+00` (09:20 UTC)
4. Parser renvoie : `"2024-11-17T09:20:00.000Z"`
5. Frontend affiche : `10:20` ✅
### **Si le décalage persiste** :
Cherchez dans les logs où la conversion échoue :
- ❌ **Google renvoie une heure incorrecte** → Problème API Google
- ❌ **Conversion UTC incorrecte** → Problème dans `new Date()`
- ❌ **PostgreSQL stocke mal** → Problème de timezone PostgreSQL
- ❌ **Parser renvoie mal** → Problème dans le parser
- ❌ **Frontend affiche mal** → Problème dans `toLocaleTimeString`
---
## 📊 COMMANDES UTILES
### Voir les logs en temps réel :
```bash
docker-compose logs -f notes-app
```
### Voir seulement les logs de timezone :
```bash
docker-compose logs -f notes-app | grep -E "\[SYNC CALENDAR\]|\[PARSER TIMESTAMPTZ\]|\[GET /events\]"
```
### Vérifier le timezone PostgreSQL :
```bash
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "SHOW timezone"
```
### Voir les événements bruts dans PostgreSQL :
```bash
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "
SELECT
title,
start_time,
start_time::text,
start_time AT TIME ZONE 'Europe/Paris' as paris_time
FROM calendar_events
ORDER BY start_time DESC
LIMIT 5
"
```
---
## 📝 RAPPORT À ENVOYER
Si le problème persiste, envoyez :
1. **Les logs de synchronisation** (ÉTAPE 4)
2. **Les logs de récupération** (ÉTAPE 5)
3. **Le résultat de l'endpoint debug** (ÉTAPE 6)
4. **Capture d'écran de NoteFlow** montrant le décalage
5. **Capture d'écran de Google Calendar** montrant l'heure correcte
Avec ces informations, je pourrai identifier EXACTEMENT où se produit le décalage.
---
## ⚠️ IMPORTANT
Les logs sont en mode `debug` et peuvent être volumineux. Pour production, pensez à :
1. Réduire le niveau de log
2. Ou désactiver les logs une fois le problème résolu
+15 -6
View File
@@ -11,19 +11,28 @@ types.setTypeParser(1184, function(stringValue) {
// 1184 = TIMESTAMPTZ
if (!stringValue) return null;
// LOG pour debug
const originalValue = stringValue;
// PostgreSQL avec timezone=UTC renvoie: "2024-11-17 09:20:00" ou "2024-11-17 09:20:00+00"
// On doit toujours renvoyer une ISO string UTC propre avec 'Z'
let result;
// Si déjà au format ISO avec Z ou timezone (+HH:MM ou +HH)
if (stringValue.includes('Z') || stringValue.match(/[+-]\d{2}(:\d{2})?$/)) {
return new Date(stringValue).toISOString();
result = new Date(stringValue).toISOString();
logger.debug(`[PARSER TIMESTAMPTZ] Input avec TZ: "${originalValue}" → Output: "${result}"`);
} else {
// Si format "YYYY-MM-DD HH:MM:SS" sans timezone
// Comme timezone=UTC, on sait que c'est en UTC
// On ajoute 'Z' pour forcer JavaScript à l'interpréter comme UTC
const isoString = stringValue.replace(' ', 'T') + 'Z';
result = new Date(isoString).toISOString();
logger.debug(`[PARSER TIMESTAMPTZ] Input sans TZ: "${originalValue}" → ISO+Z: "${isoString}" → Output: "${result}"`);
}
// Si format "YYYY-MM-DD HH:MM:SS" sans timezone
// Comme timezone=UTC, on sait que c'est en UTC
// On ajoute 'Z' pour forcer JavaScript à l'interpréter comme UTC
const isoString = stringValue.replace(' ', 'T') + 'Z';
return new Date(isoString).toISOString();
return result;
});
// Configuration PostgreSQL depuis DATABASE_URL ou variables d'environnement
+103
View File
@@ -352,6 +352,17 @@ router.get('/events', authenticateToken, async (req, res) => {
LIMIT $3
`, [req.user.id, now, limit]);
// LOG pour chaque événement récupéré
events.forEach(event => {
logger.debug(`[GET /events] "${event.title}"`);
logger.debug(`[GET /events] - start_time de la DB: "${event.start_time}" (type: ${typeof event.start_time})`);
if (event.start_time) {
const date = new Date(event.start_time);
logger.debug(`[GET /events] - new Date(): ${date.toISOString()}`);
logger.debug(`[GET /events] - Affichage Paris: ${date.toLocaleString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })}`);
}
});
res.json(events || []);
} catch (error) {
logger.error('Erreur lors de la récupération des événements:', error);
@@ -359,6 +370,87 @@ router.get('/events', authenticateToken, async (req, res) => {
}
});
/**
* GET /api/calendar/debug
* Endpoint de diagnostic détaillé pour le timezone
*/
router.get('/debug', authenticateToken, async (req, res) => {
try {
const diagnostics = {
timestamp: new Date().toISOString(),
serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
postgresTimezone: null,
sampleEvent: null,
parsing: {
input: null,
output: null,
display: null
}
};
// Récupérer le timezone de PostgreSQL
const tzResult = await getOne('SHOW timezone');
diagnostics.postgresTimezone = tzResult?.timezone || tzResult?.TimeZone;
// Récupérer un événement pour test
const event = await getOne(`
SELECT
title,
start_time,
pg_typeof(start_time) as type_col,
start_time::text as start_text,
to_char(start_time AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as start_utc_formatted,
to_char(start_time AT TIME ZONE 'Europe/Paris', 'YYYY-MM-DD HH24:MI:SS') as start_paris_formatted
FROM calendar_events
WHERE user_id = $1
ORDER BY start_time DESC
LIMIT 1
`, [req.user.id]);
if (event) {
diagnostics.sampleEvent = {
title: event.title,
type_col: event.type_col,
start_time_raw: event.start_time,
start_time_type: typeof event.start_time,
start_text: event.start_text,
start_utc_formatted: event.start_utc_formatted,
start_paris_formatted: event.start_paris_formatted
};
// Test de parsing
if (event.start_time) {
const date = new Date(event.start_time);
diagnostics.parsing = {
input: event.start_time,
inputType: typeof event.start_time,
dateObject: date.toISOString(),
displayUTC: date.toUTCString(),
displayParis: date.toLocaleString('fr-FR', {
timeZone: 'Europe/Paris',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}),
displayParisTime: date.toLocaleTimeString('fr-FR', {
timeZone: 'Europe/Paris',
hour: '2-digit',
minute: '2-digit'
})
};
}
}
res.json(diagnostics);
} catch (error) {
logger.error('Erreur lors du diagnostic:', error);
res.status(500).json({ error: error.message });
}
});
/**
* POST /api/calendar/sync
* Synchroniser avec Google Calendar (OAuth2, Service Account ou API Key)
@@ -410,6 +502,17 @@ router.post('/sync', authenticateToken, async (req, res) => {
const endTime = event.end.dateTime || event.end.date;
const isAllDay = !event.start.dateTime; // Si pas de dateTime, c'est un événement toute la journée
// LOG DÉTAILLÉ pour debug timezone
logger.info(`[SYNC CALENDAR] Événement: "${event.summary}"`);
logger.info(`[SYNC CALENDAR] - Google startTime brut: ${JSON.stringify(event.start)}`);
logger.info(`[SYNC CALENDAR] - startTime extrait: "${startTime}"`);
logger.info(`[SYNC CALENDAR] - Type: ${typeof startTime}`);
if (startTime) {
const testDate = new Date(startTime);
logger.info(`[SYNC CALENDAR] - new Date(startTime): ${testDate.toISOString()}`);
logger.info(`[SYNC CALENDAR] - Affichage Paris: ${testDate.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' })}`);
}
// Vérifier si l'événement existe déjà
const existing = await getOne(
'SELECT id FROM calendar_events WHERE google_event_id = $1 AND user_id = $2',